How to test Date with timezone aware in Swift

Issue #402 I want to test if a date has passed another date let base = Date(timeIntervalSince1970: 1567756697) XCTAssertEqual(validator.hasPassed(event: event, date: base), true) My hasPassed is using Calendar.current func minuteSinceMidnight(date: Date) -> MinuteSinceMidnight { let calendar = Calendar.current let start = calendar.startOfDay(for: date) return Int(date.timeIntervalSince(start)) / 60 } But the minute is always having timezone applied. Even if I try with DateComponents func minuteSinceMidnight(date: Date) -> MinuteSinceMidnight { let components = calendar....

September 6, 2019 · 2 min · 230 words · Khoa

How to do simple analytics in iOS

Issue #395 Prefer static enum to avoid repetition and error. The Log should have methods with all required fields so the call site is as simple as possible. How to format and assign parameters is encapsulated in this Analytics. import Foundation import Firebase import FirebaseAnalytics struct Analytics { enum Parameter: String { case studentId = "student_id" case classId = "class_id" case url = "url" } enum Property: String { case grantLocation = "grant_location" } enum Name: String { case login case logOut = "log_out" case enroll } struct Log { private func log(_ name: Name, parameters: [Parameter: String] = [:]) { let mapped: [String: String] = Dictionary(uniqueKeysWithValues: parameters....

September 4, 2019 · 1 min · 198 words · Khoa

How to manage OneSignal push notification in iOS

Issue #377 OneSignal is an alternative for Parse for push notifications but the sdk has many extra stuff and assumptions and lots of swizzling. We can just use Rest to make API calls. From https://github.com/onmyway133/Dust Every official push notification SDK can do many things Register device token. This is crucial for the notification to get from your backend -> APNS -> device Manage player id, user id, arn, …This is used to associate with device token Manager tag, topic, subscription, segments, …This is used to group a set of device tokens Do swizzling, update your application badge number, change your user notification settings, … without your knowing about that Some other fancy stuffs Dust does only one thing, which is push notification handling....

August 27, 2019 · 4 min · 784 words · Khoa

How to do throttle and debounce using DispatchWorkItem in Swift

Issue #376 https://github.com/onmyway133/Omnia/blob/master/Sources/Shared/Debouncer.swift import Foundation public class Debouncer { private let delay: TimeInterval private var workItem: DispatchWorkItem? public init(delay: TimeInterval) { self.delay = delay } /// Trigger the action after some delay public func run(action: @escaping () -> Void) { workItem?.cancel() workItem = DispatchWorkItem(block: action) DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem!) } } import XCTest class DebouncerTests: XCTestCase { func testDebounce() { let expectation = self.expectation(description: #function) let debouncer = Debouncer(delay: 0....

August 27, 2019 · 1 min · 117 words · Khoa

How to simplify UIApplication life cycle observation in iOS

Issue #375 final class LifecyclerHandler { private var observer: AnyObject! var action: (() -> Void)? private let debouncer = Debouncer(delay: 1.0) func setup() { observer = NotificationCenter.default.addObserver( forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main, using: { [weak self] _ in self?.debouncer.run { self?.action?() } }) } } private let lifecycleHandler = LifecyclerHandler() override func viewDidLoad() { super.viewDidLoad() lifecycleHandler.action = { Deps.userHandler.refreshToken() } lifecycleHandler.setup() }

August 27, 2019 · 1 min · 63 words · Khoa

How to do UITests with Google Maps on iOS

Issue #374 Interact with GMSMapView Add accessibilityIdentifier to the parent view of GMSMapView. Setting directly onto GMSMapView has no effect accessibilityIdentifier = "MapView" let map = app.otherElements.matching(identifier: "MapView").element(boundBy: 0) map.pinch(withScale: 2, velocity: 1) map.rotate(CGFloat.pi/3, withVelocity: 1.0) map.swipeLeft() map.swipeRight() map.swipeDown() map.swipeDown() Interact with GMSMarker (1st try) Need to enable accessibility mapView.accessibilityElementsHidden = false Can’t use pinch to zoom out with UITests, so need to mock location !!! map().pinch(withScale: 0.05, velocity: -1) Need to use gpx to mock to preferred location...

August 27, 2019 · 2 min · 242 words · Khoa

Make to make rounded background UIButton in iOS

Issue #373 UIButton.contentEdgeInsets does not play well with Auto Layout, we need to use intrinsicContentSize final class InsetButton: UIButton { required init(text: String) { super.init(frame: .zero) titleLabel?.textColor = .white setTitle(text, for: .normal) layer.cornerRadius = 15 layer.masksToBounds = true backgroundColor = .black isUserInteractionEnabled = false } required init?(coder aDecoder: NSCoder) { fatalError() } override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize(width: size.width + 24, height: size.height) } }

