I'm writing a screen saver using Cocoa's ScreenSaver API. It's compiled for 64-bit arch and I'm running it on Lion.
In order to enable configuration, I have added the following to the main view:
- (BOOL)hasConfigureSheet
{
return YES;
}
- (NSWindow*)configureSheet
{
if (configureSheet == nil) {
if (![NSBundle loadNibNamed: #"WTConfigureSheet" owner: self]) {
NSLog(#"Failed to load config sheet");
return nil;
}
}
ScreenSaverDefaults *defaults =
[ScreenSaverDefaults defaultsForModuleWithName: WTModuleName];
backgroundColorWell.color = [defaults objectForKey: #"BackgroundColor"];
lightLetterColorWell.color = [defaults objectForKey: #"LightLetterColor"];
darkLetterColorWell.color = [defaults objectForKey: #"DarkLetterColor"];
return configureSheet;
}
After installing the saver freshly, clicking "Options" makes the config sheet appear not as a sheet, but floating freely on the screen, without a border. Otherwise, it works correctly and disappears after being dismissed.
When I click "Options" a second time, the config sheet appears again, this time correctly as a sheet of the preferences window. It then immediately freezes, so that I can't click any of its controls.
Does anyone have an idea what causes this behavior?
I had the same problem as you today and it took me quite some time to figure this one out, so here's my solution:
I discovered that the NSWindow appears as soon as you call loadNibNamed:owner:. So there had to be some sort of mechanism to automatically open windows from nibs.
So I re-checked the nib and saw that there is an option called "Visible At Launch" on the attribute inspector pane which is checked by default.
The solution is very simple: just uncheck that checkbox and it works as expected.
I find it easy to overlook since you expect the window to open, but it actually opens twice (once automatically and a second time because System Preferences.app shows it as a sheet) which leads to the glitches.
Another problem that could happen, depending on how you defined the ivar / property on your class is that after the first close and re-open of the window it just freezes.
This is because per default the window releases itself when closed.
So be sure to also uncheck "Release When Closed" in interface builder.
For this code to work as written, you need to create an IBOutlet of type NSWindow* named configureSheet in your main view's header file, save that file so Interface Builder can see the change, then load WTConfigureSheet.xib in Interface Builder and connect up the toplevel window component to Files Owner -> configureSheet.
Related
In Document-Based Apps, using XIB files, when a new window is created its behaviour is:
Be positioned and sized based on the position of the last active
window.
If the last active window is still visible then the new
window should be cascaded so it doesn't directly overlap.
However when using a storyboard this isn't done. See test project.
You can set shouldCascadeWindows on the window controller in your storyboard:
select the window controller in the storyboard
select the identity inspector
add a new User Defined Runtime Attribute with these values:
key path: shouldCascadeWindows
Type: boolean
Value: checked
Update:
If you move the first window, new windows cascade starting in the middle of the screen not under the first window. To fix it:
select the window
in the attributes inspector give it an autosave name
This should also persist window location and size on the next window load and application launch.
Cascaded Windows Problem
One of the problems is that storyboards unlike xibs can include the NSWindowController and Interface Builder doesn't serialize it right.
-initWithWindow:, -initWithWindowNibName: and friends set shouldCascadeWindows to YES.
When a NSWindowController is loaded from a storyboard via -initWithCoder:, shouldCascadeWindows is NO. (OS X 10.11)
Based on my tests, this property needs to be set in the initializer. Setting it in -[NSDocument addWindowController:] didn't work. (OS X 10.11)
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self)
{
self.shouldCascadeWindows = YES;
}
return self;
}
See rdar://47350352
Window Position Problem
Using -[NSWindowController windowFrameAutosaveName] or -[NSWindow frameAutosaveName] seems only to work sometimes. Randomly it uses the initial window position.
Window Size Problem
Even if the cascaded window position is set right, it never set the size to the one saved for the frame. I verified the saved frame with defaults read window.autosavename.test1. Also before each test I run defaults delete window.autosavename.test1 for a clean state.
Workaround
Use a xib containing a empty NSWindow and add the view controllers from the storyboard in -[NSDocument windowControllerDidLoadNib: or -[NSDocument addWindowController:] to the window.
I think the answer might be that it's just not possible to have multiple windows share the same frameAutosaveName even though it is possible to have multiple NSSplitView share the same autosaveName.
I just tried creating another NSDocument based project, but this time I used xib's instead of a storyboard. The behavior is better (shouldCascadeWindows is on by default). But new window positioning still breaks down when multiple windows are involved.
I think this is more of a runtime constraint then it is storyboard vrs xib problem. Here's a test I just did in the default non storyboard NSDocument project generated by Xcode:
Set window autosave name in interface builder.
Modify windowControllerDidLoadNib to look like this:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
[super windowControllerDidLoadNib:aController];
NSLog(#"Listing frameAutosaveName for all windows:");
for (NSWindow *each in [NSApp windows]) {
NSLog(#"%#: %#", each.title, each.frameAutosaveName);
}
}
And then (after creating a number of windows) this is the output that I see:
Listing frameAutosaveName for all windows:
Untitled: SaveMe
Untitled 2:
Untitled 3:
Untitled 4:
Untitled 5:
Window:
So only the first window created gets the "SaveMe" autosave name. For all the following windows the value is never set.
My conclusion is that you just can't use frameAutosaveName to replicate Safari's behavior. Instead you must do something manual. I'm not sure if the method used in the example project is the best way, but I think at least some manual work is needed for Safari's behavior no matter if you are using xibs or storyboards.
I'm trying to set my NSWindow to be in the center of the screen, but I'm noticing that when I quit and reopen the app, it takes the position that the window was in when the app closed. Is this expected behavior?
If you selected "Restorable" window behaviour then it's the correct behaviour.
You can disable this by behaviour by unchecking restorable and also leave your autosave name empty.
Your application saves the state into "~/Library/Saved Application State/com.identifier.appName.savedState" folder and loads on launch
Also one hidden hack to help:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[[NSUserDefaults standardUserDefaults] setObject:#NO forKey:#"NSQuitAlwaysKeepsWindows"];
}
This is how I discovered the problem:
My app has a small dialog window with an ABPeoplePickerView. This window (or its controller) is correctly deallocated when finished with.
When the app terminates, in applicationShouldTerminate: I iterate through all its windows ([NSApp windows]) and if the window has a delegate and it responds to windowShouldClose: I call that method and if the response is NO, I stop the termination.
Logging each window's title revealed that each time I created my dialog window, two extra hidden windows were created, with titles: "People Picker Debug Info" and "People Picker Preview", and that these windows aren't disposed of when the dialog is closed. You can actually see these windows if you makeKeyAndOrderFront them. The latter is blank. A long run of the app could create a large number of these windows, presumably using up resources. Should I worry? Should I do anything about it? Could I make use of them?
My hunch is to search for these windows by title in my dialog's deallocate method, and close them there. Are there any ARC issues?
The following lines in the window controller's dealloc method have fixed the problem, with no side effects as far as I can tell.
for (NSWindow *w in [NSApp windows]) {
if ([w.title isEqualToString:#"People Picker Debug Info" ]) {
[w setReleasedWhenClosed:YES];
[w close];
}
if ([w.title isEqualToString:#"People Picker Preview" ]) {
[w setReleasedWhenClosed:YES];
[w close];
}
}
I have an application, which open popover with NSTextField. The text field is not editable. Behavior for text field is set to Editable. I still can paste and copy text to this field but i can't edit it.
Anyone knows, what can be wrong?
Not sure if you still need the answer, but there may be some others still looking. I found a solution on apple developer forums. Quoting the original author:
The main problem is the way keyboard events works. Although the NSTextField (and all its superviews) receives keyboard events, it doesn't make any action. That happens because the view where the popover is atached, is in a window which can't become a key window. You can't access that window in any way, at least I couldn't. So the solution is override the method canBecomeKeyWindow for every NSWindow in our application using a category.
NSWindow+canBecomeKeyWindow.h
#interface NSWindow (canBecomeKeyWindow)
#end
NSWindow+canBecomeKeyWindow.m
#implementation NSWindow (canBecomeKeyWindow)
//This is to fix a bug with 10.7 where an NSPopover with a text field cannot be edited if its parent window won't become key
//The pragma statements disable the corresponding warning for overriding an already-implemented method
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (BOOL)canBecomeKeyWindow
{
return YES;
}
#pragma clang diagnostic pop
#end
That makes the popover fully resposive. If you need another window which must respond NO to canBecomeKeyWindow, you can always make a subclass.
I struggled with this for a while as well, until I realized it was a bug.
However, instead of relying on an isActive state of a NSStatusItem view, I find it much more reliable to use the isShown property of the NSPopover you have implemented.
In my code, I have a NSPopover in a NSViewController:
- (BOOL)canBecomeKeyWindow
{
if([self class]==NSClassFromString(#"NSStatusBarWindow"))
{
NSPopover *mainPopover = [[((AppDelegate*)[NSApp delegate]) mainViewController] mainPopover];
if(![mainPopover isShown])
return NO;
}
return YES;
}
Balazs Toth's answer works, but if you're attaching the popover to NSStatusItem.view the status item becomes unresponsive - requiring two clicks to focus.
What i found when working with this solution is that when NSStatusItem becomes unresponsive, you can easily override this behavior like this
- (BOOL)canBecomeKeyWindow {
if([self class]==NSClassFromString(#"NSStatusBarWindow")) {
CBStatusBarView* view = [((CBAppDelegate*)[NSApp delegate]) statusItemView];
if(![view isActive]) return NO;
}
return YES;
}
You will check for the class of the window, if it matches the NSStatusBarWindow we can then check somehow if the NSStatusItem is active. If it is, that means we have to return YES, because this way the NSPopover from NSStatusItem will have all keyboard events.
What I'm using for checking if the NSStatusItem was clicked (or is active) is that in my own custom view i have a bool value which changes when user clicks on the NSStatusItem, system automatically checks for "canBecomeKeyWindow" and when it does it will return NO and after user clicks on it (while it is returning the NO) it will change the bool value and return YES when system asks again (when NSPopover is being clicked for NSTextField editing).
Sidenotes:
CBStatusBarView is my custom view for NSStatusItem
CBAppDelegate is my App Delegate class
If anyone is still looking for an answer to this, I am working in Swift.
At the time where you wish the field to allow text entry, I have used myTextField.becomeFirstReponder()
To opt out; just use myTextField.resignFirstResponder()
Definitely a bug. That bug report is exactly what I was trying to do. Even down to creating the status item and overriding mousdown.
I can confirm that Balazs Toth's answer works. I just wonder if it might get in the way down the road.
If someone gets it and the solution above didn't do the trick for him.
The problem in my app was in the info tab in the targets my application was set to
Application is background only = true
and shulde of been
Application is agent = true
Spent an entire day on this thing.
Bug. http://openradar.appspot.com/9722231
I need to create several windows before NSDocument is loaded, or create a window that blocks NSDocument window and top menu.
I tried several solutions - but they didn't work right.
modal window, one after another. there were some problems with Async URLConnection, and some other problems with my NSDocument content.
I created custom MainMenu.xib with no menu, that opens my preinitialize windows.
here i found some other problems, when a file(associated with my application) is opened - the Document Window initializes. Here i tried to subclass NSDocumentController, but i found no way to pause the "open document". (i want the document to be opened anyway, but only after the preinitalize windows would be closed).
So what is the right way to do this?
Implement applicationShouldOpenUntitledFile: in your app delegate to return NO if the user has to go through the not-registered-yet dialog first.
In the action methods for your “Trial” and “Confirm Registration” buttons, create the untitled document yourself (by sending the necessary message to the document controller).
So the right answer is to implement:
* application:openFiles:
* applicationShouldOpenUntitledFile:
and implement your own document creation. this is the way it worked for me.
MyDocument* document = [[MyDocument alloc]
initWithContentsOfURL:fileURL
ofType:[fileName pathExtension]
error:nil
];
if(document)
{
[[NSDocumentController sharedDocumentController] addDocument:document];
[document makeWindowControllers];
[document showWindows];
}
of course you need to write error handling code.