How to scale system font size to support Dynamic Type

Issue #847 We should use Dynamic Font Type as much as possible, as per Typography guide and https://www.iosfontsizes.com/ But in case we have to use a specific font, we can scale it with UIFontMetrics import SwiftUI import UIKit extension Font { static func system( scaledSize size: CGFloat, weight: Font.Weight = .regular, design: Font.Design = .default ) -> Font { Font.system( size: UIFontMetrics.default.scaledValue(for: size), weight: weight, design: design ) } } Then instead of...

September 2, 2021 路 1 min 路 Khoa Pham

How to make multiline message text view in SwiftUI

Issue #843 This can be used in message bar input or some popover form. We use sizeThatFits to calculate the height so that it grow under certain height limit import SwiftUI import UIKit struct MessageTextField: View { let placeholder: String @Binding var text: String @Binding var isEditing: Bool @State private var height: CGFloat = 100 var body: some View { UITextViewWrapper( text: $text, isEditing: $isEditing, height: $height ) .frame(height: height) ....

August 21, 2021 路 2 min 路 Khoa Pham

How to make equal width buttons in SwiftUI

Issue #835 I usually define ButtonStyle to encapsulate common styles related to buttons. Here we specify .frame(maxWidth: .infinity) to let this button take the whole width as possible struct MyActionButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.headline.bold()) .foregroundColor(.white) .frame(height: 44) .frame(maxWidth: .infinity) .background(Color.green) .cornerRadius(8) } } If we have 2 buttons both specifying maxWidth: .infinity then they will be divided equally with a spacing specified by our HStack...

August 5, 2021 路 1 min 路 Khoa Pham

How to do NavigationLink programatically in SwiftUI

Issue #824 Use a custom NavigationLink with EmptyView as the background, this failable initializer accepts Binding of optional value. This works well as the destination are made lazily. extension NavigationLink where Label == EmptyView { init?<Value>( _ binding: Binding<Value?>, @ViewBuilder destination: (Value) -> Destination ) { guard let value = binding.wrappedValue else { return nil } let isActive = Binding( get: { true }, set: { newValue in if !newValue { binding....

July 20, 2021 路 1 min 路 Khoa Pham

How to make custom navigation bar in SwiftUI

Issue #823 Make atomic components and compose them. Firstly with NavigationBar that has custom leading and trailing content, there we can customize padding. import SwiftUI struct NavigationBar<Leading: View, Trailing: View>: View { @ViewBuilder let leading: Leading @ViewBuilder let trailing: Trailing var body: some View { HStack { leading Spacer() trailing } .padding(.horizontal, 8) } } struct NavigationBarWithBackButton: View { let onBack: () -> Void var body: some View { NavigationBar( leading: backButton, trailing: EmptyView....

July 20, 2021 路 1 min 路 Khoa Pham

How to highlight link in Text in SwiftUI

Issue #820 Use NSDataDetector to detect links, and NSMutableAttributedString to mark link range. Then we enumerateAttributes and build our Text func attribute(string: String) -> Text { guard let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else { return Text(string) } let stringRange = NSRange(location: 0, length: string.count) let matches = detector.matches( in: string, options: [], range: stringRange ) let attributedString = NSMutableAttributedString(string: string) for match in matches { attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single, range: match....

July 6, 2021 路 1 min 路 Khoa Pham

How to cancel vertical scrolling on paging TabView in SwiftUI

Issue #814 From iOS 14, TabView has the PageTabViewStyle that turns TabView into the equivalent UIPageViewController. We can of course implement our own Pager but the simple DragGesture does not bring the true experience of a paging UIScrollView or paging TabView. We can also use UIPageViewController under the hood but it鈥檚 hard to do lazy. Paging TabView in iOS 14 is built int and optimized for us. We just need to specify PageTabViewStyle, or just ....

June 29, 2021 路 3 min 路 Khoa Pham

How to use SwiftFormat

Issue #811 Don鈥檛 add SwiftFormat as SPM package, instead use command line directly if which swiftformat >/dev/null; then swiftformat . else echo "warning: SwiftFormaat not installed, download from https://github.com/nicklockwood/SwiftFormat" fi Then install SwiftFormat locally brew install swiftformat Then make some rules in any file name with extension .swiftformat While formatting, SwiftFormat will automatically check inside each subdirectory for the presence of a .swiftformat file and will apply any options that it finds there to the files in that directory....

