How to make stepper with plus and minus buttons in SwiftUI for macOS

Issue #717 Try to use predefined system colors in Human Interface Guidelines for macOS Here we use this color unemphasizedSelectedTextBackgroundColor for button background HStack(spacing: 1) { makeUnderListButton(action: {}, icon: .plus) makeUnderListButton(action: {}, icon: .minus) } .background(Color(NSColor.unemphasizedSelectedTextBackgroundColor)) .cornerRadius(4) func makeUnderListButton(action: @escaping () -> Void, icon: AwesomeIcon) -> some View { Button(action: action) { Text(icon.rawValue) .font(.awesome(style: .solid, size: 14)) } .buttonStyle(HighlightButtonStyle(h: 8, v: 6, cornerRadius: 4)) } Another thing is List, where we have selected and alternative background colors....

December 21, 2020 · 1 min · Khoa Pham

How to fix Picker not showing selection in SwiftUI

Issue #716 I have an enum that conforms to CaseIterable that I want to show in Picker enum Position: String, Codable, CaseIterable, Identifiable { var id: String { rawValue } case left case right case bottom case top } Picker(selection: $preference.position, label: Text("Position")) { ForEach(Preference.Position.allCases) { position in Text(position.rawValue) } } It compiles and runs just fine, but Picker does not show current selection regardless of any Picker style I choose....

December 20, 2020 · 2 min · Khoa Pham

How to add toolbar programatically in macOS

Issue #713 To setup toolbar, we need to implement NSToolbarDelegate that provides toolbar items. This delegate is responsible for many things Set visible and allowed items with toolbarDefaultItemIdentifiers Provide item with itemForItemIdentifier Being notified with toolbarWillAddItem and toolbarDidRemoveItem window.toolbarStyle = .unifiedCompact let toolbar = NSToolbar(identifier: "Toolbar") toolbar.displayMode = .iconAndLabel toolbar.delegate = (NSApp.delegate as! AppDelegate) toolbar.insertItem(withItemIdentifier: .add, at: 0) toolbar.insertItem(withItemIdentifier: .settings, at: 1) window.toolbar = toolbar extension NSToolbarItem.Identifier { static let add = NSToolbarItem....

December 15, 2020 · 1 min · Khoa Pham

How to show sidebar in SwiftUI for macOS

Issue #710 Starting from macOS 11, we can use List with SidebarListStyle inside NavigationView to declare master detail view. The SidebarListStyle makes list translucent. It automatically handles selection and marks selected row in list with accent color. struct MainView: some View { var body: some View { NavigationView { sidebar ContentView() } } private var sidebar: some View { List { Group { Text("Categories") .foregroundColor(.gray) ForEach(categories) { category in NavigationLink(destination: ContentView(category: category)) { Label(category....

December 13, 2020 · 1 min · Khoa Pham

How to support right click menu to NSStatusItem

Issue #707 The trick is to set the button oinside of statusItem to send actions on both leftMouseUp and rightMouseUp. Another thing to note is we use popUpMenu on NSStatusItem, although it is marked as deprecated on macOS 10.14. We can set menu but that overrides left click. import Omnia private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) private let statusItemMenuHandler = MenuHandler() func setupStatusMenu() { if let button = statusItem.button { button....

December 7, 2020 · 1 min · Khoa Pham

How to convert from paid to free with IAP

Issue #703 What is receipt Read When to refresh a receipt vs restore purchases in iOS? From iOS 7, every app downloaded from the store has a receipt (for downloading/buying the app) at appStoreReceiptURL. When users purchases something via In App Purchase, the content at appStoreReceiptURL is updated with purchases information. Most of the cases, you just need to refresh the receipt (at appStoreReceiptURL) so that you know which transactions users have made....

November 30, 2020 · 9 min · Khoa Pham

How to disable NSTextView in SwiftUI

