How to use HSplitView to define 3 panes view in SwiftUI for macOS

Issue #674 Specify minWidth to ensure miminum width, and use .layoutPriority(1) for the most important pane. import SwiftUI struct MainView: View { @EnvironmentObject var store: Store var body: some View { HSplitView { LeftPane() .padding() .frame(minWidth: 200, maxWidth: 500) MiddlePane(store: store) .padding() .frame(minWidth: 500) .layoutPriority(1) RightPane() .padding() .frame(minWidth: 300) } .background(R.color.background) } }

September 23, 2020 · 1 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’s 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 stitch and sort array in Swift

Issue #672 Supposed we want to stitch magazines array into books array. The requirement is to sort them by publishedDate, but must keep preferredOrder of books. One way to solve this is to declare an enum to hold all possible cases, and then do a sort that check every possible combination struct Book { let preferredOrder: Int let publishedDate: Date } struct Magazine { let publishedDate: Date } enum StitchItem { case book(Book) case magazine(Magazine) } func stitch(_ books: [Book], magazines: [Magazine]) -> [StitchItem] { let items = books....

August 28, 2020 · 2 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’s 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 make switch statement in SwiftUI

Issue #656 Lately I’ve been challenging myself to declare switch statement in SwiftUI, or in a more generalized way, execute any anonymous function that can return a View Use Switch and Case views Note that this approach does not work yet, as TupeView should support variadic number of contents, and also T.RawValue needs to conform to Equatable in order to check the cases. Also in Switch statement, Content can’t be inferred...

May 22, 2020 · 2 min · Khoa Pham

How to test DispatchQueue in Swift

Issue #646 Sync the DispatchQueue Pass DispatchQueue and call queue.sync to sync all async works before asserting Use mock Use DispatchQueueType and in mock, call the work immediately import Foundation public protocol DispatchQueueType { func async(execute work: @escaping @convention(block) () -> Void) } extension DispatchQueue: DispatchQueueType { public func async(execute work: @escaping @convention(block) () -> Void) { async(group: nil, qos: .unspecified, flags: [], execute: work) } } final class MockDispatchQueue: DispatchQueueType { func async(execute work: @escaping @convention(block) () -> Void) { work() } }

April 29, 2020 · 1 min · Khoa Pham

How to assert asynchronously in XCTest

Issue #644 import XCTest extension XCTestCase { /// Asynchronously assertion func XCTAssertWait( timeout: TimeInterval = 1, _ expression: @escaping () -> Void, _: String = "", file _: StaticString = #file, line _: UInt = #line ) { let expectation = self.expectation(description: #function) DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { expression() expectation.fulfill() } let waiter = XCTWaiter() XCTAssertTrue(waiter.wait(for: [expectation], timeout: timeout + 1) == .completed) } } Updated at 2020-04-28 09:23:59

April 28, 2020 · 1 min · Khoa Pham

How to format percent in Swift

Issue #639 Never use String(format: "%.2f %%", 1.2 because each region can have different separator and placement of percent sign. Use NumberFormatter instead let formatter = NumberFormatter() formatter.numberStyle = .percent formatter.minimumIntegerDigits = 1 formatter.maximumIntegerDigits = 3 formatter.maximumFractionDigits = 2 formatter.locale = Locale(identifier: "en_US") formatter.string(from: NSDecimalNumber(decimal: 1.2 / 100)) // 0.12% formatter.locale = Locale(identifier: "nb_NO") formatter.string(from: NSDecimalNumber(decimal: 1.2 / 100)) // 0,12 % Note that the space created by NumberFormatter is a non breakable space \u{00a0}, which can be created by Alt Space....

April 16, 2020 · 1 min · Khoa Pham

How to declare commands in Xcode extensions

Issue #638 Use commandDefinitions in XCSourceEditorExtension. import Foundation import XcodeKit class SourceEditorExtension: NSObject, XCSourceEditorExtension { func extensionDidFinishLaunching() { } var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] { func makeDef( _ className: String, _ commandName: String ) -> [XCSourceEditorCommandDefinitionKey: Any] { guard let bundleId = Bundle(for: type(of: self)).bundleIdentifier else { return [:] } return [ XCSourceEditorCommandDefinitionKey.identifierKey: bundleId + className, XCSourceEditorCommandDefinitionKey.classNameKey: className, XCSourceEditorCommandDefinitionKey.nameKey: commandName ] } return [ makeDef(TypeCommand.className(), "Type"), makeDef(ReloadCommand.className(), "Reload"), ] } } There is a weird crash that we can’t seem to declare functions or use commandDefinitions, the workaround is to declare in plist...

April 13, 2020 · 1 min · Khoa Pham

How to declare commands in Xcode extenstions

Issue #638 Use commandDefinitions in XCSourceEditorExtension. import Foundation import XcodeKit class SourceEditorExtension: NSObject, XCSourceEditorExtension { func extensionDidFinishLaunching() { } var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] { func makeDef( _ className: String, _ commandName: String ) -> [XCSourceEditorCommandDefinitionKey: Any] { guard let bundleId = Bundle(for: type(of: self)).bundleIdentifier else { return [:] } return [ XCSourceEditorCommandDefinitionKey.identifierKey: bundleId + className, XCSourceEditorCommandDefinitionKey.classNameKey: className, XCSourceEditorCommandDefinitionKey.nameKey: commandName ] } return [ makeDef(TypeCommand.className(), "Type"), makeDef(ReloadCommand.className(), "Reload"), ] } } There is a weird crash that we can’t seem to declare functions or use commandDefinitions, the workaround is to declare in plist...

