Cocoa: NSApp beginSheet sets the application delegate? - cocoa

I am trying to display a custom sheet in my application, but I think I am doing something wrong. While everything seems to be working just fine, I have a rather odd side-effect. (which took hours to figure out). It turns out that everytime I display a sheet in my application, the Application delegate gets set to the instance of the sheet, thus my Controller gets unset as the delegate causing all sorts of problems.
I've created a NIB file which I called FailureSheet.xib. I laid out my interface in IB, and then created a subclass of 'NSWindowController' called 'FailureSheet.m' which I set to the File's Owner. Here is my FailureSheet class:
#import "FailureSheet.h"
#implementation FailureSheet // extends NSWindowController
- (id)init
{
if (self = [super initWithWindowNibName:#"FailureSheet" owner:self])
{
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (IBAction)closeSheetTryAgain:(id)sender
{
[NSApp endSheet:[self window] returnCode:1];
[[self window] orderOut:nil];
}
- (IBAction)closeSheetCancel:(id)sender
{
[NSApp endSheet:[self window] returnCode:0];
[[self window] orderOut:nil];
}
- (IBAction)closeSheetCancelAll:(id)sender
{
[NSApp endSheet:[self window] returnCode:-1];
[[self window] orderOut:nil];
}
#end
Nothing complex going on here. Now this is how I display the FailureSheet in my 'Controller' class:
sheet = [[FailureSheet alloc] init];
[NSApp beginSheet:[sheet window]
modalForWindow:window
modalDelegate:self
didEndSelector:#selector(failureSheetDidEnd:etc:etc:)
contextInfo:nil];
Now if I log what the [NSApp delegate] is before displaying my sheet, it is <Controller-0x012345> which is correct. Then, after running this code and my sheet is up, if I log it again it is <FailureSheet-0xABCDEF>.
Not sure what I'm doing wrong here - Any ideas?

This is one of those "I'm-an-idiot" answers.
Turns out I at some point I accidentally made a connection in my sheet's NIB file between the Application and the File's Owner (FailureSheet) setting it as the delegate. So, everytime it got loaded it overwrote the existing delegate connection I had in my MainMenu NIB file.

Related

NSWindow beginSheet completionHandler not called

I am showing a sheet within my main window. I present the sheet using this code:
AddContactWindowController *addContact = [[AddContactWindowController alloc] initWithWindowNibName:#"AddContactWindow"];
addContact.currentViewController = myView;
self.addWindowController = addContact;
[self.view.window beginSheet: addContact.window completionHandler:^(NSModalResponse returnCode) {
NSLog(#"completionHandler called");
}];
AddContactWindowController is a NSWindowController subclass. It has a view controller within it. Inside the view is a "close" button which invokes this:
[[[self view] window] close];
This does close the window, but the completionHandler from beginSheet is not invoked. This causes me problems down the road.
Is there any particular way we should close the NSWindow sheet for the completion handler to be successfully called? I've also tried [[[self view] window] orderOut:self] but that doesn't work either.
Thanks.
You will want to call -endSheet:returnCode: on your window, rather than just ordering it out.
You must properly finish the modal session.
I used to call - (void)performClose:(id)sender and stop the modal session in the delegate method.
- (void)windowWillClose:(NSNotification *)notification {
[NSApp stopModal];
}
But for a sheet, endSheet looks more appropriate.
self.addWindowController = addContact;
[self.view.window beginSheet:self.addWindowController.window];
...
...
[self.view.window endSheet:self.addWindowController.window];
self.addWindowController = nil

loading a borderless window from a xib with view

I am trying to load a borderless window from a .xib in my program. I can load a borderless window with by overriding [[[NSWindow]] initWithContentRect:styleMask:backing:defer:] as follows:
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag {
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
if (!self) {
return nil;
}
[self setOpaque:NO];
[self setHasShadow:YES];
[self setLevel:NSFloatingWindowLevel];
[self setBackgroundColor:[NSColor clearColor]];
[self setAlphaValue:1.0];
// Ignore events
[self setIgnoresMouseEvents:YES];
return self;
}
When another method which contains [self orderFront:self]; is called, a window shows. However, I have a separate .xib file with a window created that I want to show when this method is called. I have the file's owner set as NSApplication and the window itself is of the class that contains the aforementioned code. How do I, when I call the method with [self orderFront:self];, load the window in the xib and show it instead of this class creating a window?
If I understand what you're trying to do, you can use NSWindowController to load an NSWindow from a separate nib (or xib) file. Sub-class NSWindowController, and put your controller code in there. Create that object in the xib file and set that to be the file's owner. Link the NSWindow to the NSWindowController's delegate outlet.
Then it's as easy as:
NSWindowController * windowController = [[[YourWindowClass alloc] initWithWindowNibName:#"YourWindowClass"] autorelease];
NSWindow * sheet = [windowController window];

Access main window from a NSDocument class

I have a NSDocument class, where I'd need to access the main menu window, the one that gets opened when the app start. When I operate in that window from the app all seems to work, but when trying to do the same operations from readFromFileWrapper:ofType:error: the window I access seems to be nil. Why this happens?
EDIT: Some code which deals with this:
- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError **)outError
{
if([[NSFileManager alloc] fileExistsAtPath:[NSString stringWithFormat:#"%#/Project.plist",[[self fileURL] path]]]) {
NSLog(#"%#", [[self fileURL] path]);
NSDictionary *project = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:#"%#/Project.plist",[[self fileURL] path]]];
if([[project objectForKey:#"type"] isEqualToString:#"vote"]) {
[self openProject:[[self fileURL] path] type:#"vote"];
return YES;
} else if([[project objectForKey:#"type"] isEqualToString:#"quiz"]) {
[self openProject:[[self fileURL] path] type:#"quiz"];
return YES;
} else {
return NO;
}
} else {
return NO;
}
}
That is my readFromFileWrapper:ofType:error: method. Here is my openProject:type: method:
-(void)openProject:(NSString *)filepath type:(NSString *)type
{
NSLog(#"Opening project # %#",filepath);
NSLog(#"%#", [MainWindow description]);
[projectDesignerView setFrame:[[[[MainWindow contentView] subviews] objectAtIndex:0] frame]];
[projectDesignerToolbar setFrame:[MainWindow frame] display:FALSE];
[[MainWindow contentView] replaceSubview:[[[MainWindow contentView] subviews]objectAtIndex:0] with:projectDesignerView];
[[projectDesignerToolbar toolbar] setShowsBaselineSeparator:YES];
[MainWindow setToolbar:[projectDesignerToolbar toolbar]];
[MainWindow setRepresentedFilename:filepath];
[MainWindow setTitle:[NSString stringWithFormat:#"%# - %#", [[filepath lastPathComponent] stringByDeletingPathExtension], [projectDesignerToolbar title]]];
NSString *path = [[NSBundle mainBundle] pathForResource:#"projectDesigner" ofType:#"html"];
NSURL *url = [NSURL fileURLWithPath:path];
[[projectDesignerWebview mainFrame] loadRequest:[NSURLRequest requestWithURL:url]];
}
NSLog(#"%#", [MainWindow description]); returns nil, when MainWindow should be the Main App Window. I think the problem is that double-clicking on a file reallocs all, and hence is failing.
It's not entirely clear what you're asking. You mention that MainWindow is an outlet in MainMenu.xib but you don't specify what class is defining the outlet.
If this window is designed to have a single main "project" window then you should assign the outlet property in your application delegate.
You can then access this from other classes using something like [(YourAppDelegate*)[NSApp delegate] mainWindow];.
If, however, you are trying to obtain a reference to the window of the current document then it's a little bit more complicated.
The reason that NSDocument does not have a window outlet by default is that it is designed to work with instances of NSWindowController that themselves manage the various windows related to the document. This is so a document can have multiple windows showing different views of the same data, additional palettes related to the document and so on. Each instance of NSWindowController would have its own window nib file and window outlet.
By default, NSDocument creates a single instance of NSWindowController for you if you do not specifically create and assign NSWindowController instances to the document. This is automatic, you don't need to even know the window controller exists.
That means that if you aren't managing your document windows with NSWindowController instances yourself, you can get the window attached to the NSWindowController that is automatically-created by NSDocument like so:
/* Only implement this in an NSDocument instance where the
automatic window controller is being used.
If the document has multiple window controllers, you must
keep track of the main window controller yourself
and return its window
*/
- (NSWindow*)documentWindow
{
if([[self windowControllers] count] == 1)
{
return [[[self windowControllers] firstObject] window];
}
return nil;
}
The normal way to handle this is to add an IBOutlet to your NSDocument subclass, then hook it up to the document window in the .xib file.
In your .h:
#interface MyDocument : NSDocument
#property (nonatomic, assign) IBOutlet NSWindow *docWindow;
#end
In your .m:
#implementation MyDocument : NSDocument
#synthesize docWindow;
#end
Then, the most important part, open up MyDocument.xib (or whatever it's called), and drag a connection from File's Owner (assuming that's your NSDocument subclass, which it is by default) to the main document window, and hook it up to the docWindow outlet.

makeKeyAndOrderFront only does the latter

I am trying to open one window from another using makeKeyAndOrderFront. The new window appears, but does not receive focus.
The code for the main window is:
#import "SecondWindowController.h"
#implementation FirstWindowController
-(IBAction)showSecondWindow:(id)sender
{
if (!secondWindowController)
secondWindowController = [[SecondWindowController alloc] init];
[[secondWindowController window] makeKeyAndOrderFront:self];
}
SecondWindowController is a NSWindowController, as follows:
#implementation SecondWindowController
-(id)init
{
if (![super initWithWindowNibName:#"SecondWindow"])
return nil;
return self;
}
I've also tried putting [secondWindowController showWindow:self] before the makeKeyAndOrderFront but it doesn't make a difference.
Did you make sure the window outlet for SecondWindowController is hooked up to the window in your NIB? The window could be displayed just by loading the NIB, even if the outlet is not hooked up.
Are you using a borderless window? If so you need to override canBecomeKeyWindow and return YES
Try this:
if (!secondWindowController)
secondWindowController = [[SecondWindowController alloc] init];
NSApplication *thisApp = [NSApplication sharedApplication];
[thisApp activateIgnoringOtherApps:YES];
[[secondWindowController window] makeKeyAndOrderFront:self];

Multiple custom controls that use mouseMoved in one window

At first I had one window with my custom control. To get it to accept the mouse moved events I simply put in it's awakeFromNib:
Code:
[[self window] makeFirstResponder:self];
[[self window] setAcceptsMouseMovedEvents:YES];
Now I'm doing something with four of them in the same window, and this doesn't work so pretty anymore. First off, I took them out of the control's awakeFromNib and decided I'd use my appController to manage it i.e. [window makeFirstResponder:View]
My question is, how do I manage four of these in the same view if I want each one to respond to mouse moved events? Right now, I've told the window to respond to mouseMoved events but none of the views are responding to mouseMoved.
You will also need to override -acceptsFirstResponder to return YES.
#pragma mark NSResponder Overrides
- (BOOL)acceptsFirstResponder
{
return YES;
}
-mouseMoved events are expensive so I turn off mouse moved events when my control's -mouseExited message is called and I turn it on in -mouseEntered.
- (void)mouseEntered:(NSEvent *)theEvent
{
[[self window] setAcceptsMouseMovedEvents:YES];
[[self window] makeFirstResponder:self];
}
- (void)mouseMoved:(NSEvent *)theEvent
{
...
}
- (void)mouseExited:(NSEvent *)theEvent
{
[[self window] setAcceptsMouseMovedEvents:NO];
}
I quickly tested this in my custom control application. I duplicated the control several times in the nib file and it worked as expected.
You may also need:
- (void)awakeFromNib
{
[[self window] setAcceptsMouseMovedEvents:YES];
[self addTrackingRect:[self bounds] owner:self userData:NULL assumeInside:YES];
}
I don't think the -setAcceptsMouseMovedEvents is necessary, but I'm pretty sure the tracking rect code is. You may also need to experiment with the value of the assumeInside: parameter, but that is documented.

Resources