How do I make the AppDelegate a NSWindowDelegate? - objective-c

I'm finally trying out storyboards on OSX and have become confused by the structure of a generated project. Basically, I'd like to know how windowWillReturnUndoManager is supposed to work as part of the application delegate.
I create a Cocoa application with Storyboards, Core Data, and no
documents.
The generated AppDelegate contains an implementation of -
(NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window.
The AppDelegate is not defined as a NSWindowDelegate, however.
The above method is never called.
Trying to set the AppDelegate as the window delegate gives me some problems.
The Application Scene has the AppDelegate object and the Window
Controller Scene has the window, so IB connections don't work.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification is called before the application's mainWindow is available.
I can make it work by defining a mainWindow ivar and using code like...
- (void)applicationDidUpdate:(NSNotification *)aNotification {
if (!mainWindow) {
NSWindow *appWindow = [[NSApplication sharedApplication] mainWindow];
if (appWindow) {
mainWindow = appWindow;
[mainWindow setDelegate:self];
}
}
}
...and it sets the delegate the fourth time it's called. To me, that seems like a ridiculous hack.
What's the expected strategy here?

Related

Why is this delegate method automatically called in Objective-C?

I'm going through this book called "cocoa programming for mac os x" and I just started with delegates. This whole thing with delegates is still a little bit wacky to me but I think I just need to let it settle.
However there was this one exercise where I should implement a delegate of the main window so that if resized height is always 2xwidth.
So I got 4 files:
AppDelegate.h
AppDelegate.m
WindowDelegate.h
WindowDelegate.m
AppDelegate are just the two standard files that get created when you open a new Cocoa project. I had to look up the solution because I didn't quite know how to accomplish this task.
The solution was just to create a new cocoa class, "WindowDelegat.h/.m" and add this to it's implementation file:
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
Then I opened the interface builder, added a new object and made it my WindowDelegate. I then had to ctrl drag from the WindowDelegate to the actual window and made it the window's delegate.
Clicked run and it worked. Yay! But why?
First I thought that "windowWillResize" is just one of these callback functions that get's called as soon as the window is resized but it isn't. Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements.
But "windowWillResize" is non of them. So why is it called?
EDIT: Problem solved! Thanks a lot!
Now I'm trying to connect the delegate to the window programmatically. Therefore I deleted the referencing outlet from WindowDelegate to the actual window in interface builder. It works but I just want to verify that this it the correct way how it's done:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#import "WindowDelegate.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) WindowDelegate *winDeleg;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)awakeFromNib {
[_window setOpaque:NO];
NSColor *transparentColor = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.5];
[_window setBackgroundColor:transparentColor];
NSSize initialSize = NSMakeSize(100, 200);
[_window setContentSize:initialSize];
_winDeleg = [[WindowDelegate alloc] init];
[_window setDelegate: _winDeleg];
}
#end
WindowDelegate.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface WindowDelegate : NSObject <NSWindowDelegate>
#end
WindowDelegate.m
#import "WindowDelegate.h"
#implementation WindowDelegate
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
- (id)init {
self = [super init];
return self;
}
#end
Why does the #property of WindowDelegate need to be strong?
And isn't my winDeleg an object? Why do I have to access it through _winDeleg when it's an object. I though the underscore is used to access variables?
Thank you for your help!
Clicked run and it worked. Yay! But why?
Because instances of NSWindow have a delegate property that can point to any object that implements the NSWindowDelegate protocol, and that protocol includes the -windowWillResize:toSize: method.
Read that a few times. The reason it's important is that you can create your own object, say that it implements NSWindowDelegate, implement -windowWillResize:toSize:, and set that object as your window's delegate. Then, whenever the user resizes the window, your method will be called and can modify the proposed new size.
Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements. But "windowWillResize" is non of them. So why is it called?
This really isn't so different. Think of delegates as "helper objects." They let you customize the behavior of an object without having to create a whole new subclass. The NSWindowDelegate object is essentially a contract that the NSWindow promises to follow: whenever certain things happen, such as the user resizing the window, the window will call certain methods in its delegate object, if the delegate exists and implements those methods. In the case of NSApplication, a lot of those delegate methods are application lifecycle events, like the app starting up or quitting or getting a message from the operating system. In the case of NSWindow, delegate methods correspond to interesting events that can happen to a window, like the user moving it, hiding it, showing it, maximizing it, moving it to a different screen, etc. Other classes, like text views or network connections or movie players, have their own sets of interesting events and their own delegate protocols to match.
Note that methods marked IBAction really aren't delegate methods, they're just methods that get called by objects like controls that use a target/action paradigm. The IBAction keyword lets the IDE know which methods it should present as possible actions for things like buttons. You often find actions in window controllers and view controllers, and those objects frequently act as a delegate for some other object, but the actions themselves aren't part of the delegate protocol. For example, NSTableView takes a delegate object that determines how the table will act and what's displayed in it. It often makes sense for the view controller that manages the table to be the table's delegate, and that same view controller might also manage some buttons and contain the action methods that said buttons trigger, but the actions aren't part of the NSTableViewDelegate protocol and you therefore wouldn't call them delegate methods.

