onmyway133

Hi there, I’m Khoa aka onmyway133 👋

  • 🧑‍💻 I love crafting high quality and useful apps

  • 🔥 I love open source. My GitHub open source has 1.6k followers with packages that are integrated by 45k+ apps and over 3.4m+ downloads on CocoaPods.

  • ✍️ I write here on my blog and on Medium, which has over 2.4k+ followers with tons of articles and 90k+ monthly views.

  • 🖥 Follow me for sharings about Swift, SwiftUI, iOS and macOS development

How to use debounce in Combine in Swift

Issue #827 I’m trying a simple Future and sink. As long as I have debounce, only receiveCompletion gets called, but not receiveValue private var bag = Set<AnyCancellable>() let future = Future<Int, Never> { promise in promise(.success(1)) } future .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) .sink( receiveCompletion: { print($0) // Called }, receiveValue: { print($0) // Not called } ) .store(in: &bag) Reading debounce Publishes elements only after a specified time interval elapses between events....

July 21, 2021 · 1 min · Khoa Pham

How to use Button inside NavigationLink in SwiftUI

Issue #826 Use isActive binding @State private var goesToDetail: Bool = false NavigationLink( destination: DetailView(viewModel: viewModel), isActive: $goesToDetail) { Button(action: { goesToDetail = true }) { Text("Next") } .buttonStyle(MyButtonStyle)) }

July 21, 2021 · 1 min · Khoa Pham

How to structure user state for App in SwiftUI

Issue #825 For many apps that require user authentication, a common practice is to define a shared UserManager with an optional User. This is convenient but it requires us to constantly unwrap and check that user class UserManager { static let shared = UserManager() private(set) var user: User? } A more safer approach is to leverage Swift type system and separate the need based on authenticated and unauthenticated usage. For example, for an unauthorized users, we show the login screen....

July 21, 2021 · 2 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 convert NSEvent locationInWindow to window coordinate

Issue #822 Get active screen with mouse func activeScreen() -> NSScreen? { let mouseLocation = NSEvent.mouseLocation let screens = NSScreen.screens let screenWithMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) }) return screenWithMouse } Reposition our NSWIndow if window.frame != screen.frame { window.setFrameOrigin(screen.frame.origin) window.setContentSize(screen.frame.size) } Flip y as AppKit has coordinate origin starting from bottom left of the screen, while our SwiftUI starts from top left var point = window.convertPoint(fromScreen: event.locationInWindow) point.y = window....

July 13, 2021 · 1 min · Khoa Pham

How to sync width for child views in SwiftUI

Issue #821 Suppose we have a 16:9 preview Image and we want the title Text to be the same width First we need PreferenceKey to store and sync size struct SizePreferenceKey: PreferenceKey { typealias Value = CGSize static var defaultValue: Value = .zero static func reduce(value: inout Value, nextValue: () -> Value) { value = nextValue() } } For our Image, we use a GeometryReader in the background with invisible View Color....

July 6, 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 conform UIImage to Codable

Issue #819 Either use EasyStash or make a simple CodableImage struct CodableImage: Codable { let image: UIImage? init(image: UIImage) { self.image = image } enum CodingKeys: CodingKey { case data case scale } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let scale = try container.decode(CGFloat.self, forKey: .scale) let data = try container.decode(Data.self, forKey: .data) self.image = UIImage(data: data, scale: scale) } public func encode(to encoder: Encoder) throws { var container = encoder....

July 4, 2021 · 1 min · Khoa Pham

How to show custom context menu in SwiftUI

Issue #818 There’s a lot to do to imitate iOS ContextMenu look and feel vibrancy blur background haptics feedback targeted view position interaction dismiss gesture For now here’s a rough implementation of a custom context menu where we can show using .overlay import SwiftUI struct CustomContextMenu<Content: View>: View { @Binding var shows: Bool @ViewBuilder let content: Content var body: some View { ZStack { Color.black.opacity(0.1) .onTapGesture { shows = false } VStack(alignment: ....

July 2, 2021 · 1 min · Khoa Pham

How to declare View with ViewBuilder in SwiftUI

Issue #817 Thanks to resultBuilder and container type in Swift, the following are possible in SwiftUI Local variable struct ChartView { var body: some View { computation } @ViewBuilder var computation: some View { let left = 10 let right = 20 if left + right == 30 { Text("Correct") } } } if and switch statement struct ChartView: View { @State private var showsOverlay: Bool = false @State private var overlayKind: OverlayKind?...

