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 · 95 words · Khoa

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 · 207 words · Khoa

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 · 106 words · Khoa

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 · 39 words · Khoa

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 · 61 words · Khoa

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 · 38 words · Khoa

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 · 90 words · Khoa

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 · 104 words · Khoa

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 · 79 words · Khoa

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 · 90 words · Khoa

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 · 118 words · Khoa

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 · 58 words · Khoa

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 If you want to provide your sandboxed app with persistent access to file system resources, you must enable security-scoped bookmark and URL access....

March 17, 2020 · 2 min · 274 words · Khoa

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. To do this, first make sure the resultType of the NSBatchDeleteRequest is set to NSBatchDeleteRequestResultType....

March 15, 2020 · 1 min · 191 words · Khoa

How to make TextField focus in SwiftUI for macOS

Issue #620 For NSWindow having levelother than .normal, need to override key and main property to allow TextField to be focusable class FocusWindow: NSWindow { override var canBecomeKey: Bool { true } override var canBecomeMain: Bool { true } } Furthermore to customize TextField, consider using custom import SwiftUI import AppKit struct MyTextField: NSViewRepresentable { @Binding var text: String func makeNSView(context: NSViewRepresentableContext<MyTextField>) -> NSTextField { let tf = NSTextField() tf.focusRingType = ....

March 13, 2020 · 1 min · 135 words · Khoa

How to show popover for item in ForEach in SwiftUI

Issue #618 Create custom Binding List { ForEach(self.items) { (item: item) in ItemRowView(item: item) .popover(isPresented: self.makeIsPresented(item: item)) { ItemDetailView(item: item) } } } func makeIsPresented(item: Item) -> Binding<Bool> { return .init(get: { return self.selectedId == item.id }, set: { _ in self.selectedId = nil }) }

March 11, 2020 · 1 min · 46 words · Khoa

How to make tooltip in SwiftUI for macOS

Issue #617 On macOS 11, we can use .help modifier to add tooltip Button() .help("Click here to open settings") If you support macOS 10.15, then create empty NSView and use as overlay. Need to updateNSView in case we toggle the state of tooltip import SwiftUI struct Tooltip: NSViewRepresentable { let tooltip: String func makeNSView(context: NSViewRepresentableContext<Tooltip>) -> NSView { let view = NSView() view.toolTip = tooltip return view } func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<Tooltip>) { nsView....

March 11, 2020 · 1 min · 171 words · Khoa

How to make tab view in SwiftUI

Issue #614 struct MyTabView: View { @EnvironmentObject var preferenceManager: PreferenceManager var body: some View { VOrH(isVertical: preferenceManager.preference.position.isVertical) { OneTabView(image: "one", text: "One", tab: .one) OneTabView(image: "two", text: "Two", tab: .two) OneTabView(image: "three", text: "Three", tab: .three) Spacer() } } } struct OneTabView: View { @EnvironmentObject var preferenceManager: PreferenceManager let image: String let text: String let tab: Tab var selected: Bool { preferenceManager.preference.tab == tab } var body: some View { Button(action: { self....

March 2, 2020 · 1 min · 117 words · Khoa

How to present NSWindow modally

Issue #612 Use runModal This method runs a modal event loop for the specified window synchronously. It displays the specified window, makes it key, starts the run loop, and processes events for that window. (You do not need to show the window yourself.) While the app is in that loop, it does not respond to any other events (including mouse, keyboard, or window-close events) unless they are associated with the window....

March 2, 2020 · 1 min · 190 words · Khoa

How to use Picker with enum in SwiftUI

Issue #611 enum WindowPosition: String, CaseIterable { case left case top case bottom case right } Picker(selection: $preference.position, label: Text("Position")) { ForEach(WindowPosition.allCases, id: \.self) { Text($0.rawValue) } }

February 29, 2020 · 1 min · 28 words · Khoa