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 · 212 words · Khoa

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 · 209 words · Khoa

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 · 120 words · Khoa

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’s 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 · 610 words · Khoa

How to use SwiftFormat

Issue #811 Don’t 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 · 95 words · Khoa

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 · 481 words · Khoa

How to use SwiftLint in SPM project

Issue #807 Don’t 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 · 240 words · Khoa

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 · 32 words · Khoa

How to convert from paid to freemium in SwiftUI with RevenueCat

Issue #794 I’ve been distributing my apps PastePal and Push Hero as a paid upfront apps on Appstore. I’ve 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 · 1023 words · Khoa

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 · 1454 words · Khoa

How too save image to Photo library in iOS

Issue #790 Use UIImageWriteToSavedPhotosAlbum Adds the specified image to the user’s Camera Roll album. Let’s 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.completion(error) } } } func addToPhotos(image: UIImage, completion: @escaping (Error?...

March 10, 2021 · 1 min · 95 words · Khoa

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’t 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 · 6 min · 1109 words · Khoa

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 · 201 words · Khoa

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 · 65 words · Khoa

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’s 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’s 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 · 388 words · Khoa

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 · 108 words · Khoa

How to check app going to background in SwiftUI

Issue #746 From iOS 13, the default is to support multiple scene, so the the old UIApplicationDelegate lifecycle does not work. Double check your Info.plist for UIApplicationSceneManifest key <key>UIApplicationSceneManifest</key> <dict> <key>UIApplicationSupportsMultipleScenes</key> <true/> </dict> One way to be notified about application life cycle is to use UIApplicationDelegateAdaptor and via NotificationCenter import SwiftUI import UIKit import FontAwesomeSwiftUI final class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { FontAwesome....

January 7, 2021 · 1 min · 110 words · Khoa

How to make Auto Layout more convenient in iOS

Issue #742 Auto Layout has been around since macOS 10.7 and iOS 6.0 as a nicer way to do layouts over the old resizing masks. Besides some rare cases when we need to manually specify origins and sizes, Auto Layout is the preferred way to do declarative layouts whether we choose to do UI in code or Storyboard. The Auto Layout APIs have some improvements over the years, there are also some sugar libraries that add easy syntaxes, so more choices for developers....

January 5, 2021 · 18 min · 3711 words · Khoa

How to convert from paid to free with IAP

Issue #703 What is receipt Read When to refresh a receipt vs restore purchases in iOS? From iOS 7, every app downloaded from the store has a receipt (for downloading/buying the app) at appStoreReceiptURL. When users purchases something via In App Purchase, the content at appStoreReceiptURL is updated with purchases information. Most of the cases, you just need to refresh the receipt (at appStoreReceiptURL) so that you know which transactions users have made....

November 30, 2020 · 9 min · 1834 words · Khoa

How to avoid multiple match elements in UITests from iOS 13

Issue #691 Supposed we want to present a ViewController, and there exist both UIToolbar in both the presenting and presented view controllers. From iOS 13, the model style is not full screen and interactive. From UITests perspective there are 2 UIToolbar, we need to specify the correct one to avoid multiple match errors let editButton = app.toolbars["EditArticle.Toolbar"].buttons["Edit"] Updated at 2020-11-04 10:02:29

November 4, 2020 · 1 min · 61 words · Khoa