How to disable NSTextView in SwiftUI
Issue #702
The trick is to use an overlay
1 | MessageTextView(text: $input.message) |
Issue #702
The trick is to use an overlay
1 | MessageTextView(text: $input.message) |
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
1 | struct R { |
Another way is to rely on appearance
on NSView. You can quickly check via NSApp.keyWindow?.effectiveAppearance
but notice that keyWindow
can be nil when the app is not active since no window is focused for keyboard events. You should use NSApp.windows.first
1 | let isDark = NSApp.windows.first?.effectiveAppearance.bestMatch(from: [.darkAqua, .vibrantDark]) == .darkAqua |
Then build a simple Theme system
1 | import SwiftUI |
For SwiftUI, you can colorScheme
environment, then use modifier .id(colorScheme)
to force SwiftUI to update when color scheme changes
1 | struct MainView: View { |
Updated at 2020-11-10 05:52:33
Issue #689
1 | func applicationDidFinishLaunching(_ aNotification: Notification) { |
HSplitView
, some Button are not clickable until I drag the split view handle. The workaround is to use HStack with a DividerUpdated at 2020-11-03 05:21:50
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.
1 | Text(longText) |
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
.
Updated at 2020-10-07 05:02:01
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
1 | import Introspect |
then
1 | List { |
Or we can add extension on NSTableView
to alter its content when it moves to superview
1 | extension NSTableView { |
This works OK for me on macOS 10.15.5, 10.15.7 and macOS 10.11 beta. But it was reported crash during review on macOS 10.15.6
The app launches briefly and then quits without error message.
After inspecting crash log, it is because of viewDidMoveToWindow
. So it’s wise not to mess with NSTableView for now
1 | Thread 0 Crashed:: Dispatch queue: com.apple.main-thread |
Updated at 2020-10-09 03:49:39
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.
1 | extension View { |
The culprit is that withAnimation
, it seems to apply opacity effect. So the workaround is to disable animation wrappedValue
, or to tweak transition so that there’s no opacity adjustment.
1 | if shows.wrappedValue { |
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.
1 | import SwiftUI |
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.
1 | struct MaterialTextField: View { |
The solution here is write an extension that converts Binding<String?>
to Binding<String>
1 | extension Binding where Value == String? { |
so we can use them as normal
1 | MaterialTextField(text: $input.notificationId.toNonOptional()) |
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
Using a Text instead of Button here gives me default static text look.
1 | struct AuthenticationView: View { |
Updated at 2020-09-29 04:55:14
Issue #674
Specify minWidth
to ensure miminum width, and use .layoutPriority(1)
for the most important pane.
1 | import SwiftUI |
Updated at 2020-09-23 08:42:29
Issue #636
Normally we can just wrap NSTextField
1 | struct SearchTextField: NSViewRepresentable { |
But there is a weird Appstore rejection where the textfield is not focusable. The workaround is to use TextField
1 | extension NSTextField { |
Issue #635
1 | textField.delegate = self |
1 | NSTextFieldDelegate |
Issue #632
Use Group
1 | private func makeHeader() -> some View { |
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
1 | (lldb) po NSApp.windows |
1 | let handler = MenuHandler() |
Issue #629
Use Picker
with SegmentedPickerStyle
.
1 | Picker(selection: $preferenceManager.preference.display, label: EmptyView()) { |
Alternatively, we can make custom NSSegmentedControl
1 | import AppKit |
Issue #627
Algorithm from https://www.w3.org/WAI/ER/WD-AERT/#color-contrast
1 | extension NSColor { |
Then we can apply contrast color for our Text
1 | extension Text { |
Issue #626
SwiftUI does not trigger onAppear and onDisappear like we expect. We can use NSView to trigger
1 | import SwiftUI |
Then we can use it as an hidden view, like in a ZStack
1 | ZStack { |
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
1 | struct NoteRow: View { |
Updated at 2020-11-20 03:29:39
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
1 | <key>com.apple.security.files.user-selected.read-only</key> |
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. Security-scoped bookmarks are available starting in macOS v10.7.3.
To add the bookmarks.app-scope or bookmarks.document-scope entitlement, edit the target’s .entitlements property list file using the Xcode property list editor. Use the entitlement keys shown in Table 4-4, depending on which type of access you want. Use a value of
for each entitlement you want to enable. You can enable either or both entitlements.
1 | func saveBookmark(item: ShortcutItem) { |
1 | _ = url.startAccessingSecurityScopedResource() |
Issue #620
For NSWindow
having level
other than .normal
, need to override key and main property to allow TextField to be focusable
1 | class FocusWindow: NSWindow { |
Furthermore to customize TextField, consider using custom
1 | import SwiftUI |
Issue #617
On macOS 11, we can use .help
modifier to add tooltip
1 | Button() |
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
1 | import SwiftUI |
1 | Button(action: self.onGear) { |
Now we can add tooltip as a background. Before I used to add as overlay but that prevents interaction, even with .disabled(true)
1 | Button(action: self.onGear) { |
And we can even make an extension on View
to use tooltip easily
1 | import SwiftUI |
Updated at 2020-12-12 19:54:53
Issue #615
1 | List { |
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. It also does not perform any tasks (such as firing timers) that are not associated with the modal run loop. In other words, this method consumes only enough CPU time to process events and dispatch them to the action methods associated with the modal window.
Specify level
in windowDidBecomeKey
1 | let controller = SettingsWindowController() |
Issue #610
Set NSVisualEffectView
as contentView of NSWindow, and our main view as subview of it. Remember to set frame or autoresizing mask as non-direct content view does not get full size as the window
1 | let mainView = MainView() |
Updated at 2021-01-05 21:13:39
Issue #609
Use animator
proxy and animate
parameter
1 | var rect = window.frame |
Updated at 2020-11-22 18:26:33
Issue #608
An NSRunningApplication instance for the current application.
1 | NSRunningApplication.current |
The running app instance for the app that receives key events.
1 | NSWorkspace.shared.frontmostApplication |
Issue #601
On Xcode 11, applicationWillTerminate
is not called because of default automatic termination on in Info.plist. Removing NSSupportsSuddenTermination
to trigger will terminate notification
1 | func applicationWillTerminate(_ notification: Notification) { |
1 | <key>NSSupportsAutomaticTermination</key> |
Issue #595
SwiftUI uses ListCoreScrollView
and ListCoreClipView
under the hood. For now the workaround, is to avoid using List
1 | List { |
use
1 | VStack { |
Updated at 2020-08-14 07:26:27
Issue #593
#if canImport(Combine)
is not enough, need to specify in Other Linker Flags
1 | OTHER_LDFLAGS = -weak_framework Combine |
Issue #591
Use NSTextView
instead
Issue #590
Use custom NSTextField
as it is hard to customize TextFieldStyle
1 | import SwiftUI |