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 not encode with Enum key in Swift

Issue #854 If you use enum case as key in Dictionary, JSONEncoder will encode it as Array. For example enum Vehicle: String, Codable { case car case truck } struct Container: Codable { var map: [Vehicle: String] } struct Container2: Codable { var map: [String: String] } let container = Container(map: [ .car: "Car 1" ]) let container2 = Container2(map: [ "car": "Car 1" ]) let data = try! JSONEncoder().encode(container) print(String(data: data, encoding: ....

January 10, 2022 · 2 min · Khoa Pham

How to disable with ButtonStyle in SwiftUI

Issue #853 With ButtonStyle, the disabled modifier does not seem to work, we need to use allowsHitTesting. import SwiftUI struct ActionButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { HStack { Text("Button") } .padding() .disabled(true) // does not work .allowsHitTesting(false) } } We need to call disabled outside, after buttonStyle. In case we have onTapGesture on the entire view, touching on that disabled button will also trigger our whole view action, which is not what we want....

December 4, 2021 · 1 min · Khoa Pham

How to query document id in array in Firestore

Issue #852 Supposed we have Book object struct Book: Identifiable, Codable, Hashable { @DocumentID var id: String? } We should use FieldPath instead of id for query let booksRef: CollectionReference = ... let ids: [String] = ... booksRef .whereField( FieldPath.documentID(), in: ids )

November 28, 2021 · 1 min · Khoa Pham

How to provide default Codable in Swift

Issue #851 Use DefaultValue to provide defaultValue in our property wrapper DefaultCodable public protocol DefaultValue { associatedtype Value: Codable static var defaultValue: Value { get } } public enum DefaultBy { public enum True: DefaultValue { public static let defaultValue = true } public enum False: DefaultValue { public static let defaultValue = false } } @propertyWrapper public struct DefaultCodable<T: DefaultValue> { public var wrappedValue: T.Value public init(wrappedValue: T.Value) { self....

October 23, 2021 · 1 min · Khoa Pham

How to use dynamic shape in SwiftUI

Issue #850 Erase with AnyShape struct AnyShape: Shape { init<S: Shape>(_ wrapped: S) { innerPath = { rect in let path = wrapped.path(in: rect) return path } } func path(in rect: CGRect) -> Path { return innerPath(rect) } private let innerPath: (CGRect) -> Path } extension Shape { func erase() -> AnyShape { AnyShape(self) } } Then we can use like private struct ContentView: View { var body: some View { ZStack { Color....

September 30, 2021 · 1 min · Khoa Pham

How to use Picker with optional selection in SwiftUI

Issue #849 We need to explicitly specify optional in tag extension AVCaptureDevice: Identifiable { public var id: String { uniqueID } } @State var device: AVCaptureDevice? Picker("Camera", selection: $device) { ForEach(manager.devices) { d in Text(d.localizedName) .tag(AVCaptureDevice?.some(d)) } }

September 30, 2021 · 1 min · Khoa Pham

How to deinit NSWindow

Issue #848 Hold a weak reference to NSWindow, and let system window server manages its lifetime weak var window = NSWindow() window.isReleasedWhenClosed = true

September 9, 2021 · 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 round specific corner in SwiftUI

Issue #846 We use UIBezierPath with addArc to draw specific corner with different rounding values. import SwiftUI extension View { func clipCorners( topLeft: CGFloat = 0, bottomLeft: CGFloat = 0, topRight: CGFloat = 0, bottomRight: CGFloat = 0 ) -> some View { clipShape( SpecificCornerShape( topLeft: topLeft, bottomLeft: bottomLeft, topRight: topRight, bottomRight: bottomRight ) ) } } struct SpecificCornerShape: Shape { var topLeft: CGFloat = 0 var bottomLeft: CGFloat = 0 var topRight: CGFloat = 0 var bottomRight: CGFloat = 0 func path(in rect: CGRect) -> Path { let minX = rect....

August 26, 2021 · 2 min · Khoa Pham

How to show suffix text in TextField in SwiftUI

Issue #845 Suppose we have struct Payment as the state, we declare custom Binding<String> that derives from our state. struct Payment { var amount: Int = 0 } To show our suffix view, we use .fixedSize(horizontal: true, vertical: false) to constrain our TextField to fit content, and to avoid it to grow outside our frame, we constrain a max limit, like 10_000 in this case. An interesting thing is we divide by 10 to not let the newly typed digit enter if the amount is above our limit...

August 24, 2021 · 1 min · Khoa Pham

How to show currency symbol in TextField in SwiftUI

