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

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 路 Khoa Pham

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 路 Khoa Pham

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 路 Khoa Pham

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 路 Khoa Pham

How to test push notifications in simulator and production iOS apps

Issue #682 After has recently reminded about his updating APNs provider API which makes me realised a lot has changed about push notifications, both in terms of client and provider approach. The HTTP/2-based Apple Push Notification service (APNs) provider API lets you take advantage of great features, such as authentication with a JSON Web Token, improved error messaging, and per-notification feedback. If you send push notifications with the legacy binary protocol, we strongly recommend upgrading to the APNs provider API....

October 11, 2020 路 12 min 路 Khoa Pham

How to draw arc corner using Bezier Path

Issue #673 To draw rounded 2 corners at top left and top right, let鈥檚 start from bottom left let path = UIBezierPath() // bottom left path.move(to: CGPoint(x: 0, y: bounds.height)) // top left corner path.addArc(withCenter: CGPoint(x: radius, y: radius), radius: radius, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3 / 2, clockwise: true) // top right corner path.addArc(withCenter: CGPoint(x: bounds.width - radius, y: radius), radius: radius, startAngle: CGFloat.pi * 3 / 2, endAngle: 0, clockwise: true) // bottom right path....

September 15, 2020 路 1 min 路 Khoa Pham

How to make dynamic font size for UIButton

Issue #671 Use adjustsFontForContentSizeCategory A Boolean that indicates whether the object automatically updates its font when the device鈥檚 content size category changes. If you set this property to YES, the element adjusts for a new content size category on a UIContentSizeCategoryDidChangeNotification. button.titleLabel?.adjustsFontForContentSizeCategory = true button.backgroundColor = UIColor.green button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .title1) label.adjustsFontForContentSizeCategory = true label.backgroundColor = UIColor.yellow label.font = UIFont.preferredFont(forTextStyle: .title1) However it seems view (UIButton or UILabel) size is the same, just the inner text increases in size....

August 14, 2020 路 1 min 路 Khoa Pham

How to test for view disappear in navigation controller

Issue #670 To test for viewWillDisappear during UINavigationController popViewController in unit test, we need to simulate UIWindow so view appearance works. final class PopTests: XCTestCase { func testPop() { let window = UIWindow(frame: UIScreen.main.bounds) let navigationController = UINavigationController() window.rootViewController = navigationController let viewController = DetailViewController() navigationController.viewControllers = [ UIViewController(), viewController ] window.makeKeyAndVisible() let expectation = XCTestExpectation() navigationController.popViewController(animated: false) DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { XCTAssertTrue(viewController.wasDismissed) expectation.fulfill() } wait(for: [expectation], timeout: 1) } } class DetailViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super....

August 6, 2020 路 1 min 路 Khoa Pham

How to set text color for UIDatePicker

Issue #660 Apply tintColor does not seem to have effect. datePicker.setValue(UIColor.label, forKeyPath: "textColor") datePicker.setValue(false, forKey: "highlightsToday") ...

June 10, 2020 路 1 min 路 Khoa Pham

How to use background in iOS

Issue #631 beginBackgroundTask https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/extending_your_app_s_background_execution_time When your app moves to the background, the system calls your app delegate鈥檚 applicationDidEnterBackground(_:) method. That method has five seconds to perform any tasks and return. Shortly after that method returns, the system puts your app into the suspended state. For most apps, five seconds is enough to perform any crucial tasks, but if you need more time, you can ask UIKit to extend your app鈥檚 runtime....

March 25, 2020 路 2 min 路 Khoa Pham

How to mask with UILabel

Issue #603 Need to set correct frame for mask layer or UILabel, as it is relative to the coordinate of the view to be masked let aView = UIView(frame: .init(x: 100, y: 110, width: 200, height: 100)) let textLayer = CATextLayer() textLayer.foregroundColor = UIColor.white.cgColor textLayer.string = "Hello world" textLayer.font = UIFont.preferredFont(forTextStyle: .largeTitle) textLayer.frame = aView.bounds aView.layer.mask = textLayer Use sizeToFit to ensure frame for UILabel let label = UILabel() label.frame.origin = CGPoint(x: 80, y: 80) label....

February 13, 2020 路 1 min 路 Khoa Pham

How to sync multiple CAAnimation

Issue #600 Use same CACurrentMediaTime final class AnimationSyncer { static let now = CACurrentMediaTime() func makeAnimation() -> CABasicAnimation { let animation = CABasicAnimation(keyPath: "opacity") animation.fillMode = .forwards animation.fromValue = 0 animation.toValue = 1 animation.repeatCount = .infinity animation.duration = 2 animation.beginTime = Self.now animation.autoreverses = true animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) return animation } } ...

February 11, 2020 路 1 min 路 Khoa Pham