April 13, 2020 · 1 min · Khoa Pham

How to disable ring type in TextField in SwiftUI

Issue #636 Normally we can just wrap NSTextField struct SearchTextField: NSViewRepresentable { @Binding var text: String var hint: String var onCommit: (String) -> Void func makeNSView(context: NSViewRepresentableContext<SearchTextField>) -> NSTextField { let tf = NSTextField() tf.focusRingType = .none tf.isBordered = false tf.isEditable = true tf.isSelectable = true tf.drawsBackground = false tf.delegate = context.coordinator tf.font = NSFont(name: OpenSans.bold.rawValue, size: 14) tf.placeholderString = hint return tf } func updateNSView( _ nsView: NSTextField, context: NSViewRepresentableContext<SearchTextField> ) { nsView....

April 6, 2020 · 1 min · Khoa Pham

How to handle enter key in NSTextField

Issue #635 textField.delegate = self NSTextFieldDelegate func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { if (commandSelector == #selector(NSResponder.insertNewline(_:))) { // Do something against ENTER key print("enter") return true } else if (commandSelector == #selector(NSResponder.deleteForward(_:))) { // Do something against DELETE key return true } else if (commandSelector == #selector(NSResponder.deleteBackward(_:))) { // Do something against BACKSPACE key return true } else if (commandSelector == #selector(NSResponder.insertTab(_:))) { // Do something against TAB key return true } else if (commandSelector == #selector(NSResponder....

April 3, 2020 · 1 min · Khoa Pham

How to decode with default case for enum in Swift

Issue #634 public enum Weapon: String, Decodable { case sword = "SWORD" case gun = "GUN" case unknown = "UNKNOWN" public init(from decoder: Decoder) throws { let rawValue = try decoder.singleValueContainer().decode(String.self) self = Weapon(rawValue: rawValue) ?? .unknown } }

April 2, 2020 · 1 min · Khoa Pham

How to conditionally apply modifier in SwiftUI

Issue #633 Use autoclosure and AnyView @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) public extension View { func applyIf<T: View>(_ condition: @autoclosure () -> Bool, apply: (Self) -> T) -> AnyView { if condition() { return apply(self).erase() } else { return self.erase() } } } Button(action: onSearch) { Image("search") .resizable() .styleButton() .overlay(ToolTip("Search")) } .buttonStyle(BorderlessButtonStyle()) .applyIf(showsSearch, apply: { $0.foregroundColor(Color.orange) })

March 30, 2020 · 1 min · Khoa Pham

How to toggle with animation in SwiftUI

Issue #632 Use Group private func makeHeader() -> some View { Group { if showsSearch { SearchView( onSearch: onSearch ) .transition(.move(edge: .leading)) } else { InputView( onAdd: onAdd ) .transition(.move(edge: .leading)) } } } withAnimation { self.showsSearch.toggle() }

March 30, 2020 · 1 min · Khoa Pham

How to show context popover from SwiftUI for macOS

Issue #630 For SwiftUI app using NSPopover, to show context popover menu, we can ask for windows array, get the _NSPopoverWindow and calculate the position. Note that origin of macOS screen is bottom left (lldb) po NSApp.windows ▿ 2 elements - 0 : <NSStatusBarWindow: 0x101a02700> - 1 : <_NSPopoverWindow: 0x101c01060> let handler = MenuHandler() handler.add(title: "About", action: onAbout) handler.add(title: "Quit", action: onQuit) guard let window = NSApp.windows.last else { return } let position = CGPoint( x: window....

March 22, 2020 · 1 min · Khoa Pham

How to make segmented control in SwiftUI for macOS

Issue #629 Use Picker with SegmentedPickerStyle. Picker(selection: $preferenceManager.preference.display, label: EmptyView()) { Image("grid") .resizable() .padding() .tag(0) Image("list") .resizable() .tag(1) }.pickerStyle(SegmentedPickerStyle()) .frame(width: 50) .padding(.leading, 16) .padding(.trailing, 24) Alternatively, we can make custom NSSegmentedControl import AppKit import SwiftUI struct MySegmentControl: NSViewRepresentable { func makeCoordinator() -> MySegmentControl.Coordinator { Coordinator(parent: self) } func makeNSView(context: NSViewRepresentableContext<MySegmentControl>) -> NSSegmentedControl { let control = NSSegmentedControl( images: [ NSImage(named: NSImage.Name("grid"))!, NSImage(named: NSImage.Name("list"))! ], trackingMode: .selectOne, target: context.coordinator, action: #selector(Coordinator.onChange(_:)) ) return control } func updateNSView(_ nsView: NSSegmentedControl, context: NSViewRepresentableContext<MySegmentControl>) { } class Coordinator { let parent: MySegmentControl init(parent: MySegmentControl) { self....

March 22, 2020 · 1 min · Khoa Pham

How to iterate over XCUIElementQuery in UITests

Issue #628 extension XCUIElementQuery: Sequence { public typealias Iterator = AnyIterator<XCUIElement> public func makeIterator() -> Iterator { var index = UInt(0) return AnyIterator { guard index < self.count else { return nil } let element = self.element(boundBy: Int(index)) index = index + 1 return element } } } extension NSPredicate { static func label(contains string: String) -> NSPredicate { NSPredicate(format: "label CONTAINS %@", string) } } let books = app.collectionViews.cells.matching( NSPredicate....

March 20, 2020 · 1 min · Khoa Pham