Issue #702 The trick is to use an overlay MessageTextView(text: $input.message) .overlay(obscure) var obscure: AnyView { if store.pricingPlan.isPro { return EmptyView().erase() } else { return Color.black.opacity(0.01).erase() } }

November 27, 2020 · 1 min · Khoa Pham

How to check dark mode in AppKit for macOS apps

Issue #693 AppKit app has its theme information stored in UserDefaults key AppleInterfaceStyle, if is dark, it contains String Dark. Another way is to detect appearance via NSView struct R { static let dark = DarkTheme() static let light = LightTheme() static var theme: Theme { let isDark = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" return isDark ? dark : light } } Another way is to rely on appearance on NSView. You can quickly check via NSApp....

November 10, 2020 · 2 min · Khoa Pham

How to make full size content view in SwiftUI for macOS

Issue #689 func applicationDidFinishLaunching(_ aNotification: Notification) { // extend to title bar let contentView = ContentView() // .padding(.top, 24) // can padding to give some space .edgesIgnoringSafeArea(.top) // specify fullSizeContentView window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), styleMask: [.titled, .closable, .miniaturizable, .texturedBackground, .resizable, .fullSizeContentView], backing: .buffered, defer: false ) window.center() window.setFrameAutosaveName("My App") // window.title = ... // no title // window.toolbar = NSToolbar() // use toolbar if wanted....

November 3, 2020 · 1 min · Khoa Pham

How to style multiline Text in SwiftUI for macOS

Issue #681 Only need to specify fixedSize on text to preserve ideal height. The maximum number of lines is 1 if the value is less than 1. If the value is nil, the text uses as many lines as required. The default is nil. Text(longText) .lineLimit(nil) // No need .fixedSize(horizontal: false, vertical: true) If the Text is inside a row in a List, fixedSize causes the row to be in middle of the List, workaround is to use ScrollView and vertical StackView....

October 7, 2020 · 1 min · Khoa Pham

How to clear List background color in SwiftUI for macOS

Issue #680 For List in SwiftUI for macOS, it has default background color because of the enclosing NSScrollView via NSTableView that List uses under the hood. Using listRowBackground also gives no effect The solution is to use a library like SwiftUI-Introspect import Introspect extension List { func removeBackground() -> some View { return introspectTableView { tableView in tableView.backgroundColor = .clear tableView.enclosingScrollView!.drawsBackground = false } } } then List { ForEach(items) { item in // view here } } ....

October 4, 2020 · 1 min · Khoa Pham

How to avoid reduced opacity when hiding view with animation in SwiftUI

Issue #679 While redesigning UI for my app Push Hero, I ended up with an accordion style to toggle section. It worked great so far, but after 1 collapsing, all image and text views have reduced opacity. This does not happen for other elements like dropdown button or text. extension View { func sectionBackground(_ title: String, _ shows: Binding<Bool>) -> some View { VStack(alignment: .leading) { HStack { Text(title.uppercased()) Spacer() if shows !...

October 1, 2020 · 1 min · Khoa Pham

How to unwrap Binding with Optional in SwiftUI

Issue #677 The quick way to add new properties without breaking current saved Codable is to declare them as optional. For example if you use EasyStash library to save and load Codable models. import SwiftUI struct Input: Codable { var bundleId: String = "" // New props var notificationId: String? This new property when using dollar syntax $input.notificationId turn into Binding with optional Binding<Strting?> which is incompatible in SwiftUI when we use Binding....

September 29, 2020 · 1 min · Khoa Pham

How to make custom toggle in SwiftUI

Issue #676 I’ve used the default SwiftUI to achieve the 2 tab views in SwiftUI. It adds a default box around the content and also opinionated paddings. For now on light mode on macOS, the unselected tab has wrong colors. The way to solve this is to come up with a custom toggle, that we can style and align the way we want. Here is how I did for my app Push Hero...

September 29, 2020 · 1 min · Khoa Pham

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 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 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