Access main window from a NSDocument class - macos

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.

Related

Open NSWindowController from NSMenu

I'm with a NSMenu in an agent application (without the icon in the dock). When a button from this menu is tapped, I want to show a generic NSWindowController.
My menu button action:
- (IBAction)menuButtonTapped:(id)sender {
MyWindowController *myWindow = [[MyWindowController alloc] initWithWindowNibName:#"MyWindowController"];
[myWindow showWindow:nil];
[[myWindow window] makeMainWindow];
}
But the window just "flashes" in the screen (it shows and disappears really fast).
Any solution?
The reason the window is showing up for a split second and then disappearing has to do with ARC and how you go about creating the instance of the window controller:
- (IBAction)menuButtonTapped:(id)sender {
MyWindowController *myWindow = [[MyWindowController alloc]
initWithWindowNibName:#"MyWindowController"];
[myWindow showWindow:nil];
[[myWindow window] makeMainWindow];
}
Under ARC, the myWindow instance will be valid for the scope where it is defined. In other words, after the last [[myWindow window] makeMainWindow]; line is reached and run, the window controller will be released and deallocated, and as a result, its window will be removed from the screen.
Generally speaking, for items or objects you create that you want to "stick around", you should define them as an instance variable with a strong property.
For example, your .h would look something like this:
#class MyWindowController;
#interface MDAppController : NSObject
#property (nonatomic, strong) MyWindowController *windowController;
#end
And the revised menuButtonTapped: method would look something like this:
- (IBAction)menuButtonTapped:(id)sender {
if (self.windowController == nil) {
self.windowController = [[MyWindowController alloc]
initWithWindowNibName:#"MyWindowController"];
}
[self.windowController showWindow:nil];
}
Use this:
[[myWindow window] makeKeyAndOrderFront:self];

How to add custom NSView to Window

I know how to do this in iOS but cannot figure it how to it in Cocoa.
I want to capture keyboard events and I think I need to override the acceptsFirstResponder method to accomplish that (keyDown method being triggered). So I created a class extending NSCustomView and tried to add it in the main Window but I just cannot understand how to do it. So far I added a Custom View to the main View then tried to add it programmatically like:
TestView *view = [[TestView alloc] init];
[[_window contentView] addSubview:view];
but this is not working. So how can I do this?
To see if the view has been added to a window, you can override the view's viewDidMoveToWindow method and log the value of [self window] to check (if it's nil then the view has been removed from a window):
- (void)viewDidMoveToWindow
{
NSLog(#"window=%p", [self window]);
[super viewDidMoveToWindow];
}
You should be subclassing NSView, not NSCustomView, and initWithFrame is the designated initializer for NSView, not init.
Try:
TestView *view = [[TestView alloc] initWithFrame:NSMakeRect(0, 0, 100, 200)];
[[_window contentView] addSubview:view];

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];

Cocoa: NSApp beginSheet sets the application delegate?

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.

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];

Resources