Xcode: Document-based app window is not loading during attempt to subclass NSWindowController

I really haven't done much and i'm already stuck.
So far i've done:
added NSWindowController subclass (MikesWindowController.h & .m)
removed windowNibName from MikesDocument.m (since i'm implementing my own
WindowController subclass.)
I tried:
Tested if NSLog would come back at init, windowControllerDidLoadNib, applicationDidFinishLaunching. Only the NSLog at init printed.
And, tested the Main Menu -> File -> New after after compiling my Document app.
Am I implementing this right? Thanks. Any suggestions would be great! Under MikesDocument.m
-(void)makeWindowControllers{
MikesController *controller = [[MikesWindowController alloc]init];
[self addWindowController:controller];
}
After much deliberation I found that answer. Woohoo. Enjoy future.
removed initWithWindow from my NSWindowController subclass
implemented initWithWindowNibName to my NSWindowController subclass so now anytime I initialize I must specify the window nibName.
Below I implemented initWithWindowNibName in my NSWindowController subclass heres what it looks like:
MikesWindowController.m
-(id) initWithWindowNibName:(NSString *)windowNibName{
self = [super initWithWindowNibName:windowNibName];
return self;
}
(Below) Back again to the main document I corrected the makeWindowController method and instantiated my controller with "MikesDocument" (for MikesDocument.xib) and added it.
MikesDocument.m
-(void)makeWindowControllers{
MikesWindowController *controller =
// must tell controller which nib file to use.
[[MikesWindowController alloc]initWithWindowNibName:#"MikesDocument"];
[self addWindowController:controller];
}
Success! Don't even bother calling init or implementing init as it returns an error at any time.

Delegating Outline View's Data Source To Separate Object

I want to be able to use a blue object box to delegate control over an NSOutlineView. The blue object box would be hooked up to my primary controller, so it'd just be a data source and control the content of the NSOutlineView.
The problem I'm having is that I have no control over the Channel Data Source. I'm simply calling a declared method with some test NSLog inside of it, and it doesn't get called. The outlet doesn't get instantiated.
Here's the connections of the blue object box (ChannelDataSource)
Here's the connections of File's Owner for my primary controller.
So you see, I want to do something like [dataSource callMyMethod]; with the final aim that I have control over the contents for the NSOutlineView.
Any ideas?
EDIT
The application is structured whereby my primarily app delegate looks like this:
#implementation MyAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MainController alloc] init];
[controller showWindow];
}
#end
Then in the MainController I have something along the following lines:
#implementation MainController
-(id)init {
self = [super init];
if (self) {
// loads of random stuff
[dataSource myMethod];
}
return self;
}
So "Channel Data Source" blue object box is dataSource. At this point in the application life cycle, it's null, which isn't what I was expecting. At the same time, it's still a bit of black magic to me. If you have a blue object box, at what point is it instantiated? Obviously this isn't hooked up correctly though.
EDIT EDIT
Further to my points above, and trying to fix the problem, is this actually a good way to go about it? I'm looking at this thinking it's not meeting a decent MVC architecture, because ultimately the blue object box's owning class is storing and managing the data. Is there a better way to go about managing what's in your NSOutlineView?
EDIT EDIT EDIT
So I have my app delegate, which is strangely a class all by itself that instantiates the main controller. Don't ask me why I did this, it was very early code. So my app delegate (root entry point) has this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
controller = [[MyController alloc] initWithWindowNibName:#"MainWindow"];
[controller showWindow:nil]; // this doesn't open the window
[controller loadWindow]; // this does open the window
}
And the declaration of the controller:
#interface MyController : NSWindowController
Which contains the following method declaration in it:
-(void)windowDidLoad {
[dataSource insertChannel:#"test" forServer:#"test2"];
}
I have a breakpoint in windowDidLoad and it definitely doesn't get called.
Ideas?
There's still a few things you didn't clarify, but I can do some guessing. First, I'm assuming that MainController is a subclass of NSWindowController. If so, you should be using initWithWindowNibName: instead of just init, otherwise how would the controller know what window to show when you address showWindow: to it? Second, even if you do that, and change your init method to initWithWindowNibNamed:, what your wrote won't work, because the init is too early in the process to see your outlet, datasource. If you just log dataSource it will come up null. A better place to put that code would be in windowDidLoad, as everything will have been set up by then (this will be called after showWindow:). So, in my little test project, this is what I did.
In the app delegate:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.cont = [[Controller alloc] initWithWindowNibName:#"Window"];
[self.cont showWindow:nil];
}
In the Controller.M I have this:
- (void)windowDidLoad {
NSLog(#"%#",self.dataSource);
[self.dataSource testMethod];
}
In IB, in the Window.xib file, I set the class of the file's owner to Controller, and the class of the blue cube to ChannelDataSource. EVerything was hooked up the same way you showed in your post.

textFieldShouldReturn not firing in iPad app

I have an iPad app created using XCode 4 with Storyboard. I have a UITableViewController with the interface defined as such:
#interface CustomerViewController : UITableViewController <UITextFieldDelegate>
In the .m file, I have a code snippet as:
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
if(textField == businessName) {
[email becomeFirstResponder];
return true;
}
The method 'textFieldShouldReturn' does NOT get executed. What else needs to be done? (BTW... I have never been able to get this to work in a iPad app, but always successful in a iPhone app)
In the textFieldShouldReturn method, you're testing if the textField is equal to businessName. I'm assuming that's a UITextField object, but you have to set the delegate to receive callbacks. Just a simple self.businessName.delegate = self; when you create the TextField.
Another tip, I'd recommend calling it something like businessNameTextField. It's easier to read throughout your code.
You have to set the view controller as the delegate of the text field, just declaring that you conform to the protocol isn't enough. If it works in the iPhone but not iPad, then the chances are you haven't linked the delegate outlet in the iPad storyboard, but you have in the iPhone.

Objective C Delegate for the Main Application Window

So I'm trying to do this exercise where I need to set a delegate for the main window. The purpose is to make sure that when the user resizes the window, it's always twice as wide as it is high.
This is my AppController.h file:
#import <Cocoa/Cocoa.h>
#interface AppController : NSObject
{
NSWindow *windowWillResize;
}
#end
and this is my AppController.m file:
#import "AppController.h"
#implementation AppController
- (id) init
{
[super init];
windowWillResize = [[NSWindow alloc] init];
[windowWillResize setDelegate:self];
return self;
}
- (NSSize) windowWillResize:(NSWindow *)sender
toSize:(NSSize)frameSize;
{
NSLog(#"size is changing");
return frameSize;
}
#end
However, I can remove the line
[windowWillResize setDelegate:self];
since I set the delegate in Interface Builder, but I'm not sure why this works.
How does windowWillResize know that I'm referring to the main application window since I'm doing a completely new
windowWillResize = [[NSWindow alloc] init];
I have a feeling that I am completely doing this wrong. Could someone point me in the right direction? Thanks!
Indeed, you don't need to create a NSWindow *windowWilResize since a newly created Cocoa app already has a main window. You don't need to implement an -init method either.
You only need to set you appController as a delegate of your main window in Interface Builder and to implement the -windowWillResize: method in your appController.
If you are familiar with french language, you can take a look at a blog entry I have written on this subject: Délégation en Cocoa.
You're leaking an instance of NSWindow. In -init you create an NSWindow instance. However, that is not used because when the NIB loads, it sets up all the connections that you specified in Interface Builder and you start using the window from the NIB instead. Do not create a window object in code - Interface Builder does it for you! :-)
In fact, it's not quite "instead"; your app controller is now the delegate for both NSWindow instances - the one that comes from the NIB and the one you instantiated in -init. However as the in-code NSWindow is never used anywhere else, it's still redundant and should be removed.
If you just want to maintain the aspect ratio of the window you can use either of these two NSWindow methods:
setAspectRatio:(NSSize)
setContentAspectRatio:(NSSize)
The first method locks the entire window size, including the title bar. The second one just the content. You can call this method during the initialization of your window inside the delegate (for example: -applicationDidFinishLaunching)