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

How to check if NSColor is light

Issue #627 Algorithm from https://www.w3.org/WAI/ER/WD-AERT/#color-contrast extension NSColor { var isLight: Bool { guard let components = cgColor.components, components.count >= 3 else { return false } let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 return brightness > 0.5 } } Then we can apply contrast color for our Text extension Text { func applyColorBaseOnBackground(_ color: NSColor?) -> some View { guard let color = color else { return self } if color....

March 20, 2020 · 1 min · Khoa Pham

How to trigger onAppear in SwiftUI for macOS

Issue #626 SwiftUI does not trigger onAppear and onDisappear like we expect. We can use NSView to trigger import SwiftUI struct AppearAware: NSViewRepresentable { var onAppear: () -> Void func makeNSView(context: NSViewRepresentableContext<AppearAware>) -> AwareView { let view = AwareView() view.onAppear = onAppear return view } func updateNSView(_ nsView: AwareView, context: NSViewRepresentableContext<AppearAware>) { } } final class AwareView: NSView { private var trigged: Bool = false var onAppear: () -> Void = {} override func viewDidMoveToSuperview() { super....

March 18, 2020 · 1 min · Khoa Pham

How to force refresh in ForEach in SwiftUI for macOS

Issue #625 For some strange reasons, content inside ForEach does not update with changes in Core Data NSManagedObject. The workaround is to introduce salt, like UUID just to make state change struct NoteRow: View { let note: Note let id: UUID } List { ForEach(notes) { note in NoteRow(note: note, id: UUID()) } } Updated at 2020-11-20 03:29:39...

March 17, 2020 · 1 min · Khoa Pham

How to access bookmark url in macOS

Issue #624 By default the approaches above grant you access while the app remains open. When you quit the app, any folder access you had is lost. To gain persistent access to a folder even on subsequent launches, we’ll have to take advantage of a system called Security-Scoped Bookmarks. Add entitlements Use of app-scoped bookmarks and URLs <key>com.apple.security.files.user-selected.read-only</key> <true/> <key>com.apple.security.files.bookmarks.app-scope</key> <true/> Enabling Security-Scoped Bookmark and URL Access...

March 17, 2020 · 2 min · Khoa Pham

How to batch delete in Core Data

Issue #622 Read Implementing Batch Deletes If the entities that are being deleted are not loaded into memory, there is no need to update your application after the NSBatchDeleteRequest has been executed. However, if you are deleting objects in the persistence layer and those entities are also in memory, it is important that you notify the application that the objects in memory are stale and need to be refreshed....

March 15, 2020 · 1 min · Khoa Pham