August 27, 2019 · 1 min · 70 words · Khoa

How to make scrolling UIScrollView with Auto Layout in iOS

Issue #371 Scrolling UIScrollView is used in common scenarios like steps, onboarding. From iOS 11, UIScrollView has contentLayoutGuide and frameLayoutGuide Docs https://developer.apple.com/documentation/uikit/uiscrollview/2865870-contentlayoutguide Use this layout guide when you want to create Auto Layout constraints related to the content area of a scroll view. https://developer.apple.com/documentation/uikit/uiscrollview/2865772-framelayoutguide Use this layout guide when you want to create Auto Layout constraints that explicitly involve the frame rectangle of the scroll view itself, as opposed to its content rectangle....

August 26, 2019 · 2 min · 316 words · Khoa

How to simplify anchor with NSLayoutConstraint in iOS

Issue #368 See https://github.com/onmyway133/Omnia/blob/master/Sources/iOS/NSLayoutConstraint.swift extension NSLayoutConstraint { /// Disable auto resizing mask and activate constraints /// /// - Parameter constraints: constraints to activate static func on(_ constraints: [NSLayoutConstraint]) { constraints.forEach { ($0.firstItem as? UIView)?.translatesAutoresizingMaskIntoConstraints = false $0.isActive = true } } static func on(_ constraintsArray: [[NSLayoutConstraint]]) { let constraints = constraintsArray.flatMap({ $0 }) NSLayoutConstraint.on(constraints) } func priority(_ value: Float) -> NSLayoutConstraint { priority = UILayoutPriority(value) return self } } extension Array where Element == NSLayoutConstraint { func priority(_ value: Float) -> [NSLayoutConstraint] { forEach { $0....

August 23, 2019 · 2 min · 249 words · Khoa

How to handle link clicked in WKWebView in iOS

Issue #365 import WebKit import SafariServices final class WebViewHandler: NSObject, WKNavigationDelegate { var show: ((UIViewController) -> Void)? let supportedSchemes = ["http", "https"] func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { defer { decisionHandler(.allow) } guard navigationAction.navigationType == .linkActivated, let url = navigationAction.request.url, let scheme = url.scheme, supportedSchemes.contains(scheme) else { return } let controller = SFSafariViewController(url: url) show?(controller) } }

August 21, 2019 · 1 min · 64 words · Khoa

How to use AppFlowController in iOS

Issue #364 AppFlowController.swift import UIKit import GoogleMaps import Stripe final class AppFlowController: UIViewController { private lazy var window = UIWindow(frame: UIScreen.main.bounds) func configure() { GMSServices.provideAPIKey(Constant.googleMapsApiKey) STPPaymentConfiguration.shared().publishableKey = Constant.stripeKey } func start() { if Deps.onboardingHandler.hasOnboarded { startMain() } else { startOnboarding() } window.makeKeyAndVisible() } func startOnboarding() { let controller = OnboardingController() controller.delegate = self window.rootViewController = controller } func startMain() { let controller = MainFlowController() window.rootViewController = controller controller.start() } } extension AppFlowController: OnboardingControllerDelegate { func onboardingControllerDidFinish(_ controller: OnboardingController) { Deps....

August 20, 2019 · 1 min · 116 words · Khoa

How to declare UIGestureRecognizer in iOS

Issue #362 let tapGR = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) @objc private func handleTap(_ gr: UITapGestureRecognizer) { didTouch?() } We need to use lazy instead of let for gesture to work lazy var tapGR = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))

August 19, 2019 · 1 min · 38 words · Khoa

How to use function builder in Swift 5.1

Issue #361 protocol Task {} struct Build: Task {} struct Test: Task {} @_functionBuilder public struct TaskBuilder { public static func buildBlock(_ tasks: Task...) -> [Task] { tasks } } public func run(@TaskBuilder builder: () -> [Task]) { MyManager.run(tasks: builder()) } public func run(@TaskBuilder builder: () -> Task) { MyManager.run(tasks: [builder()]) } run { Build() Test() } Read more The Swift 5.1 features that power SwiftUI’s API

August 18, 2019 · 1 min · 67 words · Khoa

How to simplify get GRPC streaming in Swift