June 27, 2021 路 1 min 路 Khoa Pham

How to show context menu with custom preview in SwiftUI

Issue #809 Add a hidden overlay UIContextMenuInteraction. Provide preview in previewProvider and actions in actionProvider. Use @ViewBuilder to make declaring preview easy. extension View { func contextMenuWithPreview<Content: View>( actions: [UIAction], @ViewBuilder preview: @escaping () -> Content ) -> some View { self.overlay( InteractionView( preview: preview, menu: UIMenu(title: "", children: actions), didTapPreview: {} ) ) } } private struct InteractionView<Content: View>: UIViewRepresentable { @ViewBuilder let preview: () -> Content let menu: UIMenu let didTapPreview: () -> Void func makeUIView(context: Context) -> UIView { let view = UIView() view....

June 26, 2021 路 3 min 路 Khoa Pham

How to use SwiftLint in SPM project

Issue #807 Don鈥檛 add SwiftLint via SPM, but just add a Run Script Build phrase if which swiftlint >/dev/null; then swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fi Install swiftlint locally brew install swiftlint Add .swiftlint.yml with some initial disabled rules. Gradually opt-in rules to have more checks included: - MyApp - MyAppTests excluded: - Tests/SwiftLintFrameworkTests/Resources analyzer_rules: - unused_declaration - unused_import disabled_rules: - line_length - trailing_whitespace - vertical_whitespace_opening_braces - vertical_whitespace_closing_braces - void_return - identifier_name - file_name - number_separator - sorted_imports - unneeded_parentheses_in_closure_argument - redundant_optional_initialization - todo - first_where - closure_spacing - vertical_parameter_alignment_on_call - unused_optional_binding - operator_usage_whitespace - large_tuple - vertical_whitespace - multiple_closures_with_trailing_closure - control_statement - statement_position - closure_end_indentation - opening_brace - empty_count - type_body_length - force_try - force_cast opt_in_rules: - anyobject_protocol - array_init - attributes - collection_alignment - contains_over_filter_count - contains_over_filter_is_empty - contains_over_first_not_nil - contains_over_range_nil_comparison - discouraged_none_name - discouraged_object_literal - empty_collection_literal - empty_string - empty_xctest_method - enum_case_associated_values_count - explicit_init - extension_access_modifier - fallthrough - fatal_error_message - file_header - flatmap_over_map_reduce - identical_operands - joined_default_parameter - last_where - legacy_multiple - legacy_random - literal_expression_end_indentation - lower_acl_than_parent - modifier_order - nimble_operator - nslocalizedstring_key - number_separator - object_literal - overridden_super_call - override_in_extension - pattern_matching_keywords - prefer_self_type_over_type_of_self - private_action - private_outlet - prohibited_interface_builder - prohibited_super_call - quick_discouraged_call - quick_discouraged_focused_test - quick_discouraged_pending_test - reduce_into - redundant_nil_coalescing - redundant_type_annotation - single_test_class - sorted_first_last - static_operator - strong_iboutlet - test_case_accessibility - toggle_bool - unavailable_function - unowned_variable_capture - untyped_error_in_catch - xct_specific_matcher - yoda_condition file_name: excluded: - Constants....

June 24, 2021 路 2 min 路 Khoa Pham

Recommended resource for learning SwiftUI

Issue #800 I鈥檝e done some #SwiftUI lately for Push Hero and PastePal, and amazed by how easy and fun it is to make apps. If you want to get into SwiftUI but don鈥檛 know where to start, here are my recommended resources Twitter https://twitter.com/onmyway133/status/1359608162899415040 Thread reader https://threadreaderapp.com/thread/1359608162899415040.html

April 30, 2021 路 1 min 路 Khoa Pham

How to make your apps stand out on the AppStore

Issue #799 Besides coming up with a good idea and building the app, there are many other things you can do to boost your apps' visibility Twitter thread https://twitter.com/onmyway133/status/1387851727714635776 Thread reader https://threadreaderapp.com/thread/1387851727714635776.html

April 30, 2021 路 1 min 路 Khoa Pham

How to convert from paid to freemium in SwiftUI with RevenueCat

Issue #794 I鈥檝e been distributing my apps PastePal and Push Hero as a paid upfront apps on Appstore. I鈥檝e quickly come to realize the importance of trials since many have requested a tryout before purchasing. Manual sending out trials comes with its own problem. I need to notarize and package the app as a dmg file, and not to even mention implement my own trial mechanism to check and notify of expired builds....

