I am trying to wrap my head around proper lifecycles of NSWindows and how to handle them properly.
I have the default macOS app template with a ViewController inside a window that is inside a Window Controller.
I also create a simple programatic window in applicationDidFinishLaunching like this:
let dummyWindow = CustomWindow(contentRect: .init(origin: .zero, size: .init(width: 200, height: 100)), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: true)
dummyWindow.title = "Code window"
dummyWindow.makeKeyAndOrderFront(nil)
The CustomWindow class is just:
class CustomWindow: NSWindow {
deinit {
print("Deinitializing window...")
}
}
When I close the programatic window (either by calling .close() or by just tapping the red close button, the app crashes with EXC_BAD_ACCESS. Even though I am not accessing the window in any way.
One might think it's because of ARC but it's not. One—the window is still strongly referenced by NSApplication.shared.windows even when the local scope of applicationDidFinishLaunching ends. And two—the "Deinitializing window..." is only printed after the window is closed.
Closing the Interface Builder window works without any crashes. I dug deep and played with the isReleasedWhenClosed property. It made no difference whether it was false or true for the IB window. It stopped the crashing for the programmatic window though.
But this raises three questions:
What is accessing the programatic window after it's closed—causing a crash because the default behaviour of NSWindow is to release it—if it's not my code?
What is the difference under the hood between a normal window and a window inside a window controller that prevents these crashes?
If the recommended approach for programmatic windows is to always set isReleasedWhenClosed = true then how do you actually release a programatic window so that it does not linger in memory indefinetely?
If the EXC_BAD_ACCESS means that an object is double de-allocated then that would mean that .close() both releases the window (first release) and removes it from the window list which would mean last strong reference is released and ARC cleans the window out (second release).
The theory is supported by me calling .orderOut() instead of close which only removes it from the application list and that does indeed release it without crash. Does this mean programmatic windows should override the close() instance method to call orderOut() instead?
This seems like poor API design or I am understanding it wrong?
AppKit
RSS for tagConstruct and manage a graphical, event-driven user interface for your macOS app using AppKit.
Post
Replies
Boosts
Views
Activity
Suppose we have a nstableview(view based) with automatic row heights enabled. Also each cell view is very dynamic with a lot of elements including text views, buttons and custom views.
The enclosingScrollView for the tableview has vertical elasticity.
In this case, there is flicker occurring when we pull down or pull up in the tableview. The more dynamic the cells are the greater the amount of flicker.
I suspect this would be the cause, Since this did not occur when building with xcode 14 macOS Ventura, There are some changes that have been said to be made in WWDC 2023 regarding automatic row heights in macOS Sonoma - https://developer.apple.com/videos/play/wwdc2023/10054/
Kindly suggest fixes or alternative ways to achieve smoother performance.
Hello!
In our codebase we have a NSView subclass that conforms to NSTextInputClient. This protocol is currently not marked as a main actor, but in the decade this has been in use here it has always been called on the main thread from AppKit.
With Swift 6 (or complete concurrency checking in Swift 5) this conformance causes issues since NSView is a main actor but not this protocol. I've tried a few of the usual fixes (MainActor.assumeIsolated or prefixing the protocol conformance with @preconcurrency) but they were not able to resolve all warnings.
So I dug in the AppKit headers and found that NSTextInputClient is usually implemented by the view itself, but that that is not a hard requirement (see NSTextInputContext.h the documentation for the client property or here). With that I turned my NSView subclass extension into a separate class that is not a main actor and in my NSView subclass create an instance of it and NSTextInputContext. This all seems to work fine in my initial tests, the delegate methods are called. But when the window loses and then regains key, I see a warning message in the console output.
-[TUINSCursorUIController activate:]: Foo.TextInputClient isn't subclass of NSView.
So my question is, am I doing it wrong with the custom class that implements the protocol? Or is the warning wrong?
I would also appreciate a hint on how to better resolve the concurrency issues with NSTextInputClient. Is a main actor annotation coming at some point from your end?
Thanks!
Markus
This function on NSTextLayoutManager has the following signature
func enumerateTextSegments(
in textRange: NSTextRange,
type: NSTextLayoutManager.SegmentType,
options: NSTextLayoutManager.SegmentOptions = [],
using block: (NSTextRange?, CGRect, CGFloat, NSTextContainer) -> Bool
)
The documentation here doesn't define what the CGRect and CGFloat passed to block are. However, looking at sample code Using TextKit2 To Interact With Text, they seem to be the frame for the textsegment and baselineposition respectively.
But, the textSegmentFrame seems to start at origin.x = 5.0 when text is empty. Is this some starting offset for text segments? I don't seem to be able to find mention of this anywhere.
With my continued experiments with TextKit2, I'm trying to figure out what sets the properties paragraphSeparatorRange and paragraphContentRange on a NSTextParagraph. These seem to be computed properties (or at least they cannot be directly set via public API).
var paragraph = NSTextParagraph(attributedString: NSAttributedString(string: "It was the best of times.\n"))
print("attributes: \(paragraph.attributedString.attributes(at: 0, effectiveRange: nil))")
print("paragraphSeparatorRange: \(String(describing: paragraph.paragraphSeparatorRange))")
print("paragraphContentRange: \(String(describing: paragraph.paragraphContentRange))")
In the above example, both paragraphSeparatorRange and paragraphContentRange are nil.
However, when using NSTextLayoutManager/NSTextContentStorage they are set.
let layoutManager = NSTextLayoutManager()
let container = NSTextContainer(size: NSSize(width: 400, height: 400))
layoutManager.textContainer = container
let contentStorage = NSTextContentStorage()
contentStorage.textStorage = NSTextStorage(string: "It was the best of times.\n")
contentStorage.addTextLayoutManager(layoutManager)
layoutManager.enumerateTextLayoutFragments(from: contentStorage.documentRange.location, options: .ensuresLayout) { textLayoutFragment in
print("layoutFragment: \(textLayoutFragment)")
print("textElement: \(String(describing: textLayoutFragment.textElement))")
print("textElement.range: \(String(describing: textLayoutFragment.textElement?.elementRange))")
let paragraph = textLayoutFragment.textElement as! NSTextParagraph
print("paragraphContentRange: \(String(describing: paragraph.paragraphContentRange))")
print("paragraphSeparatorRange: \(String(describing: paragraph.paragraphSeparatorRange))")
return true
}
Would appreciate any ideas on how these values are computed/set. Thanks
Hi, i have an old popover status bar mac app that i didn't check in the past few macOS releases, so don't know when the problem appeared. On Sonoma i have the following problem:
All NSTableViews cells when they are reused the second time, they are blank. The cells are of type view and only one column. If i investigate in the Xcode's view hierarchy I indeed see no subviews in the cell, but if i print the subviews and their frames and their visibility, they are there alright, they exist.
All the cells are instantiated from nib and I tried few strategies to use them:
register the cell in the tableview and load it with tableView.makeView(withIdentifier: )
no registration, just load them from xib and keep a reference to their instance and feed that to the table when asked
no registration, load them from xib when the table asks. This solution actually works but i really need a reference to them that doesn't change, it's a settings screen and someone else is feeding some info to this cells.
I'm stuck, I have no idea what is happening, can you think of something? Thanks!
Is there a way to opt certain windows out of tiling in macOS 15?
I'm supporting a beloved feature called App Veil[1], which places windows below others that are not owned by the host application. These windows are passive: they don't allow mouse events and cannot be resized or moved.
With the new tiling feature on macOS 15, the window manager will rearrange these windows if you choose an arrangement option (such as Arrange Left & Right).
Ideally, I'd like to opt out of this behavior altogether for these windows, but I couldn't find a way to do that. The only thing I've found was that I could set a high window level, but that's not really an option as it's important to preserve the ordering so that the windows are directly below the out-of-process windows.
1: https://tuple.app/app-veil
//
// AppDelegate.swift
// HelloCocoa
//
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let myAlert = NSAlert()
myAlert.messageText = "Alert Title"
let messageAttributedString = NSAttributedString(string: "Hello,world", attributes: [.font : NSFont.systemFont(ofSize: 12, weight: .bold)])
let myTextField = NSTextField(labelWithAttributedString: messageAttributedString)
myTextField.allowsEditingTextAttributes = true
myTextField.isSelectable = true
myAlert.accessoryView = myTextField
myAlert.runModal()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
The alert appears like this:
but when I clicks on the textfield, the text's color become black:
Adding foregroundColor key to attribute dictionary works for me but I really want to know why NSTextfield has such behavior
Hi,
I just did a NSTextField with auto complete that show a cities menu bar
the problem is when I typing the first letter, the focus change to the menubar and if I try to write another letter it just replace the first letter and not add it how can I fix it?
Thank you!
class NSTextFieldAuto: NSTextField, NSMenuDelegate, NSTextFieldDelegate {
var suggestionMenu: NSMenu?
var selectedSuggestionIndex: Int?
override func awakeFromNib() {
super.awakeFromNib()
self.delegate = self
self.suggestionMenu = NSMenu()
self.suggestionMenu?.delegate = self
}
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
// Deselect all text when the text field is clicked
}
override func textDidChange(_ notification: Notification) {
super.textDidChange(notification)
print("Still typing")
updateSuggestionMenu()
showSuggestionMenu()
}
override func textDidEndEditing(_ notification: Notification) {
print("Finished typing")
cityDef.setValue(self.stringValue, forKey: cityDefKey)
}
func updateSuggestionMenu() {
self.suggestionMenu?.removeAllItems()
let searchText = self.stringValue.lowercased()
for (index, city) in allCities.enumerated() {
if city.lowercased().hasPrefix(searchText) {
let item = NSMenuItem(title: city, action: #selector(selectSuggestion(_:)), keyEquivalent: "")
item.target = self
self.suggestionMenu?.addItem(item)
}
}
}
func showSuggestionMenu() {
if let menu = self.suggestionMenu, !menu.items.isEmpty {
let textFieldRect = self.bounds
let origin = NSPoint(x: textFieldRect.origin.x + 100.0, y: textFieldRect.origin.y + textFieldRect.size.height)
menu.autoenablesItems = false
menu.popUp(positioning: nil, at: origin, in: self)
}
}
@objc func selectSuggestion(_ sender: NSMenuItem) {
self.stringValue = sender.title
}
override func cancelOperation(_ sender: Any?) {
self.suggestionMenu?.cancelTracking()
self.window?.becomeFirstResponder()
}
func menuDidClose(_ menu: NSMenu) {
self.window?.makeFirstResponder(self) // Set text field as first responder again
print("Close menu")
}
override func becomeFirstResponder() -> Bool {
let result = super.becomeFirstResponder()
self.selectedSuggestionIndex = nil // Reset selected suggestion index
return result
}
}
A user of my app, which shows subtitles loaded from a text file above a video loaded from another file, reported that it crashes within minutes of launching it. The user confirmed that the crash only happens when they load a video within the app; if they use it without a video, it doesn't crash. The subtitles are shown in a NSTextView added to AVPlayerView.contentOverlayView.
What could cause such a crash? I'm not able to reproduce it.
(I tried attaching the full crash report but I always got a validation error "This post contains sensitive language. Please revise it in order to continue.")
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x000000019c50cae8
Termination Reason: Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process: exc handler [20614]
Application Specific Backtrace 0:
0 CoreFoundation 0x0000000198a472ec __exceptionPreprocess + 176
1 libobjc.A.dylib 0x000000019852e788 objc_exception_throw + 60
2 Foundation 0x0000000199b2caa0 -[NSKeyValueNestedProperty object:withObservance:didChangeValueForKeyOrKeys:recurse:forwardingValues:] + 664
3 Foundation 0x0000000199af4e08 -[NSKeyValueUnnestedProperty object:withObservance:didChangeValueForKeyOrKeys:recurse:forwardingValues:] + 196
4 Foundation 0x0000000199b8bc54 NSKeyValueDidChange + 200
5 Foundation 0x0000000199acde1c -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 684
6 Foundation 0x0000000199af7484 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 64
7 Foundation 0x0000000199b110e8 _NSSetObjectValueAndNotify + 284
8 AVKit 0x00000001bd3ff694 -[AVPlayerControlsViewController setPlayerController:] + 376
9 Foundation 0x0000000199acddd0 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] + 608
10 Foundation 0x0000000199af7484 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 64
11 Foundation 0x0000000199b110e8 _NSSetObjectValueAndNotify + 284
12 AVKit 0x00000001bd3c6e68 -[AVPlayerView setPlaybackControlsViewController:] + 88
13 Foundation 0x0000000199b11074 _NSSetObjectValueAndNotify + 168
14 AVKit 0x00000001bd40f4b8 -[AVPlayerView _updatePlaybackControlsViewControllerIfNeeded] + 536
15 AVKit 0x00000001bd3cc0b4 -[AVPlayerView viewDidMoveToWindow] + 136
16 AppKit 0x000000019c24456c -[NSView _setWindow:] + 1788
17 AppKit 0x000000019ccce4a0 __21-[NSView _setWindow:]_block_invoke.146 + 268
18 AppKit 0x000000019c244564 -[NSView _setWindow:] + 1780
19 AppKit 0x000000019ccce4a0 __21-[NSView _setWindow:]_block_invoke.146 + 268
20 AppKit 0x000000019c244564 -[NSView _setWindow:] + 1780
...
35 AppKit 0x000000019ccce4a0 __21-[NSView _setWindow:]_block_invoke.146 + 268
36 AppKit 0x000000019c244564 -[NSView _setWindow:] + 1780
37 AppKit 0x000000019c428ec0 -[NSWindow dealloc] + 684
38 Foundation 0x000000019a1fa118 _NSKVOPerformWithDeallocatingObservable + 172
39 Foundation 0x0000000199b17404 NSKVODeallocate + 180
40 Foundation 0x0000000199b122f0 empty + 88
41 Foundation 0x0000000199af77fc dealloc + 60
42 Foundation 0x0000000199af7740 -[NSConcreteMapTable dealloc] + 76
43 AppKit 0x000000019c936e80 ___NSTouchBarFinderSetNeedsUpdateOnMain_block_invoke_2 + 1388
44 AppKit 0x000000019c2d4c4c NSDisplayCycleObserverInvoke + 168
45 AppKit 0x000000019c2d48a8 NSDisplayCycleFlush + 644
46 QuartzCore 0x00000001a0bc3f64 _ZN2CA11Transaction19run_commit_handlersE18CATransactionPhase + 120
47 QuartzCore 0x00000001a0bc2d04 _ZN2CA11Transaction6commitEv + 320
48 AppKit 0x000000019c3589d0 __62+[CATransaction(NSCATransaction) NS_setFlushesWithDisplayLink]_block_invoke + 272
49 AppKit 0x000000019cd18208 ___NSRunLoopObserverCreateWithHandler_block_invoke + 64
50 CoreFoundation 0x00000001989d187c __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36
51 CoreFoundation 0x00000001989d1768 __CFRunLoopDoObservers + 536
52 CoreFoundation 0x00000001989d0d94 __CFRunLoopRun + 776
53 CoreFoundation 0x00000001989d0434 CFRunLoopRunSpecific + 608
54 HIToolbox 0x00000001a317419c RunCurrentEventLoopInMode + 292
55 HIToolbox 0x00000001a3173e2c ReceiveNextEventCommon + 220
...
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 AppKit 0x19c50cae8 -[NSApplication _crashOnException:] + 240
1 AppKit 0x19c358b44 __62+[CATransaction(NSCATransaction) NS_setFlushesWithDisplayLink]_block_invoke + 644
2 AppKit 0x19cd18208 ___NSRunLoopObserverCreateWithHandler_block_invoke + 64
3 CoreFoundation 0x1989d187c __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36
4 CoreFoundation 0x1989d1768 __CFRunLoopDoObservers + 536
5 CoreFoundation 0x1989d0d94 __CFRunLoopRun + 776
6 CoreFoundation 0x1989d0434 CFRunLoopRunSpecific + 608
7 HIToolbox 0x1a317419c RunCurrentEventLoopInMode + 292
8 HIToolbox 0x1a3173e2c ReceiveNextEventCommon + 220
9 HIToolbox 0x1a3173d30 _BlockUntilNextEventMatchingListInModeWithFilter + 76
10 AppKit 0x19c22fd68 _DPSNextEvent + 660
11 AppKit 0x19ca25808 -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 700
12 AppKit 0x19c22309c -[NSApplication run] + 476
13 AppKit 0x19c1fa2e0 NSApplicationMain + 880
14 Underword 0x102c099ac 0x102c08000 + 6572
15 dyld 0x19856a0e0 start + 2360
I just noticed that when closing a new document with edits in MacOS Sonoma that it skips the Save/Don't Save/Cancel panel and goes directly to default NSSavePanel with Delete/Cancel/Save buttons. The problem is that when I click "Delete" nothing happens. It should have simple solution, but I could not find anything. How does one respond to the "Delete" button?
My undocumented (as far as I can tell) hack was to implement
document:didSave:contextinfo
selector for runModalSavePanelForSaveOperation. It appears that in this method for a new document:
Delete button has didSave=YES (even though it did not save) and the document fileURL nil
Cancel button has didSave=NO and document fileURL nil
Save button has didSave=YES and document filieURL to saved file
I can handle Delete button this way, but since it is not a documented method, it make me uncomfortable. For example what happens is user clicks "Save", but the save has an error?
As an aside, since Apple is now working with ChatGPT, I thought it might provide some help. I asked it how I can respond to "Delete" button in MacOS Sonoma and it said to implement deleteDocument: in your NSDocument subclass.
I pointed out to ChatGPT that deleteDocument: does not exist. It said "you are correct" and you should instead check the returned result from runModalSavePanelForSaveOperation and look for "stop" action.
I pointed out to ChatGPT that runModalSavePanelForSaveOperation is void and does not return a result, it said again, "you are correct." It gave a third option which basically said to override runModalSavePanelForSaveOperation and build your own save panel from scratch. I didn't know if I should trust this answer. I reverted to my hack and wrote this post.
Also ChatGPT never apologized for wasting my time with the wrong answers.
Hello everybody! How to resize a window using a keyboard shortcut in MacOS Sequoia. Please if who know help me!
Our app exports a number of file types in info.plist. For each of these file types, we export various possible file name extensions. For example, for one of our file types, we specify the possible extensions ".data", ".fitdat", and ".profitdata".
In the settings of our app, we allow the user to select their preferred extension to be used, i.e. the extension to be used when, e.g., saving a document with "Save As..." panel without explicitly specifying the extension.
For this, we override NSDocument's fileNameExtensionForType:saveOperation to return the presently preferred extension.
This has stopped working, probably starting from macOS 14. If the user does not specify the extension in the NSSavePanel, it's always the first extension (".data") that gets added to the file name.
I guess it's a consequence of the introduction of UTType, which has its own preferredFilenameExtension, which in turn probably just grabs the first extension we specify in our info.plist
Any advice how to resolve this? Is there any way to override NSSavePanel's selection of extension if the user does not specify one?
Thanks for any advice in advance
Kurt
Is there any way to tell if the user clicked X button in windowWillClose delegate method?
- (void)windowWillClose:(NSNotification *)notification
I'm seeing many apps, when restoring their state from a previous launch, have trouble opening windows to the space and minimized state they were last.
What do I need to do so my AppKit app doesn't have this problem?
I saw this thread https://forums.developer.apple.com/forums/thread/69888 and I need to not open a new default window just because the app is brought to the front without having any windows open. Unfortunately, that isn't working any more under Sonoma 14.5.
Here's my code:
// Let's not open default window just because we're being brought to the front.
-(BOOL)applicationShouldHandleReopen
{
return NO;
}
-(BOOL)applicationShouldOpenUntitledFile
{
return NO;
}
I have breakpoints set on both return statements, but they are never hit.
I can't get CoreImage to render an HDR image file with correct colors to a CAMetalLayer on macOS 14. I'm comparing the result with NSImageView and the SupportingHDRImagesInYourApp 'HDRDemo23' sample code, which use CVPixelBuffer. With CAMetalLayer, the images are displayed as HDR (definitely more highlights), but they're darker with some kind saturation increase & color shift.
Files I've tested include the sample ISO HDR files in the SupportingHDRImagesInYourApp sample code. Methods I've tried to render to CAMetalLayer include:
Modifying the GeneratingAnAnimationWithACoreImageRenderDestination sample code's ContentView so it uses HDRDemo23/example-ISO-HDR-images/image_01.heic, loaded using CIImage(contentsOf:)
Creating a test AppKit app that uses MTKView and CIRenderDestination the same way. I have NSImageView and the MTKView in the same window for comparison.
Using CIRAWFilter > CIRenderDestination > IOSurface > MTKView/CAMetalLayer
All these methods produce the image with the exact same appearance; a dark HDR image with some saturation/color shift.
The only clue I've found is that when using the Metal Debugger on the test AppKit app, the CAMetalLayer's 'Present' shows the 'input' thumbnail is HDR without the color shift, but the 'output' thumbnail looks like what I actually see. I tried changing the color profile on the layer to various things but nothing looked more correct.
I've tried this on two Macs, an M1 Mac Studio with an LG display, and a MacBook Air M2. The MacBook Air shows the same color shift, but since it has less dynamic range overall there isn't as much difference between NSImageView and MTKView.
I have the following code:
+ (BOOL)activateWindow:(NSWindow*)window
{
if (NSApp.activationPolicy != NSApplicationActivationPolicyRegular)
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
if (window)
{
[NSApp activate];
//if (window.isMiniaturized) [window deminiaturize:nil];
[window makeKeyAndOrderFront:nil];
}
return YES;
}
+ (BOOL)hideWindowFromDock:(NSWindow*)window
{
if (NSApp.activationPolicy != NSApplicationActivationPolicyProhibited)
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
window.isVisible = NO;
return YES;
}
I hide app main window by setting NSApplicationActivationPolicyProhibited if it is minimized or being closed.
The code worked most of the time. But since upgrading to Sonoma, sometimes it won't correctly activate main window. The window icon reappears in the dock, but the window won't show up. I have to click on the window icon again to let it 'order front'.
Sometimes, I observe a very weird behavior of the window being activated. It 'order front' and then disappears and re-appears.
We would like to be able to modify the display name of our app in the dock and finder (etc) but not change the name of the .app bundle.
We've tried modifying CFBundleName and CFBundleDisplayName in Info.plist, but this doesn't seem to have an effect.
Is there any way to have the displayed name be different from the base name of the app bundle? We want this to apply for all languages.
Thanks!
When running my app from Xcode Umlauts are all of a sudden broken in several places. The app has been out in the public w/o similar issues for years.
For example, this is the result when entering a filename in my NSDocument-based app:
So far I could only observe the bug when launching under Sonoma from Xcode but I'm worried this might be a general issue for users of the app running the release build.
Any ideas about what happened in macOS 14.4..?
Cheers,
Jay