Issue #360 Given a streaming service service Server { rpc GetUsers(GetUsersRequest) returns (stream GetUsersResponse); } To get a response list in Swift, we need to do observe stream, which is a subclass of ClientCallServerStreaming func getUsers(roomId: String, completion: @escaping (Result<[User], Error>) -> Void) { let request = withValue(Server_GetUsersRequest()) { $0.roomId = roomId } DispatchQueue.global().async { var users = [User]() do { var streaming = true let stream = try self.client.getUsers(request, completion: { _ in streaming = false }) while streaming { if let response = try stream....

August 16, 2019 · 2 min · 300 words · Khoa

How to use Payment Intent and Setup Intents with Stripe in iOS

Issue #356 StripeHandler.swift From Stripe 16.0.0 https://github.com/stripe/stripe-ios/blob/master/CHANGELOG.md#1600-2019-07-18 Migrates STPPaymentCardTextField.cardParams property type from STPCardParams to STPPaymentMethodCardParams final class StripeHandler { func createPaymentMethod( textField: STPPaymentCardTextField, completion: @escaping (Result<STPPaymentMethod, Error>) -> Void) { let paymentMethodParams = STPPaymentMethodParams( card: textField.cardParams, billingDetails: nil, metadata: nil ) STPAPIClient.shared().createPaymentMethod( with: paymentMethodParams, completion: { paymentMethod, error in DispatchQueue.main.async { if let paymentMethod = paymentMethod { completion(.success(paymentMethod)) } else { completion(.failure(error ?? AppError.request)) } } }) STPAPIClient.shared().createPaymentMethod( with: paymentMethodParams, completion: { paymentMethod, error in DispatchQueue....

August 14, 2019 · 4 min · 757 words · Khoa

How to format currency in Swift

Issue #355 final class CurrencyFormatter { func format(amount: UInt64, decimalCount: Int) -> String { let formatter = NumberFormatter() formatter.minimumFractionDigits = 0 formatter.maximumFractionDigits = decimalCount formatter.numberStyle = .decimal let value = Double(amount) / pow(Double(10), Double(decimalCount)) let fallback = String(format: "%.0f", value) return formatter.string(from: NSNumber(value: value)) ?? fallback } } class CurrencyFormatterTests: XCTestCase { func testFormat() { let formatter = CurrencyFormatter() // 120 USD XCTAssertEqual(formatter.format(amount: 120, decimalCount: 0), "120") // 12000 cents XCTAssertEqual(formatter....

August 13, 2019 · 1 min · 85 words · Khoa

How to simplify struct mutating in Swift

Issue #354 In Construction, we have a build method to apply closure to inout struct. We can explicitly define that with withValue func withValue<T>(_ value: T, closure: (inout T) -> Void) -> T { var mutableValue = value closure(&mutableValue) return mutableValue } So we can modify Protobuf structs easily user.book = withValue(Book()) { $0.price = 300 $0.author = withValue(Author()) { $0.name = "Thor" } }

August 13, 2019 · 1 min · 65 words · Khoa

How to use Firebase PhoneAuth in iOS

Issue #350 Read Authenticate with Firebase on iOS using a Phone Number Disable swizzling Info.plist <key>FirebaseAppDelegateProxyEnabled</key> <string>NO</string> Enable remote notification Enable Capability -> Background mode -> Remote notification AppDelegate.swift import Firebase import UIKit import FirebaseAuth @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { private let appFlowController = AppFlowController() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UIApplication.shared.registerForRemoteNotifications() FirebaseApp.configure() return true } // MARK: - Remote Notification func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { Auth....

August 7, 2019 · 1 min · 191 words · Khoa

How to make digit passcode input in Swift

Issue #347 Add a hidden UITextField to view hierarchy, and add UITapGestureRecognizer to activate that textField. Use padding string with limit to the number of labels, and prefix to get exactly n characters. DigitView.swift import UIKit final class DigitView: UIView { lazy var stackView: UIStackView = { let view = UIStackView() view.axis = .horizontal view.distribution = .equalSpacing return view }() private(set) var boxes: [UIView] = [] private(set) var labels: [UILabel] = [] lazy var hiddenTextField: UITextField = { let textField = UITextField() textField....

August 6, 2019 · 2 min · 323 words · Khoa

How to make credit card input UI in Swift

Issue #346 We have FrontCard that contains number and expiration date, BackCard that contains CVC. CardView is used to contain front and back sides for flipping transition. We leverage STPPaymentCardTextField from Stripe for working input fields, then CardHandler is used to parse STPPaymentCardTextField content and update our UI. For masked credit card numbers, we pad string to fit 16 characters with ● symbol, then chunk into 4 parts and zip with labels to update....

August 5, 2019 · 3 min · 632 words · Khoa