I'm learning some obj-c and therefore i'm building a small cocoa application.
From the MainMenu.xib i have added a menu to the "Main menu" in the top. When click, this triggers a IBAction that opens an instance of a window, in this case a window for managing categories.
This category window has a NSWindowController, looks like this:
// CategoriesWindow.h
#import <Cocoa/Cocoa.h>
#interface CategoriesWindow : NSWindowController
-(IBAction)OpenCategoriesWindow:(id)sender;
#end
// CategoriesWindow.m
#import "CategoriesWindow.h"
#implementation CategoriesWindow
-(IBAction)OpenCategoriesWindow:(id)sender
{
CategoriesWindow *Categories = [[CategoriesWindow alloc] initWithWindowNibName:#"CategoriesWindow"];
[Categories showWindow:self];
}
#end
To this i have a CategoriesWindow.xib with a NSTableView that does some things, so there for i have a CategoryTableController.h and .m that handles the data for this table.
When i hit a button i want it to do a bunch of things, and then i want the window to close it self. That is, i want this window to close itself from a IBAction in the CategoryTableController.m.
How do I do this? One bad thing with this setup (followed from a tutorial somewhere...) is that I can open a lots of instances of this window by clicking the menu-button.
Any tips or ideas where to begin?
From there Reference:
[Categories close];
However there is something wrong with your implementation as you are creating an instance of the class from an instance method of the class. That doesn't look right to me. Also you aren't retaining the new instance anywhere, so it will be possibly destroyed under ARC or leaked under MRR.
I think you might want:
-(IBAction)OpenCategoriesWindow:(id)sender
{
[self showWindow:sender];
}
-(IBAction)CloseCategoriesWindow:(id)sender
{
[self close];
}
Although I can't be sure.
Related
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.
Simple structure:
exampleController.h :
#import <Foundation/Foundation.h>
#interface exampleController : NSWindowController {
#public
IBOutlet NSPanel *entryPanel;
#property (nonatomic, strong) IBOutlet NSPanel *entryPanel;
#end
exampleController.m :
#import "exampleController.h"
#implementation exampleController
#synthesize entryPanel;
- (id)init {
self = [super initWithWindowNibName:#"ExamplePanel"];
if (self) {
// Initialization code here.
NSLog(#"entryPanel: %#", entryPanel);
[self.entryPanel setTitle:#"TESTING!"];
}
return self;
}
randomController.m :
...
- (id) init {
self = [super init];
if (self) {
// loading our example controller if it isn't loaded yet.
if (!ourExampleController) {
ourExampleController = [exampleController alloc] init];
}
}
return self;
}
...and then later in the random controller within a method I show the NSPanel via:
[ourExampleController showWindow:self];
[ourExampleController window] makeKeyAndOrderFront:self];
My problem is that no matter what, the first time the NSPanel displays and shows itself the title is always still set to the title that it has in Interface Builder! Even though I explicitly set the title in the exampleController init method.
I've also tried throwing an NSLog(#"entryPanel: %#", entryPanel) in the init method for exampleController and at launch it is always NULL. I do not have to ALLOC all my IBOutlets in the init because I am already synthesizing them?
I've double checked everything in interface builder. The File Owner for the ExamplePanel.xib is set to the exampleController class. The window AND entryPanel outlets are both referencing the NSPanel in our xib file. What am I missing ??
Thanks in advance!
EDIT: Just to add. If I open the window (..and see the default IB title) and then close it and reopen it with a method that changes the title - it seems to work! This problem seems to only reside with the window first opening. It seems like my properties are not being alloc'd until the window first opens?
EUREKA!
As per discussion here:
IBOutlet instances are (null) after loading from NIB
I learnt that the window itself is not loaded when my controller is initialized. Found that surprising since I figured using initWithWindowNibName:#"myNibFile" would also alloc and initialize all outlet properties but since I'm new to OSX Obj-C that appears to not be the case. All the outlet properties are only alloc'd once the window itself is loaded too.
It's easy to just show the window (which also loads the window if it's not loaded yet) and then quickly set all the outlets to my desired values BUT this was an issue for me since I wanted to avoid that ever so slight "screen flicker" (for lack of a better description) that occurs as the values adjust to their new settings.
The solution was to find a way to load the controller and load the window without actually showing it first! Then I discovered this:
Can you force a NSWindow to load, i.e. before it is presented onscreen?
Steps to make that happen:
Add the following to my NSWindowController subclass init method:
// this loads the window as per link/description above
[self window]
The key seems to be though to ensure that in your NIB/XIB file that the Visible At Launch is unchecked. If it is checked (default behavior) then the [self window] call above will still show your window when your app launches. Unchecking the above option ensures the above call does not show your window until you explicitly show it yourself!
E.g. You can define an action button which loads your window:
[exampleController showWindow:self];
[[exampleController window] makeKeyAndOrderFront:self];
Hope this helps someone else out. Was a head scratcher for a couple hours!
You should set the title, etc. in -awakeFromNib or -windowDidLoad instead of an -init… method. That way the values will be set before the window is shown and you won't get the flicker.
In this answer is said it is possible to have a custom NSWindowController by removing the window from MainMenu.xib and instantiating the window controller 'manually' from applicationDidFinishLaunching:. But it also says:
To stop the default window from showing I just delete it. There's probably a better way but I don't know that.
Is there a better way? What is that better way, should it exist? Is it considered 'normal' practice to get your own window controller?
To do this, you would usually subclass NSWindowController and change the File's Owner class to your WindowController subclass in the nib.
EDIT:
If you aren't doing a document-based app, and just want an NSWindowController of your own to do on-demand loading of Nibs (completely reasonable), then you'd delete the window from your nib and instantiate an NSWindowController subclass programmatically, using it explicitly to do your window loading...
#implementation MyApplicationDelegate {
MyWindowControllerSubclass *windowController;
}
-(void)applicationDidFinishLaunching:(NSNotification *)notification {
windowController = [[MyWindowControllerSubclass alloc] initWithWindowNibName:#"MyWindowNib"];
[windowController showWindow:nil];
[windowController.window makeKeyAndOrderFront:nil];
}
I was running into the same issue and I want to show you my own solution.
Create a normal Cocoa Application (not Document Based)
Go to MainMenu.xib an delete the Window
Go ahead and create a new file, User Interface -> Window
After that create a subclass of NSWindowController
Open the just created xib file and set the Custom Class in the Identity inspector to the just created subclass of NSWindowController
Right click on File's Owner and connect the window property to the actual window
Now go to the AppDelegate an create an instance variable that holds you CustomWindowController
Last thing you have to do is instantiate your CustomWindowController self.customWindowController = [[AccountWindowController alloc] initWithWindowNibName:#"CustomWindow"]; and show the Window [self.customWindowController showWindow:nil] in - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
Here is an example project https://www.dropbox.com/s/ft3t7w72806tnoe/CustomWindowController.zip
I actually found another way: NSWindowController has the method -initWithWindow:. Because the App Delegate has a property window which is linked to the window from MainMenu.xib on startup, it was easy to link it to my WindowController:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
MyWindowController *wincon = [[MyWindowController alloc] initWithWindow:window];
}
I have yet to research this, but I don't get any errors.
I am setting my first steps in OSX development and I've run into some problems. I have quite some experience with iOS development but the window system for OSX programs is something else.
I am making a client for a social network like twitter and need 2 seperate window controller for first starting the app, one if you are logged in to show your timeline and one for logging in, if you are not yet logged in. In the info.plist you need to give it a main.xib. For this I made an empty xib which I hide, the second the app starts. This is not really a good solutions IMO, what is a better solution for this? I want to keep the windows seperate from the appdelegate because that way I can keep my code seperated.
This gives me a problem, when I open my 'second' window to login it shows up but isn't active. I have tried all the things like, orderFront:, activateIgnoringOtherApps:, makeKeyAndOrderFront: & more. But this all doesn't work..
So: First off, is there a better way to handle the main.xib that is needed in the info.plist and if not, is there a way around the focus problem?
I'm working om osx 10.7
For more than one-offs, you really ought to separate your app delegate from your window controllers. Go ahead and create a new Cocoa application from the template. In MainMenu.xib, delete the window. In AppDelegate.h delete the IBOutlet to the NSWindow. Create a couple new subclasses of NSWindowController complete with XIBs--perhaps LoginWindowController and TimelineWindowController.
For "final" NSWindowController subclasses (i.e. those which won't be subclassed), the best practice for designated initializers is
//for our example class LoginWindowController
- (id)init
{
self = [super initWithWindowNibName:#"LoginWindowController"];
if (self) {
//....
}
return self;
}
Now in your app delegate, you should have #properties for the two different window controller instances:
//Within AppDelegate.m
#import "AppDelegate.h"
#import "LoginWindowController.h"
#import "TimelineWindowController.h"
#interface AppDelegate ()
#property (nonatomic) LoginWindowController *loginWindowController;
#property (nonatomic) TimelineWindowController *timelineWindowController;
//For the sake of this demo, add a property for the loggedIn state:
#property (nonatomic) BOOL loggedIn;
#end
You ought to have some sort of method in your app delegate that presents the correct window controller. Let's call it -updateWindowVisibility:
- (void)updateWindowVisibility
{
BOOL isLoggedIn = self.loggedIn;
BOOL loginWindowVisible = self.loginWindowController.window.isVisible;
BOOL showLoginWindow = !isLoggedIn;
BOOL timelineWindowVisible = self.timelineWindowController.window.isVisible;
BOOL showTimelineWindow = isLoggedIn;
if (!loginWindowVisible && showLoginWindow) {
if (!self.loginWindowController) self.loginWindowController = [[LoginWindowController alloc] init];
[self.loginWindowController showWindow:nil];
} else if (loginWindowVisible && !showLoginWindow) {
[self.loginWindowController close];
self.loginWindowController = nil;
}
if (!timelineWindowVisible && showTimelineWindow) {
if (!self.timelineWindowController) self.timelineWindowController = [[TimelineWindowController alloc] init];
[self.timelineWindowController showWindow:nil];
} else if (timelineWindowVisible && !showTimelineWindow) {
[self.timelineWindowController close];
self.timelineWindowController = nil;
}
}
This method as implemented above does a tiny bit more work than is necessary given the present setup, but should be easier to modify when you need to show/hide other windows. All that's left to do at this point is to call -updateWindowVisibility from -applicationDidFinishLaunching:.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.isLoggedIn = NO;
[self updateWindowVisibility];
}
I've posted an example app to github which demonstrates this approach.
In terms of structure (your first question), I would recommend this:
Create a XIB with one blank window and a big NSView (called, say, megaView) covering it. Create an IBOutlet in AppDelegate for your big NSView. Set the app to use this XIB on load.
Create two separate NSView XIBs: one for the state of being logged in, one for the state of being logged out. Put your layout in these.
Create two NSViewController subclasses: one controlling the logic of each NSView you just created. Let's call them LoggedOutViewController and LoggedInViewController.
Jump back to the two NSViews you created. Set the File Owner of your logged in NSView to LoggedInViewController and the File Owner of your logged out NSView to LoggedOutViewController. Hook up each File Owner's view (right-click on File Owner to find it) to the respective NSView.
In your app delegate, determine the user's authentication status in whatever way you need.
If logged in, do this:
NSViewController *loggedInController = [[NSViewController alloc] initWithNibName:#"NibNameGoesHere" bundle:nil];
[[self megaView] addSubview:[loggedInController view]];
Otherwise do the above process with your loggedOutController:
NSViewController *loggedOutController = [[NSViewController alloc] initWithNibName:#"OtherNibNameGoesHere" bundle:nil];
[[self megaView] addSubview:[loggedOutController view]];
That should get you what you want and will likely clear up your second question in the process. The difference between my answer and Nate's is that mine uses the same window. Instantiating view controllers conditionally and loading their views into superviews is probably the most important aspect of Cocoa I learned.
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)