July 2, 2021 · 2 min · Khoa Pham

How to make custom Menu in SwiftUI

Issue #816 SwiftUI supports Menu but the menu items only appear after the menu button is touched. We can initiate the look and feel of Menu with a custom implementation import SwiftUI struct CustomMenu<Content: View>: View { @ViewBuilder let content: Content var body: some View { VStack(spacing: 0) { content } .frame(width: 234) .background( Color(UIColor.systemBackground) .opacity(0.8) .blur(radius: 50) ) .cornerRadius(14) } } struct CustomMenuButtonStyle: ButtonStyle { let symbol: String let color: Color func makeBody(configuration: Configuration) -> some View { HStack { configuration....

July 1, 2021 · 1 min · Khoa Pham

How ObservableObject work in SwiftUI

Issue #815 When ever a property marked with @Published change, ObservableObject will emit objectWillChange.send hence telling the View that observes it to reinvalidate. In WWDC 2021 session Discover concurrency in SwiftUI they mention how objectWillChange is used to grab before-change data to diff the changes. In the below example, we have 2 ObservableObject Store with just count value being used in the View, while flag is not used Store2 with count not being used But since the View observes these 2 objects, whenever any @Publish property changes, SwiftUI thinks that the data changes, then invalidates the body again to see if it needs to redraw....

June 30, 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 make simple swipe vertically to dismiss in SwiftUI

Issue #813 Use simultaneousGesture to not collide with potential horizontal scrolling in carousel view, and check that we' more accidentally swipe horizontally. import SwiftUI struct SwipeToDismissModifier: ViewModifier { var onDismiss: () -> Void @State private var offset: CGSize = .zero func body(content: Content) -> some View { content .offset(y: offset.height) .animation(.interactiveSpring(), value: offset) .simultaneousGesture( DragGesture() .onChanged { gesture in if gesture.translation.width < 50 { offset = gesture.translation } } .onEnded { _ in if abs(offset....

June 29, 2021 · 1 min · Khoa Pham

How to make carousel pager view in SwiftUI

Issue #812 Use GeometryReader to set width and a DragGesture on LazyVStack CarouselView( pageCount: images.count, currentIndex: $selectedImageIndex ) { ForEach(0 ..< images) { image in // AsyncImage } } struct CarouselView<Content: View>: View { let pageCount: Int @Binding var currentIndex: Int @ViewBuilder let content: Content @GestureState private var translation: CGFloat = 0 var body: some View { GeometryReader { geometry in LazyHStack(spacing: 0) { self.content.frame(width: geometry.size.width) } .frame(width: geometry.size.width, alignment: ....

June 29, 2021 · 1 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 update Firestore value with KeyPath in Swift

Issue #810 struct User { var createdAt: Date var name: Sttring var locked: Bool } extension KeyPath where Root == User { var keyPathString: String { switch self { case \User.createdAt: return "createdAt" case \User.name: return "name" case \User.locked: return "locked" default: return "" } } } Then we can call reference .collection("users") .document(user.id) .updateData([ (\User.locked).keyPathString:. true ])

June 26, 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 login with Apple in SwiftUI

Issue #808 Make SignInWithAppleButton Wrap ASAuthorizationAppleIDButton inside UIViewRepresentable import SwiftUI import UIKit import AuthenticationServices struct SignInWithAppleButton: View { @Environment(\.colorScheme) private var colorScheme: ColorScheme var body: some View { ButtonInside(colorScheme: colorScheme) .frame(width: 280, height: 45) } } private struct ButtonInside: UIViewRepresentable { let colorScheme: ColorScheme func makeUIView(context: Context) -> ASAuthorizationAppleIDButton { switch colorScheme { case .dark: return ASAuthorizationAppleIDButton(type: .signIn, style: .white) default: return ASAuthorizationAppleIDButton(type: .signIn, style: .black) } } func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) { // No op } } struct SignInWithAppleButton_Previews: PreviewProvider { static var previews: some View { SignInWithAppleButton() } } Handle logic in ViewModel import SwiftUI import AuthenticationServices import Resolver import Firebase import FirebaseAuth final class AuthViewModel: NSObject, ObservableObject { enum RequestState: String { case signIn case link case reauth } let firebaseService: FirebaseService var window: UIWindow?...

June 25, 2021 · 3 min · Khoa Pham