How to get notification userInfo at launch

Issue #884 When user taps on push notification, depending on app state SceneDelegate Checking UIScene.ConnectionOptions.notificationResponse?.notification.request.content.userInfo in scene(_:willConnectTo:options:) app terminated: sometimes nil app in background: notification info UNUserNotificationCenter Checking UNNotificationResponse.notification.request.content.userInfo in userNotificationCenter(_:didReceive:withCompletionHandler:) app terminated: notification info app in background: notification info

June 1, 2022 · 1 min · Khoa Pham

How to use NSPersistentCloudKitContainer

Issue #879 Setting Up Core Data with CloudKit Enable iCloud Enable CloudKit and Push Notifications Enable Remote Notifications in the Background Creating a Core Data Model for CloudKit Initialize Your CloudKit Schema During Development let container = NSPersistentCloudKitContainer(name: "Earthquakes") // Only initialize the schema when building the app with the // Debug build configuration. #if DEBUG do { // Use the container to initialize the development schema....

May 11, 2022 · 2 min · Khoa Pham

How to add dot indicator to tab bar item in iOS

Issue #874 From iOS 13, use UITabBarAppearance and UITabBarItemAppearance let appearance = UITabBarAppearance() let itemAppearance = UITabBarItemAppearance(style: .stacked) itemAppearance.normal.badgeBackgroundColor = .clear itemAppearance.normal.badgeTextAttributes = [.foregroundColor: UIColor.red] profileViewController.tabBarItem.badgeValue = "●" Read more https://emptytheory.com/2019/12/31/using-uitabbarappearance-for-tab-bar-changes-in-ios-13/

March 15, 2022 · 1 min · Khoa Pham

How to use Multipeer Connectivity

Issue #873 Use assistant let assistant = MCAdvertiserAssistant(serviceType: "my-service, discoveryInfo: nil, session: mcSession) assistant.start() let browser = MCBrowserViewController(serviceType: "my-service", session: mcSession) browser.delegate = self present(browser, animated: true) Manual let advertiser = MCNearbyServiceAdvertiser(peer: localPeerID, discoveryInfo: nil, serviceType: self.serviceType) advertiser.startAdvertisingPeer() let browser = MCNearbyServiceBrowser(peer: localPeerID, serviceType: self.serviceType) browser.startBrowsingForPeers() Enable in Info.plist <key>NSLocalNetworkUsageDescription</key> <string>Enable local network discovery to use Peer Share</string> <key>NSBonjourServices</key> <array> <string>_my-service._tcp</string> <string>_my-service._udp</string> </array> Read more https://www.hackingwithswift.com/example-code/networking/how-to-create-a-peer-to-peer-network-using-the-multipeer-connectivity-framework https://www.toptal.com/ios/collusion-ios-multipeerconnectivity

March 15, 2022 · 1 min · Khoa Pham

How to use Apple Pay in iOS

Issue #856 Use PKPaymentRequest and PKPaymentAuthorizationViewController @MainActor final class WalletViewModel: NSObject, ObservableObject { var canMakePayments: Bool { PKPaymentAuthorizationViewController.canMakePayments() } func showApplePay(amount: Amount, from window: UIWindow) { let request = PKPaymentRequest() request.supportedNetworks = [PKPaymentNetwork.amex, .discover, .masterCard, .visa] request.countryCode = "US" request.currencyCode = "USD" request.merchantIdentifier = "merchant.\(Bundle.main.bundleIdentifier!)" request.merchantCapabilities = .capability3DS let item = PKPaymentSummaryItem(label: "Add Cash", amount: amount.toNsDecimal) request.paymentSummaryItems = [item] guard let vc = PKPaymentAuthorizationViewController(paymentRequest: request) else { return } vc.delegate = self window....

January 17, 2022 · 1 min · Khoa Pham

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’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 · Khoa Pham

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 · 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’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 · Khoa Pham

Recommended resource for learning SwiftUI

Issue #800 I’ve 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’t 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’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 · 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’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....

March 10, 2021 · 1 min · Khoa Pham