Define a controller for NSDocument for document-based application - objective-c

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.

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 :)

Global Access to Class instance - Optimal design approach?

OK, here is my situation and I'm really not sure on which design approach to use. So, I'd be glad to read some input on my particular case...
The scenario :
I've got a tab-based application
Initially we create an instance of NSWindowController (e.g. MyDocumentManager) which takes care of the tab creation/manipulation
Each tab contains (= is attached to) an instance of MyDocument
Now, I'm about to implement the menu actions.
For each menu there is a separate Menu Controller (actually a subclass of NSObject grouping all relevant functions), e.g. MyFileMenuController
File Menu's actions are linked to actions in a MyFileMenuController object, via Interface Builder
The question :
How is it possible that MyFileMenuController "knows" about MyDocumentManager (created in my AppDelegate.m), so that we can access current document details and perform all relevant actions? Any ideas? Which approach is preferable?
My ideas :
Passing object from class to class (not sounding that great)
Singletons (although I've honestly never used them, and do not know whether/how it could be my particular case)
Notifications & Notification Listeners
(Looking at it from the opposite side, though not sure) Delegate methods
OK (not sure if that's the best way to do it), but this is what I decided to do :
[[[NSApplication sharedApplication] delegate] MyDocumentManager]
So, as long as an object is part of my AppDelegate, this way I can access it from anywhere.
I would make an initial UIViewController linked to appDelegate.rootViewController.
In this new view controller (just call it "mainViewController") I would put my tabBar and the menuController.
I understand your interface is similar to facebook with a leftBarbutton which makes horizontal scroll and discover the menu. So in the selector for this leftBarButton I would call a method like:
- (void)discoverMenuForDocument:(MyDocument*)document {
// Set menu configuration for specific document
// Make animation to discover menu
}
Where document could be something like:
self.selectedViewController (<-- You cand make this in several ways depending on your code...)
Realize that (MyDocument*) is not an object but just a reference, so in my opinion there isn't any problem doing this.
Good luck!

multiple delegates in objective-c

i have been working on moving one of my apps away from the "shared appdelegate" process which seems to be frowned up, despite its over whelming use. i have been attempting to setup protocol methods for what i want to do but am having zero luck. my question is, can you even have lets say a single viewcontroller send delegate requests to multiple classes? from what im finding out it doesn't seem like you can. which doesn't make sense because i thought that was the whole point of delegates and protocols with mvc. now just to clarify, i know you can have a single viewcontroller act as the delegate for multiple other viewcontrollers. but that's not what i am asking. for a simple example, lets say you have apples flip-utility template. the "done" button just calls a delegate method to the mainvc to dismiss it. now lets say we added a new class called...
#interface NewClass : NSObject <TheOtherDelegate>
and it had a delegate method...
- (void)doSomething
{
NSLog(#"The Delegate did something...");
}
can we have a button on the flipsideviewcontroller, that we wanted to call that delegate method, but still keep the "done" button call to the delegate method on the mainviewcontroller that dismisses it?
that being said, i put together a quicky project just to see if it would work and it doesn't. i came across an "answer" that says you have to instantiate the class first you want to be the delegate...
NewClass *myDelegate = [NewClass alloc] init]
[fillInMethodHere setDelegate:myDelegate];
not sure why it got a correct answer check, because needless to say it doesn't work. is there something i am missing? i scoured ib to see if there is some "delegate" connection somewhere but i couldn't find anything.
on a side note, as i was working in my working project, i read a suggestion about removing the #import and adding #class. again, that broke all kinds of things. the strange thing is before doing that, what i had so far was working and building fine. when i removed the new #class and un-commented the #import. xcode all of a sudden gave me an error "cannot find protocol deceleration for..." but yet, it worked seconds earlier. i would up having to remove the protocol code and re-add it for it to work again. very starge.
any help would be appreciated. everything iv read in docs, google, stack, etc that say something should work, don't in an actual project.
A "delegate" isn't some fancy object. It's simply a synthesized property of type id called delegate. If you wanted to, you could have an arbitrary number of properties that all conformed to the same protocol. Then when you wanted to issue a callback, you would just address all of them:
[self.mydelegateA doSomething];
[self.mydelegateB doSomething];
etc.
You could also have an NSMutableArray property that you could add objects to, and then use [self.myMutableArrayOfDelegates makeObjectsPerformSelector:#selector(doSomething)].
Finally, there's always the route of NSNotificationCenter (not to be confused with push notifications) is a class that provides an inter-object messaging system. Many objects can register for a message that any other object can send.
Please see the Apple's documentation for more information. Click Here.
Regardless of the fact that this is OS X documentation, it's still quite good at explaining things visually: click here.
Here's an example of simply changing the name of the delegate property: click here
And here's an example of adding another protocol and a second delegate: click here
Finally, here's an example that builds on the previous two and has a third delegate that also conforms to the same protocol: click here

Using NSProgressIndicator inside an NSMenuItem

I'm trying to use a NSProgressIndicator (indeterminate) inside of a statusbar-menu. I'm using an NSView-object as view for the menuitem, and then subviews the progress indicator to display it. But whenever i try to call the startAnimation: for the progress, nothing happens. When i try do the very same thing on a normal NSWindow it works perfectly, just not when inside a menuitem.
I'm new to both cocoa and objective-c so I might've overlooked something "obvious" but I've searched quite a bit for a workaround but without success. I found something about menuitems cant be updated while shown and that you need to use a bordeless window instead. But I have not been able to confirm this in any documentation.
Edit:
Ok, almost works now. When using the setUsesThreadedAnimation: and from a MenuDelegate's menuWillOpen and creating a new thread. This thread runs a local method:
-(void) doWork(NSProgressIndicator*) p{
[p startAnimation:self];
}
This will start the progressindicator on a random(?) basis when opening the menu. If I call startAnimation: directly without going through doWork: (still using a new thread), it never works. Doesn't setUsesThreadedAnimation: make the progress-bar create it's own thread for the animation?
Solved it by using:
[progressIndicator performSelector:#selector(startAnimation:)
withObject:self
afterDelay:0.0
inModes:[NSArray
arrayWithObject:NSEventTrackingRunLoopMode]];
Inside the menuWillOpen:, the problem seems to have been calling startAnimation: before the progressbar was finished drawing itself.
How are you referencing the NSProgressIndicator that is in the view (and the one in the window, for that matter)? For example, do you have a controller class that has IBOutlet's hooked up to the progress indicators? If you are using an IBOutlet, are you sure it's hooked up properly in the nib file?
Also, where and when are you calling startAnimation:? (We need to see some code).
One thing that can sometimes happen is that you forget to hook up an IBOutlet in the nib. Then, when you attempt to tell the object to do something in code at runtime, the IBOutlet is nil, and so what you think is a message being sent to your object is in fact, a message being sent to nil. In other words, it's just ignored, and effectively looks like it's not working.
Provided you do have a (potentially) valid reference to the UI object, the other common issue you'll see is when a developer is trying to send a message to the object at "too early" of a time. In general, init methods are too early in the controller object's lifetime to be able to send messages to user interface objects—those IBOutlet's are still nil. By the time -awakeFromNib is called, IBOutlet's should be valid (provided you hooked them up in IB) and you can then send the message to the UI object.
Have you told it to use threaded animation via -setUsesThreadedAnimation:?

Programmatically creating new windows and accessing window objects in Cocoa

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.