On macOS, it's not uncommon to present windows as sheets that can be resized. By setting the NSWindow's various frame auto save properties, you can restore the size of the sheet the next time it is presented.
When presenting a Sheet from within SwiftUI using the .sheet view modifier, how can I preserve and restore the sheet's frame size?
The closest I've been able to come is to put the SwiftUI view into a custom NSHostingController and then into an NSViewControllerRepresentable and then override viewWillAppear and look for self.view.window, which is all little awkward.
Is there a more idiomatic way to achieve this in "pure" SwiftUI?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
How do you atomically set a Picker's selection and contents on macOS such that you don't end up in a situation where the selection is not present within the Picker's content?
I presume Picker on macOS is implemented as an NSPopUpButton and an NSPopUpButton doesn't really like the concept of "no selection". SwiftUI, when presented with that, outputs:
Picker: the selection "nil" is invalid and does not have an associated tag, this will give undefined results.
Consider the following pseudo code:
struct ParentView: View {
@State private var items: [Item]
var body: some View {
ChildView(items: items)
}
}
struct ChildView: View {
let items: [Item]
@State private var selectedItem: Item?
var body: some View {
Picker("", selection: $selectedItem) {
ForEach(items) { item in
Text(item.name).tag(item)
}
}
}
}
When items gets passed down from ParentView to the ChildView, it's entirely possible that the current value in selectedItem represents an Item that is not longer in the items[] array.
You can "catch" that by using .onAppear, .task, .onChange and maybe some other modifiers, but not until after at least one render pass has happened and an error has likely been reported because selectedItem is nil or it's not represented in the items[] array.
Because selectedItem is private state, a value can't easily be passed down from the parent view, though even if it could that just kind of moves the problem one level higher up.
What is the correct way to handle this type of data flow in SwiftUI for macOS?
I'm currently integrating SwiftUI into an AppKit based application and was curious if the design pattern below was viable or not. In order to "bridge" between AppKit and SwiftUI, most of my SwiftUI "root" views have aViewModel that is accessible to the SwiftUI view via @ObservedObject.
When a SwiftUI views need to use NSViewRepresentable I'm finding the use of a ViewModel and a Coordinator to be an unnecessary layer of indirection. In cases where it makes sense, I've just used the ViewModel as the Coordinator and it all appears to be working ok, but I'm curious if this is reasonable design pattern or if I'm overlooking something.
Consider the following pseudo code:
// 1. A normal @ObservedObject acting as the ViewModel that also owns and manages an NSTableView.
@MainActor final class ViewModel: ObservedObject, NSTableView... {
let scrollView: NSScrollView
let tableView: NSTableView
@Published var selectedTitle: String
init() {
// ViewModel manages tableView as its dataSource and delegate.
tableView.dataSource = self
tableView.delegate = self
}
func reload() {
tableView.reloadData()
}
// Update view model properties.
// Simpler than passing back up through a Coordinator.
func tableViewSelectionDidChange(_ notification: Notification) {
selectedTitle = tableView.selectedItem.title
}
}
// 2. A normal SwiftUI view, mostly driven by the ViewModel.
struct ContentView: View {
@ObservedObject model: ViewModel
var body: some View {
Text(model.selectedTitle)
// No need to pass anything down other than the view model.
MyTableView(model: model)
Button("Reload") { model.reload() }
Button("Delete") { model.deleteRow(...) }
}
}
// 3. A barebones NSViewRepresentable that just vends the required NSView. No other state is required as the ViewModel handles all interactions with the view.
struct MyTableView: NSViewRepresentable {
// Can this even be an NSView?
let model: ViewModel
func makeNSView(context: Context) -> some NSView {
return model.scrollView
}
func updateNSView(_ nsView: NSViewType, context: Context) {
// Not needed, all updates are driven through the ViewModel.
}
}
From what I can tell, the above is working as expected, but I'm curious if there are some situations where this could "break", particularly around the lifecycle of NSViewRepresentable
Would love to know if overall pattern is "ok" from a SwiftUI perspective.
I'm wondering what the correct, or recommended, way is to dismiss a SwiftUI that is being presented as a sheet hosted by an NSHostingController. The usual technique of invoking @Environment(\.dismiss) does not appear to work.
Consider the code below. An NSWindowController is attempting to display a SwiftUI SettingsView as a sheet. The sheet is correctly presented, but the SettingsView is unable to dismiss itself.
I am able to make it work by passing a closure into SettingsView that calls back to the NSWindowController but it's rather convoluted because SettingsView doesn't know the view controller that's hosting it until after SettingsView has been created, which means "finding" that view controller in the window controller to dismiss is more involved than it should be.
Is there a better strategy to leverage here?
final class MyViewController: NSViewController {
@IBAction func buttonClicked(_ sender: NSButton) {
if let presenter = window?.contentViewController {
presenter.presentAsSheet(NSHostingController(rootView: SettingsView()))
}
}
}
struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack {
Button("Cancel", role: .cancel) {
dismiss() // This call does not dismiss the sheet.
} .keyboardShortcut(.cancelAction)
}
}
}
Thank you.
macOS 15.4.1 (24E263), Xcode 16.3 (16E140)
Trying to incorporate a LookAroundView into my macOS application but unable to make the LookAroundView interactive at all. I can get it to display a static image, but there's no interactivity at all and no controls visible.
This is using the SwiftUI LookAroundPreview view as well as trying to wrap MKLookAroundViewController inside an NSViewRepresentable.
The navigation properties are set to true but that doesn't seem to make a difference. Would love to incorporate this feature but without interactivity its value is limited.
macOS 15.4.1 (24E263), Xcode Version 16.3 (16E140)
In AppKit, NSSegmentedControl has various styles defined by NSSegmentStyle and various tracking modes defined by NSSegmentSwitchTracking.
How can we set these properties in SwiftUI?
I'm currently using a Picker with the view modifier .pickerStyle(.segmented) applied but this seems to produce a segmented control with tracking set to "select one".
In particular I'm looking for momentary tracking so that I can create navigation-style buttons for backward/forward navigation.
Under AppKit, the canonical way to do this is an NSSegmentedControl of style separated and tracking momentary.
Is that possible under SwiftUI for macOS? (Using the latest versions of everything.)
Without resorting to NSViewRepresentable, is there a view or view modifier in SwiftUI that can create an NSComboButton on macOS?
NSComboButton was introduced in macOS 13 and is (relatively) new to AppKit:
Apple Developer - NSComboButton
I only require support on macOS for this control.
Note that this is not to be confused with NSComboBox, which is a completely different control.
I'm currently working on a project to integrate some SwiftUI components into an existing AppKit application. The application makes extensive use of NSViewControllers. I can easily bridge between AppKit and SwiftUI using a view model that conforms to ObservableObject and is shared between the NSViewController and the SwiftUI View. But it's kind of tedious creating a view model for every view.
Is it "safe" and "acceptable" for the NSViewController to "hold on" to the SwiftUI View that it creates and then access its @State or @StateObject properties?
The lifecycle of DetailsView, a SwiftUI View, isn't clear to me when viewed through the lens of an NSViewController. Consider the following:
import AppKit
import SwiftUI
struct DetailsView: View {
@State var details: String = ""
var body: some View {
Text(details)
}
}
final class ViewController: NSViewController {
private let detailsView: DetailsView
init() {
self.detailsView = DetailsView()
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
view.addSubview(NSHostingView(rootView: detailsView))
}
func updateDetails(_ details: String) {
// Is this 'safe' and 'acceptable'?
self.detailsView.details = details
}
}
Is the view controller guaranteed to always be updating the correct @State property or is there a chance that the view controller's reference to it somehow becomes stale because of a SwiftUI update?
This is a post down memory lane for you AppKit developers and Apple engineers...
TL;DR:
When did the default implementation of NSViewController.loadView start making an NSView when there's no matching nib file? (I'm sure that used to return nil at some point way back when...)
If you override NSViewController.loadView and call [super loadView] to have that default NSView created, is it safe to then call self.view within loadView?
I'm refactoring some old Objective-C code that makes extensive use of NSViewController without any use of nibs. It overrides loadView, instantiates all properties that are views, then assigns a view to the view controller's view property. This seems inline with the documentation and related commentary in the header. I also (vaguely) recall this being a necessary pattern when not using nibs:
@interface MyViewController: NSViewController
// No nibs
// No nibName
@end
@implementation MyViewController
- (void)loadView {
NSView *hostView = [[NSView alloc] initWithFrame:NSZeroRect];
self.button = [NSButton alloc...];
self.slider = [NSSlider alloc...];
[hostView addSubview:self.button];
[hostView addSubview:self.slider];
self.view = hostView;
}
@end
While refactoring, I was surprised to find that if you don't override loadView and do all of the setup in viewDidLoad instead, then self.view on a view controller is non-nil, even though there was no nib file that could have provided the view. Clearly NSViewController has realized that:
There's no nib file that matches nibName.
loadView is not overridden.
Created an empty NSView and assigned it to self.view anyways.
Has this always been the behaviour or did it change at some point? I could have sworn that if there as no matching nib file and you didn't override loadView, then self.view would be nil.
I realize some of this behaviour changed in 10.10, as noted in the header, but there's no mention of a default NSView being created.
Because there are some warnings in the header and documentation around being careful when overriding methods related to view loading, I'm curious if the following pattern is considered "safe" in macOS 15:
- (void)loadView {
// Have NSViewController create a default view.
[super loadView];
self.button = [NSButton...];
self.slider = [NSSlider...];
// Is it safe to call self.view within this method?
[self.view addSubview:self.button];
[self.view addSubview:self.slider];
}
Finally, if I can rely on NSViewController always creating an NSView for me, even when a nib is not present, then is there any recommendation on whether one should continue using loadView or instead move code the above into viewDidLoad?
- (void)viewDidLoad {
self.button = [NSButton...];
self.slider = [NSSlider...];
// Since self.view always seems to be non-nil, then what
// does loadView offer over just using viewDidLoad?
[self.view addSubview:self.button];
[self.view addSubview:self.slider];
}
This application will have macOS 15 as a minimum requirement.
While trying to debug some weird drawing issues under macOS 14, I remembered that there was a comment in the AppKit Release notes related to drawing and NSView.clipsToBounds.
AppKit Release Notes for macOS 14
Under the section titled NSView, the following statement is made:
For applications linked against the macOS 14 SDK, the default value of this property is true. Apps linked against older SDKs default to false. Some classes, like NSClipView, continue to default to true.
Is this statement possibly backwards? From what I can tell, under macOS 14 NSView.clipsToBounds now defaults to false.
I came across this while trying to debug an issue where views that override drawRect with the intent of calling NSFillRect(self.bounds) with a solid color are, sometimes, briefly flickering because self.bounds is NSZeroRect, even though self.frame is not (nor is the dirtyRect).
This seems to be happening when views are added as subviews to a parent view. The subviews, which override drawRect, periodically "miss" a repaint and thus flicker. This seems to happen when views are frequently added or removed, like what happens in a scrolling view that is "recycling" views as they go offscreen. Views that scroll into the viewport are added as subviews and, sometimes, briefly flicker.
Replacing calls to drawRect with wantsUpdateLayer and updateLayer eliminates the flickering, which makes me think something is going astray in drawRect and the various rects you can use.
This is with Xcode 15.4, linking against macOS 14.5 and running on macOS 14.6.1
How do you send an NSURL representing a new file, as returned from an NSSavePanel, to an XPC service such that the service is granted permission to create the file?
I can successfully pass an NSURL to the XPC process if the NSURL represents an existing file. This is documented in Apple's Documentation:
Share file access between processes with URL bookmarks
This involves creating bookmark date while passing 0 in as the options. However, if you try to create bookmark data for an NSURL that represents a file that is not yet created, you do not get any bookmark data back and an error is returned instead:
Error Domain=NSCocoaErrorDomain Code=260
"The file couldn’t be opened because it doesn’t exist."
Simply passing the file path to the XPC process, by way of:
xpc_dictionary_set_string(message, "file_path", url.fileSystemRepresentation);
Does not grant the XPC create/write permissions. Is there an API or trick I'm missing?
Note that the user should be allowed to save and create new files anywhere of their choosing, thus restricting URLs to only those within a group or container shared between the app and service isn't really viable.
Using the latest of everything on macOS with the xpc_session API...
(Updated)
See replies below.
Filed as rdar://FB11975037
When macOS Ventura is run as a guest OS within the virtualization framework, the main menu bar items will not be displayed correctly if VZMacGraphicsDisplayConfiguration defines a large resolution.
The menu bar titles appear to be using the same color as the menu bar itself. When the Appearance is set to Light, the menu bar items are effectively invisible. When the Appearance is set to Dark, the menu bar items are drawn in what looks like a disabled state.
This only affects the menu bar item titles on the left-hand side. The date-time and menu bar icons on the right side are always displayed in the correct color.
This appears to be a regression in macOS Ventura as this issue is not present in macOS 12 running as a guest.
This bug can be easily reproduced using Apple's own Virtualization sample code titled: "Running macOS in a Virtual Machine on Apple Silicon Macs"
Steps to reproduce:
Follow the sample code instructions for building and installing a VM.bundle.
Before running 'macOSVirtualMachineSampleApp', change the VZMacGraphicsDisplayConfiguration to use:
width = 5120,
height = 2880,
ppi = 144.
Run 'macOSVirtualMachineSampleApp' and notice that the menu bar titles on the left side of the screen are not correctly drawn in the guest instance.
This has been tested on:
Host: macOS 13.1
Guest: macOS 13.x (All versions)
Hardware: MBP 14" M1 Pro 32GB/2TB
Is there anything that can be done to resolve this issue?
The new Virtualization framework (and sample code!) are great. It's a lot of fun to run the sample code and quickly fire up multiple VMs of macOS running as a guest.
However, the inability to authenticate with any iCloud services is a significant roadblock. Xcode, for example, is not allowing me to authenticate my developer account.
Are there any plans to resolve this issue so that iCloud accounts can be authenticated from within a VM?
In WWDC21 session 10233: Bring Encrypted Archives and Performance Improvements to Your App with Accelerate, there is an example of encrypting a directory using the AppleArchive framework. There is also accompanying sample code.
However, that sample code uses a SymmetricKey and the hkdf_sha256_aesctr_hmac__symmetric__none profile. The key is set by calling context.setSymmetricKey(encryptionKey).
How can you perform the same operation of encrypting a directory using AppleArchive but with a "human" password? (i.e.: A password provided by the user from a prompt?)
Simply changing the profile to hkdf_sha256_aesctr_hmac__scrypt__none and then calling `context.setPassword("MyPassword") producing the following output "Error setting password (invalidValue)."
I also tried using the command line aea application, but received the output Password is too short.
Prompt:
> aea encrypt -v -password-value "password" -profile 5 -i MyDirectory -o MyDirectory.aea
Operation: encrypt
input: FOO
output: FOO.aea
profile: hkdf_sha256_aesctr_hmac__scrypt__none
worker threads: 10
auth data (raw): 0 B
compression: lzfse 1 MB
Error 0xb9075800
Password is too short
Main key derivation failed (-2)
Main key derivation
Invalid encryption parameters
Finally, in the file AEAContext.h, there is a comment associated with the method AEAContextSetPassword() that states:
Set context password
Stores a copy of password in context. Required to
encrypt / decrypt a stream when encryption mode is SCRYPT.
An internal size range is enforced for the password.
The caller is expected to enforce password strength policies.
@param context target object
@param password password (raw data)
@param password_size password size (bytes)
@return 0 on success, a negative error code on failure
I cannot find any other documentation that states what the password policy. And if there is a password policy for AppleEncryptedArchives, does that mean AEA is not a good fit for encrypting personal directories where the user just wants to use "any old password", regardless of the password's strength?