The following code only ever causes shouldDrawInsertionPoint to be printed (no drawInsertionPoint), but even if that method returns false, the blinking insertion point is still drawn. On the other hand, with TextKit 1 it works as expected.
Is there a way to hide the default insertion point in TextKit 2? My app draws its own.
I've filed FB13684251.
class TextView: NSTextView {
override var shouldDrawInsertionPoint: Bool {
print("shouldDrawInsertionPoint")
return false
}
override func drawInsertionPoint(in rect: NSRect, color: NSColor, turnedOn flag: Bool) {
print("drawInsertionPoint", flag)
}
}
``
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Apparently when setting a window to hide its title, the toolbar's displayMode is not restored when relaunching the app. For example, by default my app sets to show toolbar icons only, but when right-clicking it, selecting "Icon and Text" and relaunching the app, it's again "Icon Only".
Is there a workaround? I've filed FB17144212.
class ViewController: NSViewController, NSToolbarDelegate {
override func viewDidAppear() {
let toolbar = NSToolbar(identifier: "toolbar")
toolbar.delegate = self
toolbar.autosavesConfiguration = true
toolbar.displayMode = .iconOnly
view.window?.titleVisibility = .hidden
view.window?.toolbar = toolbar
view.window?.toolbarStyle = .unified
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.init(rawValue: "item")]
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [.init(rawValue: "item")]
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
let item = NSToolbarItem(itemIdentifier: itemIdentifier)
item.image = NSImage(named: NSImage.addTemplateName)!
item.label = "item"
return item
}
}
I would like to provide a default filename when saving a document depending on the document data. I thought I could do so by overriding NSDocument.prepareSavePanel(_:) and setting NSSavePanel.nameFieldStringValue, but simply implementing that method seems to hide the file format popup button shown by default (see image). Calling super doesn't help.
Is it possible to set a default filename and keep the file format popup button? On macOS 15, I can toggle NSSavePanel.showsContentTypes, but how about macOS 14 and older?
I had noticed an unsettling behaviour about NSDocument some years ago and created FB7392851, but the feedback didn't go forward, so I just updated it and hopefully here or there someone can explain what's going on.
When running a simple document-based app with a text view, what I type before closing the app may be discarded without notice. To reproduce it, you can use the code below, then:
Type "asdf" in the text view.
Wait until the Xcode console logs "saving". You can trigger it by switching to another app and back again.
Type something else in the text view, such as "asdf" on a new line.
Quit the app.
Relaunch the app. The second line has been discarded.
Am I doing something wrong or is this a bug? Is there a workaround?
class ViewController: NSViewController {
@IBOutlet var textView: NSTextView!
}
class Document: NSDocument {
private(set) var text = ""
override class var autosavesInPlace: Bool {
return true
}
override func makeWindowControllers() {
let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! NSWindowController
(windowController.contentViewController as? ViewController)?.textView.string = text
self.addWindowController(windowController)
}
override func data(ofType typeName: String) throws -> Data {
Swift.print("saving")
text = (windowControllers.first?.contentViewController as? ViewController)?.textView.string ?? ""
return Data(text.utf8)
}
override func read(from data: Data, ofType typeName: String) throws {
text = String(decoding: data, as: UTF8.self)
(windowControllers.first?.contentViewController as? ViewController)?.textView.string = text
}
}
In my app the user can select a source folder to be synced with a destination folder. The sync can also happen in response to a change in the source folder detected with FSEventStreamCreate.
If the user unzips an archive in the source folder and the sync process begins before the unzip operation has completed, the sync can fail because of a "Permission denied" error. I assume this is related to the posix permissions of the extracted folder being 420 during the unzip operation and (in my case) 511 afterwards.
Is there a way to detect than an unzip operation is in progress and wait until it has completed? I thought that using NSFileCoordinator would solve this issue, but unfortunately it's not the case. Since an unzip operation can last any amount of time, it's not ideal to just delay a sync by a fixed number of seconds and let the user deal with any error if the unzip operation takes longer.
let openPanel = NSOpenPanel()
openPanel.canChooseDirectories = true
if openPanel.runModal() == .cancel {
return
}
let url = openPanel.urls[0].appendingPathComponent("extracted", isDirectory: false)
var error: NSError?
NSFileCoordinator(filePresenter: nil).coordinate(readingItemAt: url, error: &error) { url in
do {
print(try FileManager.default.attributesOfItem(atPath: url.path).sorted(by: { $0.key.rawValue < $1.key.rawValue }).map({ ($0.key.rawValue, $0.value) }))
try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
} catch {
print(error)
}
}
if let error = error {
print("file coordinator error:", error)
}
The online documentation for fs_snapshot_create, which is on a website which apparently I'm not allowed to link to on this forum, mentions that some entitlement is necessary, but doesn't specify which one. Searching online I found someone mentioning com.apple.developer.vfs.snapshot, but when adding this to my entitlement file and building my Xcode project, I get the error
Provisioning profile "Mac Team Provisioning Profile: com.example.myApp" doesn't include the com.apple.developer.vfs.snapshot entitlement.
Searching some more online, I found someone mentioning that one has to request this entitlement from DTS. Is this true? I couldn't find any official documentation.
I actually want to make a snapshot of a user-selected directory so that my app can sync it to another volume while avoiding that the user makes changes during the sync process that would make the copy inconsistent. Would fs_snapshot_create be faster than traversing the chosen directory and creating clones of each nested file with filecopy and the flag COPYFILE_CLONE? Although I have the impression that only fs_snapshot_create could make a truly consistent snapshot.
In my app I have a background task performed on a custom DispatchQueue. When it has completed, I update the UI in DispatchQueue.main.async. In a particular case, the app then needs to show a modal window that contains a table view, but I have noticed that when scrolling through the tableview, it only responds very slowly.
It appears that this happens when the table view in the modal window is presented in DispatchQueue.main.async. Presenting it in perform(_:with:afterDelay:) or in a Timer.scheduledTimer(withTimeInterval:repeats:block:) on the other hand works. Why? This seems like an ugly workaround.
I created FB7448414 in November 2019 but got no response.
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let windowController = NSWindowController(window: NSWindow(contentViewController: ViewController()))
// 1. works
// runModal(for: windowController)
// 2. works
// perform(#selector(runModal), with: windowController, afterDelay: 0)
// 3. works
// Timer.scheduledTimer(withTimeInterval: 0, repeats: false) { [self] _ in
// self.runModal(for: windowController)
// }
// 4. doesn't work
DispatchQueue.main.async {
self.runModal(for: windowController)
}
}
@objc func runModal(for windowController: NSWindowController) {
NSApp.runModal(for: windowController.window!)
}
}
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
override func loadView() {
let tableView = NSTableView()
tableView.dataSource = self
tableView.delegate = self
tableView.addTableColumn(NSTableColumn())
let scrollView = NSScrollView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
scrollView.documentView = tableView
scrollView.hasVerticalScroller = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
view = scrollView
}
func numberOfRows(in tableView: NSTableView) -> Int {
return 100
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let view = NSTableCellView()
let textField = NSTextField(labelWithString: "\(row)")
textField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textField)
NSLayoutConstraint.activate([textField.leadingAnchor.constraint(equalTo: view.leadingAnchor), textField.trailingAnchor.constraint(equalTo: view.trailingAnchor), textField.topAnchor.constraint(equalTo: view.topAnchor), textField.bottomAnchor.constraint(equalTo: view.bottomAnchor)])
return view
}
}
When I connect to another Mac via Finder (using SMB), creating a hard link with FileManager.linkItem(atPath:toPath:) fails (both source and destination are on the remote Mac). I read online that SMB itself supports creating hard links, so is this a macOS limitation or bug?
Until now I was using FileManager.contentsEqual(atPath:andPath:) to compare file contents in my App Store app, but then a user reported that this operation is way slower than just copying the files (which I made faster a while ago, as explained in Making filecopy faster by changing block size).
I thought that maybe the FileManager implementation reads the two files with a small block size, so I implemented a custom comparison with the same block size I use for filecopy (as explained in the linked post), and it runs much faster. When using the code for testing repeatedly also found on that other post, this new implementation is about the same speed as FileManager for 1KB files, but runs 10-20x faster for 1MB files or bigger.
Feel free to comment on my implementation below.
extension FileManager {
func fastContentsEqual(atPath path1: String, andPath path2: String, progress: (_ delta: Int) -> Bool) -> Bool {
do {
let bufferSize = 16_777_216
let sourceDescriptor = open(path1, O_RDONLY | O_NOFOLLOW, 0)
if sourceDescriptor < 0 {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
let sourceFile = FileHandle(fileDescriptor: sourceDescriptor)
let destinationDescriptor = open(path2, O_RDONLY | O_NOFOLLOW, 0)
if destinationDescriptor < 0 {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
let destinationFile = FileHandle(fileDescriptor: destinationDescriptor)
var equal = true
while autoreleasepool(invoking: {
let sourceData = sourceFile.readData(ofLength: bufferSize)
let destinationData = destinationFile.readData(ofLength: bufferSize)
equal = sourceData == destinationData
return sourceData.count > 0 && progress(sourceData.count) && equal
}) { }
if close(sourceDescriptor) < 0 {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
if close(destinationDescriptor) < 0 {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
return equal
} catch {
return contentsEqual(atPath: path1, andPath: path2) // use this as a fallback for unsupported files (like symbolic links)
}
}
}
A user of my app reported that when trying to remove a file it always fails with the error "file couldn't be removed because you don't have permission to access it (Cocoa Error Domain 513)". After some testing, we found out that it's caused by trying to delete non-empty directories.
I'm using FileManager.removeItem(atPath:) which has worked fine for many years, but it seems that with their particular NAS, it doesn't work.
I could work around this by checking if the file is a directory, and if it is, enumerating the directory and remove each contained file before removing the directory itself. But shouldn't this already be taken care of? In the source code of FileManager I see that for Darwin platforms it calls
removefile(pathPtr, state, removefile_flags_t(REMOVEFILE_RECURSIVE))
so it seems that it should already work. Is the REMOVEFILE_RECURSIVE flag perhaps ignored by the device? But then, is the misleading "you don't have permission to access the file" error thrown by the device or by macOS?
For the FileManager source code, see https://github.com/swiftlang/swift-foundation/blob/1d5d70997410fc8b7700c8648b10d6fc28194202/Sources/FoundationEssentials/FileManager/FileOperations.swift#L444
A user of my AppKit, document-based app brought to my attention that when setting it as the default app to open a certain file with extension .md (by choosing in the Finder "File > Open With > Other", then selecting my app and enabling "Always open with"), trying to open it with a double-click displays the warning "Apple could not verify [file] is free of malware that may harm your mac or compromise your privacy".
This is what happens for me:
When keeping the default app for a .md file (Xcode in my case), the file opens just fine.
When choosing my app in the "File > Open With" menu, the file opens just fine in my app.
But when setting my app as the default app (see above), the warning is displayed.
From that moment on, choosing my app in the "File > Open With" menu doesn't work anymore. Selecting Xcode doesn't work either.
Only setting Xcode again as the default app allows me to open it in Xcode, but my app still isn't allowed to open it.
Is this a macOS issue, or can I do anything in my app to prevent it? Where should I start looking for the issue in my code?
A user of my app noticed that when using it to move a file to the trash on an USB drive, the trash doesn't show the file until unmounting the drive and mounting it again. I was able to reproduce it with one of my own USB drives, but with another USB drive it doesn't reproduce. All USB drives are formatted APFS.
When moving a file to the trash from the Finder, both USB drives immediately list it in the trash. Is this a macOS bug, or am I doing something wrong?
I created FB19941168.
let openPanel = NSOpenPanel()
openPanel.runModal()
let url = openPanel.urls[0]
do {
var result: NSURL?
try FileManager.default.trashItem(at: url, resultingItemURL: &result)
print(result as Any)
} catch {
fatalError(error.localizedDescription)
}
My app displays some text that should appear the same regardless of the container view or window size, i.e. it should grow and shrink with the container view or window.
On iOS there is UILabel.adjustsFontSizeToFitWidth but I couldn't find any equivalent API on macOS. On the internet some people suggest to iteratively set a smaller font size until the text fits the available space, but I thought there must be a more efficient solution. How does UILabel.adjustsFontSizeToFitWidth do it?
My expectation was that setting a font's size to a fraction of the window width or height would do the trick, but when resizing the window I can see a slightly different portion of it.
class ViewController: NSViewController {
override func loadView() {
view = MyView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
NSLayoutConstraint.activate([view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: 3), view.heightAnchor.constraint(greaterThanOrEqualToConstant: 100)])
}
}
class MyView: NSView {
let textField = NSTextField(labelWithString: String(repeating: "a b c d e f g h i j k l m n o p q r s t u v w x y z ", count: 2))
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
textField.translatesAutoresizingMaskIntoConstraints = false
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
addSubview(textField)
NSLayoutConstraint.activate([textField.topAnchor.constraint(equalTo: topAnchor), textField.leadingAnchor.constraint(equalTo: leadingAnchor), textField.trailingAnchor.constraint(equalTo: trailingAnchor)])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func resize(withOldSuperviewSize oldSize: NSSize) {
// textField.font = .systemFont(ofSize: frame.width * 0.05)
textField.font = .systemFont(ofSize: frame.height * 0.1)
}
}
A user of my app brought to my attention that unless they select their ~/Library/Mail folder explicitly in an open panel, they get an error when scanning it inside my app. I can confirm that I also get a permission error when trying to scan it as a subfolder of ~/Library, but not if I select it directly.
I'm assuming this is intentional, but it would be nice to have an explanation or some documentation that I can point my users to when they encounter what appears to them as a bug in my app. What makes this matter even more confusing is that selecting a folder in any open panel of an app gives the app access to it for the lifetime of the app, but after restarting the app, access is lost again (unless it has a bookmark to it). This was probably the reason why the user thought that it worked in another app but not in mine.
This is the code I use to scan:
let openPanel = NSOpenPanel()
openPanel.canChooseDirectories = true
if openPanel.runModal() == .cancel {
return
}
let enumerator = FileManager.default.enumerator(at: openPanel.urls[0], includingPropertiesForKeys: nil) { url, error in
print(url.path, error)
return true
}
while let url = enumerator?.nextObject() as? URL {
}
And this the error related to the Mail folder:
~/Library/Mail Error Domain=NSCocoaErrorDomain Code=257 "The file “Mail” couldn’t be opened because you don’t have permission to view it." UserInfo={NSURL=file:///~/Library/Mail, NSFilePath=/~/Library/Mail, NSUnderlyingError=0x600002991470 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}
In macOS 26 I noticed there is a section Menu Bar in System Settings which allows to toggle visibility of status items created with NSStatusItem. I'm assuming this is new, since I never noticed it before.
Currently my app has a menu item that allows toggling its status item, but now I wonder whether it should always create the status item and let the user control its visibility from System Settings. Theoretically, keeping this option inside the app could lead to confusion if the user has previously disabled the status item in System Settings, then perhaps forgot about it, and then tries to enable it inside the app, but apparently nothing happens because System Settings overrides the app setting. Should I remove the option inside the app?
This also makes me think of login items, which can be managed both in System Settings and inside the app via SMAppService. Some users ask why my app doesn't have a launch at login option, and I tell them that System Settings already offers that functionality. Since there is SMAppService I could offer an option inside the app that is kept in sync with System Settings, but I prefer to avoid duplicating functionality, particularly if it's something that is changed once by the user and then rarely (if ever) changed afterwards. But I wonder: why can login items be controlled by an app, and the status item cannot (at least I'm not aware of an API that allows to change the option in System Settings)? If the status item can be overridden in System Settings, why do login items behave differently?