Programmatically creating new windows and accessing window objects in Cocoa - objective-c

I'm having an issue with creating new windows in Cocoa.
Hypothetically speaking, let's say I have "WindowA" and has a button called "myButton".
When you click on "myButton", it runs this code in the following class file:
-(void)openFile2:(id)sender
{
myNextWindow = [[TestWindowController alloc] initWithWindowNibName:#"MainMenu"];
NSString *testString = #"foo";
[myNextWindow showWindow:self];
[myNextWindow setButtonText:testString];
}
The code in a nutshell makes a duplicate "WindowA" and shows it. As you can see, this code also runs a method called 'setButtonText', which is this:
- (void)setButtonText:(NSString *)passedText
{
[myButton setTitle:passedText];
}
The problem is that when I call this method locally, in the original window - the button text changes (e.g., [self setButtonText:testString]) it works. However, it does not work in the newly created window (e.g., [myNextWindow setButtonText:testString];)
When I debug the newly created window, step by step, the 'myButton' value it gives is 0x0. Do I have to manually assign controllers/delegates to the new window? I think the 'myButton' in the code isn't associated to the 'myButton' in the newly created window.
How would I fix this problem?

The first problem is that you are loading the MainMenu NIB/XIB repeatedly. That will do Very Bad Things -- the MainMenu should only be loaded once at application startup.
You want to break out any UI that needs to be loaded repeatedly into a separate NIB/XIB file (the same way a document based application has a MainMenu.xib and Document.xib files).
To properly do this, you need to understand the concept of "File's Owner" and how to leverage it properly. Note that there is also overlap with window controllers and understanding those, if you want to use them, will be helpful.

Related

How to get an NSDocument subclass to print custom views

What needs to be hooked up for an NSDocument subclass to call its print methods when File->Print... is chosen?
How does the File->Print menu get hooked up? (Right now the selector/action is hooked up to first responder's print method. That's in a storyboard, while my NSDocument subclass has its own xib.)
I've tried implementing all of:
-(void)print:(id)sender;
-(void)printDocument:(id)sender;
-(void)printDocumentWithSettings:(NSDictionary *)printSettings showPrintPanel:(BOOL)showPrintPanel delegate:(id)delegate didPrintSelector:(SEL)didPrintSelector contextInfo:(void *)contextInfo;
-(NSPrintOperation*)printOperationWithSettings:(NSDictionary *)printSettings error:(NSError *__autoreleasing *)outError;
But none of them are ever called when I choose Print. Where are these methods supposed to go/who calls them? (I also tried a basic app with a custom view and didn't have luck there either.)
Okay. Looks like the problem is due to a bug in Xcode: When creating a Document based app using storyboards the file menu is by default hooked to print: and printDocument: is not available.
The strange part is that my print: call is getting hijacked somewhere along the line but I can't figure out where (at the application level, not the document, because the print dialog is a window not a sheet). printDocument: works as expected, but must be defined manually in order to hook it up.
This is for a document-based app, targeting 10.10, and using storyboards.
In the storyboard with Main Menu, add a User Defined Action for printDocument: (This is where storyboard based differs, and I feel is a bug. Xib based do not require this User Defined Action.)
Hook up the selector for File -> Print to First Responder and choose printDocument: instead of print:
Don't define printDocument: in your NSDocument subclass. If you want to, then be sure to call super or perhaps one of the methods below.
From NSDocument.h
/* The action of the File menu's Print... item in a document-based application.
The default implementation of this method merely invokes
[self printDocumentWithSettings:[NSDictionary dictionary]
showPrintPanel:YES
delegate:nil
didPrintSelector:NULL
contextInfo:NULL].
*/
- (IBAction)printDocument:(id)sender;
The default implementation of printDocumentWithSettings in turn calls printOperationWithSettings, so you can use either of those methods to draw custom information prior to the print sheet appearing.
The accepted solution of setting the menu item to -printDocument: is correct, but not (technically) because of an Xcode bug. (It is very a poor default setting, though.)
The menu item is calling the -print: of the whatever is the first-responder. NSView implements -print:, so if anything has been set as first-responder you will print with NSView's -print:, not your document's -print:. If editing your document requires text editing, the control you are using to implement editing will be set to first-responder, and that control will get the -print:.
Take a look at the TextEdit sample code from Apple (https://developer.apple.com/library/mac/samplecode/TextEdit/Introduction/Intro.html)
In my non-document based app I have set a custom action for the print menu item. In that method I notified my controller about the print operation via NSNotificationCenter. Maybe that works for you too :)

Define a controller for NSDocument for document-based application

I'm not very sure how Document-Based Applications works.
I've created some actions for NSObject in the Mainmenu.xib. One of this is called when the user click on "File>new":
-(IBAction) newDocument:(id)sender{
Document* newDoc =[[Document alloc] init];
[[NSDocumentController sharedDocumentController]addDocument:newDoc];
[newDoc addWindowController: [[NSWindowController alloc] initWithWindowNibName:[newDoc windowNibName] owner:newDoc]];
[newDoc showWindows];
}
I've also this code inside the openDocument:(id) sender action that does the same but of course loading data to define the application workspace.
If I run the application it show a blank document without to call newDocument action. I don't know how to stop default blank document and to set newDocument: to be called.
Then if i do openDocument: too (so I've two documents, one blank and one not) and I do some operation on the second document it also replicate in the first blank one.
I've double check delegates, file owners, and also if the - (void)windowDidBecomeMain:(NSNotification *)notification return different pointers and all seem to be ok.
Probably I've not understood document based application work flow but I've read the Apple guide and other istructions. What do I miss?
An IBAction method is called, when the user did something. So this is not called from the system at app launch.
You can customize the behavior at app launch with -applicationShouldOpenUntitledFile: (NSApplicationDelegate) and – this is probably your next question – -applicationShouldHandleReopen:hasVisibleWindows: (NSApplicationDelegate). Changing the behavior in both cases is not recommended.
Looking to your action method, I see no reason, why you want to customize it.
A instance of your document class is created automatically.
You can create a window controller for it in your document subclass. This is documented.
Just let NSDocumentController do the work for you. What is the problem of the default behavior?
No. I thought to be confused instead the only problem was about releasing observer notification. When you call the close message for a NSDocument notification observers still persist. Working in ARC I miss this point.
So This is the solution at my issue. Thank you anyway.

Xcode: How should I organize my multi-window user-interface project?

Note: Using Objective-C, Cocoa, and Xcode.
At first, I did all my programming in the AppDelegate and had all user-interface elements such as windows in the same '.xib' (nib file). This worked great at first, but then as my application became more advanced with more "features", everything became extremely cluttered and the file too long for my liking.
I'm now trying to progress. I'm wondering how I should (properly and efficiently) go about having a multi-window project? My plan was to have a .xib file for every window, and put only necessary things in the AppDelegate. I would then have a core file for all necessary calculations and such to be used in my application and a Main Controller file to control outlets and actions from all windows in my app. However, I'm quite new to Objective-C and have been running into SO many issues and problems trying to set up Window Controllers and stuff.
Am I even on the right path? Am I doing it wrong? How should I manage a multi-window user-interface application in Xcode?
Thanks in advance.
For multiple windows, I think it's best to use an NSWindowController for each, with it's associated xib file for the window. I'm not sure what you mean by "a Main Controller file to control outlets and actions from all windows" -- each window controller will have outlets to its own window. You can't connect outlets across multiple xib files. You can have one window in the MainMenu.xib file that you get by default when you create a project, and use that to launch other windows perhaps, but it's hard to offer any more specific advice without knowing how all your windows relate to each other.
After Edit:
If you want to open another window, have a menu item's action method be something like this:
self.controller = [[WindowController alloc] initWithWindowNibName:#"WindowController"];
[self.controller showWindow:nil];
Here, I have a property called controller to keep a strong reference to the new window controller. If you don't do that, the controller will be deallocated, so if you have multiple windows, that you want to show at the same time, you'll need properties to hold on to them.

Multiple instances of an object inside a XIB file

I have a document-based app with the recommended NSDocument / NSWindowController setup. Every window has its own NSWindowController instance and an associated XIB file. The interface is loaded pretty straight-forward in -(id)init: if (self = [super initWithWindowNibName:#"DocumentWindow"]) { // yadda yadda }. The XIB file contains an object that represents a separate controller that should be instanciated every time the user opens a new window. It presents data that is only relevant to the current document. When I unpack the object with -(id)initWithWindowNibName: I always get a reference to the same instance despite having two different window controllers and despite having called -(id)initWithWindowNibName: twice. The XIB loading mechanism seems to only unpack the same archived object once which kind of makes sense (really?).
Is there no way besides allocating separate objects in code, manually loading views, calculating their sizes, adding them as subviews, then setting bindings and keeping an eye on unbinding them manually when the window gets closed?
I’m banging my head against the wall, because of my own stupidity. It just makes things worse.
As I initially thought I was absolutely on the wrong path. The issue was simply that I registered a notification in the aforementioned instantiated class for an object that was a subview of the application’s keyWindow (I hacked it together and wanted to change it later to a property, ouch). When the application was loaded all instances registered for all document windows and all of them got notified and calculated the same data.
The debugger is my best friend today.
There is actually nothing like an optimization when you unarchive objects from a XIB and you will always get different instances (which absolutely makes sense in retrospect). If you encounter a similar issue, then it’s probably an unrelated bug at another place in your code.

How to set up OSX screen saver configuration sheet?

I'm working on a screen saver for OSX (Mountain Lion) and I'm having trouble setting up the configuration sheet (so when the user clicks "Screen Saver Options..." within System Preferences, my options appear). There seem to be only two or three tutorials on writing OSX screen savers anywhere on the Internet, and they're all several years old so the material doesn't quite translate to OSX 10.8 and Xcode 4.
First of all, in my ScreenSaverView.m file, I have:
- (BOOL)hasConfigureSheet
{
return NO;
}
- (NSWindow*)configureSheet
{
return nil;
}
...and yet, in System Preferences, the "Screen Saver Options..." button is still clickable (nothing happens when it's clicked) rather than disabled as in the "Arabesque" screen saver.
What are the steps to having a configuration sheet appear when the button is clicked, and why is the button not currently disabled?
Edit:
I realized why the "Screen Saver Options..." button wasn't disabled. I had forgotten to include -(BOOL)hasConfigureSheet; in the ScreenSaverView.h file. My question about how to get the configuration sheet to appear, however, remains.
First things first: Be sure to include ScreenSaver.framework in your project; it provides the necessary classes ScreenSaverView and ScreenSaverDefaults.
You say you already have a file called ScreenSaverView.m. Couple that with you also saying that you have to export your -hasConfigureSheet method in order to make the "Screen Saver Options..." button disable. This leads me to wonder if you have subclassed ScreenSaverView as you should have done. (Did you subclass NSObject instead?) ScreenSaverView exports methods such as -hasConfigureSheet for you. You should be subclassing it, and overriding the appropriate methods in it.
A couple more things:
You should have included a xib file in your project that contains the UI for your configuration sheet, and you should have provided IBOutlets in your subclass' interface to reference the panel and the UI elements it contains (those for which you actually need an outlet, that is).
Finally, your -configureSheet method should fetch the configuration sheet in a manner similar to this (in this example, configSheet would be one of your IBOutlets):
if (configSheet == nil)
{
if ([NSBundle loadNibNamed:#"myConfigSheet" owner:self] == NO)
{
NSLog(#"load config sheet failed");
}
}
// then retrieve your defaults and set up your sheet; you should
// be working with ScreenSaverDefaults, a subclass of NSUserDefaults.
// then return 'configSheet'
Edit:
Apologies in advance if I'm about to tell you things you already know, but you did say you were having difficulties in configSheet, and so I'm simply covering all the bases.
In My_ScreensaverView.h, declare an outlet for your panel:
IBOutlet id configSheet;
Note that I used id instead of NSWindow * or NSPanel *, simply because I don't know what class you actually are using for the sheet. (Ideally, a NSPanel should be used for a sheet.)
In your nib file, make sure that the File's Owner is an instance of My_ScreensaverView. You can determine this by selecting the icon for this object and then using the Identity inspector to specify the class.
Make the connection between the configSheet outlet and the panel. One way of doing this is to hold down the Control key while dragging from the File's Owner object to the window or panel icon, then selecting configSheet from the pop-up that appears.
As always, good luck to you in your endeavors.