April 11, 2021 路 5 min 路 Khoa Pham

How to use dynamic color in iOS

Issue #792 iOS 13 introduced Dark Mode with User Interface Style that makes it easy to support dark and light theme in our apps. Before we dive in, here are some official resources WWDC 2019 Implementing Dark Mode on iOS Supporting Dark Mode in Your Interface Choosing a Specific Interface Style for Your iOS App Adaptive color Like adaptive layout that adapts to different screen sizes, adaptive colors adapt to different user interface styles, for now they are light and dark mode....

March 13, 2021 路 7 min 路 Khoa Pham

How too save image to Photo library in iOS

Issue #790 Use UIImageWriteToSavedPhotosAlbum Adds the specified image to the user鈥檚 Camera Roll album. Let鈥檚 make an NSObject delegate class so we can perform target action to notify about completion import UIKit struct ImageService { final class Delegate: NSObject { let completion: (Error?) -> Void init(completion: @escaping (Error?) -> Void) { self.completion = completion } @objc func savedImage( _ im: UIImage, error: Error?, context: UnsafeMutableRawPointer? ) { DispatchQueue.main.async { self....

March 10, 2021 路 1 min 路 Khoa Pham

How to use Core Data

Issue #785 Core Data Responding to changes in a managed object context Calling mergeChanges on a managed object context will automatically refresh any managed objects that have changed. This ensures that your context always contains all the latest information. Note that you don鈥檛 have to call mergeChanges on a viewContext when you set its automaticallyMergesChangesFromParent property to true. In that case, Core Data will handle the merge on your behalf....

February 26, 2021 路 5 min 路 Khoa Pham

How to make simple search bar in SwiftUI

Issue #776 We need to use a custom Binding to trigger onChange as onEditingChanged is only called when the user selects the textField, and onCommit is only called when return or done button on keyboard is tapped. import UIKit import SwiftUI import EasySwiftUI struct SearchBar: View { @Binding var searchText: String let onChange: () -> Void @State private var showsCancelButton: Bool = false var body: some View { return HStack { textField cancelButton } } private var searchTextBinding: Binding<String> { Binding<String>(get: { searchText }, set: { newValue in DispatchQueue....

February 17, 2021 路 1 min 路 Khoa Pham

How to fix share and action extension not showing up in iOS 14

Issue #775 My share sheet and action extension not showing up in iOS 14, built-in Xcode 12.3. The solution is to restart test device, and it shows up again. Also make sure your extension targets have the same version and build number, and same deployment target <dict> <key>NSExtensionAttributes</key> <dict> <key>NSExtensionActivationRule</key> <dict> <key>NSExtensionActivationDictionaryVersion</key> <integer>2</integer> <key>NSExtensionActivationSupportsText</key> <true/> <key>NSExtensionActivationSupportsWebURLWithMaxCount</key> <integer>1</integer> <key>NSExtensionActivationSupportsWebPageWithMaxCount</key> <integer>1</integer> </dict> <key>NSExtensionJavaScriptPreprocessingFile</key> <string>MyPreprocessor</string> <key>NSExtensionActivationUsesStrictMatching</key> <integer>2</integer> </dict>

February 17, 2021 路 1 min 路 Khoa Pham

How to add alternative app icons for iOS

Issue #749 Some apps want to support alternative app icons in Settings where user can choose to update app icon. Here鈥檚 some must do to make it work, as of Xcode 12.2 In Info.plist, must declare CFBundleIcons with both CFBundlePrimaryIcon and CFBundleAlternateIcons Icons must be in project folder, not Asset Catalog Here鈥檚 how it is done on my app PastePal Add app icons to project folder Prepare icons, like Icon1, Icon2 with 2 variants for 2x and 3x....

January 14, 2021 路 2 min 路 Khoa Pham

How to use UITextView in SwiftUI

Issue #747 Need to use Coordinator conforming to UITextViewDelegate to apply changes back to Binding import SwiftUI import UIKit struct MyTextView: UIViewRepresentable { @Binding var text: String final class Coordinator: NSObject, UITextViewDelegate { let parent: MyTextView init(parent: MyTextView) { self.parent = parent } func textViewDidChange(_ textView: UITextView) { if textView.text != parent.text { parent.text = textView.text } } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } func makeUIView(context: Context) -> UITextView { let view = UITextView() view....

January 8, 2021 路 1 min 路 Khoa Pham