I have a Cocoa document based picture editing application. As usual, my application has both File -> Save menu and File -> Save As menu.
File -> Save menu is linked to saveDocument: in NSDocument subclass
File -> Save As menu is linked to saveDocumentAs: in NSDocument subclass
In both cases, on a successful save, I want to present a NSAlert sheet to user saying that the save was successful and this sheet also presents the user with an option to upload the document to Facebook etc.
How do I know, that the document got saved succesfully?
I understand that in case of File -> Save As I can create a new action method mySaveDocument: and invoke
saveDocumentWithDelegate:didSaveSelector:contextInfo:
from mySaveDocument: but what should I do for File -> Save As ?
In your NSDocument subclass, override:
- (BOOL)saveToURL:(NSURL *)absoluteURL
ofType:(NSString *)typeName
forSaveOperation:(NSSaveOperationType)saveOperation
error:(NSError **)outError
{
BOOL success = [super saveToURL:absoluteURL
ofType:typeName
forSaveOperation:saveOperation
error:outError];
if (success) {
…
}
return success;
}
This method is called whenever a document is saved.
For more information on what happens when a document is saved, read the Message Flow in the Document Architecture page of the Document-Based Applications Overview document.
Related
My app observes the document property of its NSWindowController and performs some UI setup when it is set. Once it has been set, it would be difficult to rebuild the UI (for internal reasons) as the result of a change.
Once an NSWindowController has set its document property to an opened document, under what conditions will the system ever change that property to a new NSDocument instance (i.e., will the document ever be swapped out)? I've never observed it happening, but I can imagine features like versions or iCloud syncing causing the window controller's document to get swapped for a new document. However, the documentation on the NSWindowController lifecycle doesn't seem to touch on the issue.
It doesn't change once it has been set. NSWindowController gets its document by addWindowController method. Every new/opened document creates it's own windowController. Instance of document doesn't change with iCloud or revertChanges. It is up to you how to synchronise document with its views (redrawing).
/* Create the user interface for this document, but don't show it yet.
The default implementation of this method invokes [self windowNibName],
creates a new window controller using the resulting nib name (if it is not nil),
specifying this document as the nib file's owner, and then invokes [self addWindowController:theNewWindowController] to attach it. You can override
this method to use a custom subclass of NSWindowController or to create more
than one window controller right away. NSDocumentController invokes this method
when creating or opening new documents.
*/
// e.g. override
- (void)makeWindowControllers
{
if ([[self windowControllers] count] == 0) {
MainWindowController *controller = [[MainWindowController alloc] init];
[self addWindowController:controller];
}
}
How do I implement the view in following image.
The view which appears when + button is clicked in System Preferences > Network
I have following questions:
Does this view system has a specific name (like popover), because I have seen it in many places in Mac.
How to implement it in IB ?
Can this be done in a popover window instead of NSWindow ?(or is it only possible in NSWindow like toolbar)
Update:
Updating the title for better visibility
In Cocoa these are called sheets. Take a look at the sheet programming guide, however, this is terribly out of date!
You need to call -beginSheet:completionHandler: on the window you want to display the sheet. If you have single-window application you can ask the AppDelegate for the window and launch the sheet like so,
// This code should be in AppDelegate which implement the -window method
NSWindow *targetWindow = [self window]; // the window to which you want to attach the sheet
NSWindow *sheetWindow = self.sheetWindowController.window // the window you want to display at a sheet
// Now start-up the sheet
[targetWindow beginSheet:sheetWindow completionHandler:^(NSModalResponse returnCode) {
switch (returnCode) {
case NSModalResponseCancel:
NSLog(#"%#", #"NSModalResponseCancel");
break;
case NSModalResponseOK:
NSLog(#"%#", #"NSModalResponseOK");
break;
default:
break;
}
}];
You will notice that when the sheet completes it will return a certain modal response --- we will return to this point in a shortly.
Next you need to implement the content that you want to display in the sheet; this must be done in an NSWindow. I find it much easier to use a NSWindowController and implement the window in a separate XIB file. For example, see below,
Now you need to implement the code in your custom NSWindowController (or plain NSWindow if you are old-school and love to manage your own NIB loading) which will issue the correct modal response. Here I have hooked up the cancel and OK buttons to the following actions methods,
- (IBAction)cancelButtonAction:(id)sender {
[[[self window] sheetParent] endSheet:self.window returnCode:NSModalResponseCancel];
}
- (IBAction)OKButtonAction:(id)sender {
[[[self window] sheetParent] endSheet:self.window returnCode:NSModalResponseOK];
}
The model response will get sent to your completion handler block.
Sample project on github.
I'm writing a Cocoa app using the document architecture. Whenever an untitled document is created in this app, the user should be shown a window that lets them pick a template and prompts for other information. Only one of these windows should show up at a time, and preferably it should be possible to interact with the rest of the application while the template chooser is visible. (This is how Pages behaves.)
I've got most of this working by overriding -[NSDocumentController openUntitledDocumentAndDisplay:error:]:
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument
error:(NSError *__autoreleasing *)outError {
TsDocument * doc = [self makeUntitledDocumentOfType:self.defaultType
error:outError];
if(!doc) {
return nil;
}
TsNewWindowController * newController = [TsNewWindowController new];
newController.document = doc;
if([NSApp runModalForWindow:newController.window] == NSRunAbortedResponse) {
if(outError) {
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
code:NSUserCancelledError
userInfo:nil];
}
return nil;
}
[self addDocument:doc];
if(displayDocument) {
[doc makeWindowControllers];
[doc showWindows];
}
return doc;
}
However, as you can see, the window is displayed modally, blocking access to the rest of the app. Is there a simple way to achieve what I want without making the template picker modal?
To explain a couple things more clearly:
I do of course know that -runModalForWindow: will run the window modally—it's right there in the name! I'm looking for another way to display the window that will still block -openUntitledDocumentAndDisplay:error:, or that doesn't require me to block the method at all.
I don't believe I can simply create the document, show the newController's window, and call the document's makeWindowControllers and showWindows later because, if the app quits, Restore will not show the template chooser—it shows the normal editing interface.
You do need to create and use a NSWindowController, but you need to do so before openUntitledDocument…:: is called.
In the unreleased Adium Xtras Creator, I tapped in at several points:
In the application's delegate, in applicationOpenUntitledFile:, I show the template-chooser window and return YES.
In the document controller, in removeDocument:, I pass the message on to super, then check whether there are still any documents open. If not, I show the template-chooser window.
In the document controller, in addDocument:, I hide the template-chooser window, then pass to super.
Thus:
If the user tries to create a new document (of no particular type) by any means, the template-chooser will be shown instead.
If the user creates a new document (of an explicit type) by any means, the template-chooser will be hidden. (The application I did this in had its “New” menu item set up as a submenu containing specific types.)
If the user opens any document by any means, the template-chooser will be hidden.
If the user closes the last open document, the template-chooser will be shown.
If the user or another application tries to reopen the application by any means, the template-chooser will be shown.
You are calling runModalForWindow: so of course it's running the window as a modal window.
Why not just show the window instead? Use an NSWindowController and call showWindow: to display the template window. In your window controller, implement actions that react to the user's selection and then create the appropriate document (or cancel).
I don't think you need to actually create a document in openUntitledDocumentAndDisplay:error:.
I am using a custom subclass of NSDocument and a custom subclass of NSWindowController. The problem is that I cannot reference my custom document from my custom window controller.
In IB, in the TKDocument NIB I have File's Owner set to TKWindowController.
In my TKDocument subclass I have:
- (void) makeWindowControllers {
TKWindowController *controller = [[TKWindowController alloc] init];
[self addWindowController:controller];
}
Then in my TKWindowController subclass I overrode setDocument to make sure it was being called:
- (void) setDocument(NSDocument *) document {
NSLog(#"setDocument:%#", document);
[super setDocument:document];
}
and then (again in TKWindowController) my action which references the document itself:
- (IBAction) plotClicked:(id) sender {
TKDocument *doc = [self document];
NSLog(#"plotClicked %#", doc);
}
The NSLog in setDocument outputs the string returned by my [TKDocument description] override as I'd expect; I only put it there to see if it was being called. However, doc in plotClicked is null.
What might I have done wrong?
EDIT: I believe the problem is to do with NIBs. My Document has its own NIB with File's Owner set to the custom controller as mentioned above. The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.
So, how do I link the two? My question is really this: How do I obtain a handle to the current active document (or its windowcontroller) from MainMenu.xib?
Thanks
My Document has its own NIB with File's Owner set to the custom controller as mentioned above.
The File's Owner of a document nib should be the document. Consider that suspect #1.
The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.
Did you put a window controller inside your main menu nib? If not, then that isn't the problem, since you must have wired up your plotClicked: menu item to the First Responder, and the window controller and its document will be in the responder chain.
If you did, then there's the solution: delete the window controller from the MainMenu nib and hook up your menu item to the First Responder, so that the action message goes down the responder chain, which will enable it to hit the document or window controller.
How do I obtain a handle to …?
The only Handles on the Mac come from Carbon; those Handles do not exist in Cocoa.
init is not a designated initializer of NSWindowController. You want one of these: – initWithWindow:, – initWithWindowNibName:, – initWithWindowNibName:owner:, or – initWithWindowNibPath:owner:.
Also, from the docs:
In your class’s initialization method,
be sure to invoke on super either one
of the initWithWindowNibName:...
initializers or the initWithWindow:
initializer. Which one depends on
whether the window object originates
in a nib file or is programmatically
created.
I need to create several windows before NSDocument is loaded, or create a window that blocks NSDocument window and top menu.
I tried several solutions - but they didn't work right.
modal window, one after another. there were some problems with Async URLConnection, and some other problems with my NSDocument content.
I created custom MainMenu.xib with no menu, that opens my preinitialize windows.
here i found some other problems, when a file(associated with my application) is opened - the Document Window initializes. Here i tried to subclass NSDocumentController, but i found no way to pause the "open document". (i want the document to be opened anyway, but only after the preinitalize windows would be closed).
So what is the right way to do this?
Implement applicationShouldOpenUntitledFile: in your app delegate to return NO if the user has to go through the not-registered-yet dialog first.
In the action methods for your “Trial” and “Confirm Registration” buttons, create the untitled document yourself (by sending the necessary message to the document controller).
So the right answer is to implement:
* application:openFiles:
* applicationShouldOpenUntitledFile:
and implement your own document creation. this is the way it worked for me.
MyDocument* document = [[MyDocument alloc]
initWithContentsOfURL:fileURL
ofType:[fileName pathExtension]
error:nil
];
if(document)
{
[[NSDocumentController sharedDocumentController] addDocument:document];
[document makeWindowControllers];
[document showWindows];
}
of course you need to write error handling code.