Issue #844 Use custom Binding and validate the text. In case of zero, return empty string to let TextField display placeholder var textField: some View { let text = Binding<String>( get: { state.amount > 0 ? "$\(state.amount)" : "" }, set: { text in let text = text.replacingOccurrences(of: "$", with: "") state.amount = Int(text) ?? 0 } ) return TextField("$0", text: text) .keyboardType(.numberPad) .font(.system(size: 50, weight: .medium, design: .monospaced)) .foregroundColor(Asset.textPrimary.color) ....

August 24, 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 read safe area insets in SwiftUI

Issue #842 Declare EnvironmentKey and read safeAreaInsets from key window in connectedScenes struct SafeAreaInsetsKey: EnvironmentKey { static var defaultValue: EdgeInsets { UIApplication.shared.keyWindow?.safeAreaInsets.swiftUIInsets ?? EdgeInsets() } } private extension UIEdgeInsets { var swiftUIInsets: EdgeInsets { EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right) } } private extension UIApplication { var keyWindow: UIWindow? { connectedScenes .compactMap { $0 as? UIWindowScene } .flatMap { $0.windows } .first { $0.isKeyWindow } } } struct MessageBar: View { @Environment(\....

August 20, 2021 · 1 min · Khoa Pham

How to make tab strip with enum cases in SwiftUI

Issue #841 Declare generic on RawRepresentable import SwiftUI struct TabStrip<T: RawRepresentable & Hashable>: View where T.RawValue == String { let values: [T] @Binding var selected: T var body: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 24) { ForEach(values, id: \.self) { value in Button(action: { selected = value }) { Text(value.rawValue) .foregroundColor(selected == value ? Asset.textPrimary.color : Asset.textSecondary.color) .fontWeight(selected == value ? .semibold : .regular) .padding(6) } .buttonStyle(PlainButtonStyle()) } } } ....

August 19, 2021 · 1 min · Khoa Pham

How to replace multiple regex matches in Swift

Issue #840 Used to replace credit card regex 30[0-5]#-######-###L in EasyFake We can use ? to have non-greedy behavior, or I here use square bracket to fine-tune the expression \{[\d*,]*\d*\} Also, I need to reversed the matches because each replacement makes the next range incorrect extension String { func replacingHash() -> String { self .map { c in guard c == "#" else { return String(c) } let number = Int....

August 11, 2021 · 1 min · Khoa Pham

How to move files with Swift script

Issue #839 Use locales data from faker.js to https://github.com/onmyway133/EasyFake, renaming files since files, regardless of sub directories in Xcode, must have different name. We use enumerator API on FileManager to traverse all files in all sub-directories import Foundation let localesURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) .appendingPathComponent("Sources/EasyFake/Resources/locales") let manager = FileManager.default manager.enumerator(atPath: localesURL.path)?.forEach { path in guard let path = path as? String else { return } let url = URL(fileURLWithPath: path, relativeTo: localesURL) guard url....

August 10, 2021 · 1 min · Khoa Pham

How to map Binding with optional value in SwiftUI

Issue #838 We can write our custom Binding import SwiftUI extension Binding where Value == Date? { func flatten(defaultValue: Date) -> Binding<Date> { Binding<Date>( get: { wrappedValue ?? defaultValue }, set: { wrappedValue = $0 } ) } } Then use in places where it needs Binding with non-optional value DatePicker( "", selection: $date.flatten(defaultValue: Date()), displayedComponents: .hourAndMinute ) .datePickerStyle(WheelDatePickerStyle())

August 10, 2021 · 1 min · Khoa Pham

How to get view height in SwiftUI

Issue #837 I usually use GeometryReader in background to get size of view, and encapsulate it inside a ViewModifier struct GetHeightModifier: ViewModifier { @Binding var height: CGFloat func body(content: Content) -> some View { content.background( GeometryReader { geo -> Color in DispatchQueue.main.async { height = geo.size.height } return Color.clear } ) } } This is useful to constrain NavigationView height when in use inside a bottom sheet struct FilterView: View { @State private var height: CGFloat = 0 var body: some View { BottomSheet { NavigationView { VStack { Text("content") } ....

August 6, 2021 · 1 min · Khoa Pham

How to prevent wheel Picker conflict with DragGesture in SwiftUI

Issue #836 If we have a Picker inside a View that has DragGesture, chances are when we scroll the wheel, the DragGesture is detected too To prevent this, we can attach a dummy DragGesture to our Picker Picker("", selection: $number) { ForEach(FavoriteNumbers.allCases) { number in Text(number.name) } } .pickerStyle(WheelPickerStyle()) .gesture(DragGesture())

August 5, 2021 · 1 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