Proper way of showing a NSWindow as sheet - cocoa

Function NSBeginAlertSheet(...) has all the events I need to have especially the didDismiss: callback, but I really need to be able to do the same sheet action with any window I want, so I discovered this notification:
NSWindowDidOrderOffScreenAndFinishAnimatingNotification
Which is posted whenever a sheet is closed AND done with animations
now, my question is can I use that? Or is there a better way?
I use ARC and I load the windows from .xib using NSWindowController.
Overall what I need is to show a window as sheet and catch all events.

What's wrong with
- (void)beginSheet:(NSWindow *)sheet modalForWindow:(NSWindow *)docWindow modalDelegate:(id)modalDelegate didEndSelector:(SEL)didEndSelector contextInfo:(void *)contextInfo
This calls the optional didEndSelector which should look like this:
- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
This is all in the NSApplication documentation. There are two methods for ending the sheet:
- (void)endSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode
- (void)endSheet:(NSWindow *)sheet
So you could just do whatever you wanted to right before calling endSheet: or you could in the sheetDidEnd: method.
Edit:
Here is an example project showing that after calling [window orderOut:self] then animation is finished and you can do what you'd like.

NSWindowDidEndSheetNotification It is posted whenever a sheet is finished animating out.

Starting with 10.9, the correct way is to call beginSheet:completionHandler: on a NSWindow object.
This method has the advantage, that the completion handler is a block, so it can keep all the objects alive that are required as long as the sheet is still displayed and once the sheet is done and the block has been executed, the block itself is released and thus all objects it was keeping alive are as well.
To make sure a block keeps objects alive, use the objects within that block or if there is no way to use them in a meaningful fashion, put all of them into a NSMutableArray and within the block call removeAllObjects on that array; this requires the block to keep the array alive and the array keeps the rest alive -> memory management made easy.

Related

What is the difference between makeFirstResponder and becomeFirstResponder?

I just spent ages trying to work out how to keep the focus in an NSTableView column after deleting a row, rather than just keeping a selection. I did it like this:
[[myTableView window]makeFirstResponder:myTableView];
Why does the code above work, but the code below doesn't?
[myTableView becomeFirstResponder];
-makeFirstResponder: is a request to the window that it make the specified responder its first responder. -becomeFirstResponder is a notification to a responder that it is about to become the first responder. It doesn't inherently cause a state change; it gives the receiver a chance to react to a state change that was caused by -makeFirstResponder:.
You should not call -becomeFirstResponder (except, possibly, to call through to super in an override). The framework calls it as necessary.

NSArrayController rearrangeObjects error

I have troubles with NSArrayController rearrangeObjects function - this function called from some background treads and sometimes App crashes with error: 'endUpdates called without a beginUpdates'.
How can i detect if arrayController is rearranging objects in current moment and add next rearrange to some like queue or cancel current rearranging and run new?
May be there is another solution for this problem?
Edit code added:
TableController implementation:
- (void)setContent{//perfoms on main thread
//making array of content and other functions for setting-up content of table
//...
//arrayController contains objects of MyNode class
//...
//end of setting up. Call rearrangeObjects
[arrayController rearrangeObjects];
}
- (void)updateItem:(MyNode *)sender WithValue:(id)someValue ForKey:(NSString *)key{
[sender setValue:someValue forKey:key];
[arrayController rearrangeObjects];//exception thrown here
}
MyNode implementation:
- (void)notifySelector:(NSNotification *)notify{
//Getted after some processing finished
id someValue = [notify.userInfo objectForKey:#"observedKey"];
[delegate updateItem:self WithValue:someValue ForKey:#"observedKey"];
}
Don't do that. AppKit (to which NSArrayController belongs) is not generally thread safe. Instead, use -performSelectorOnMainThread:... to update your UI (including NSArrayController). ALWAYS do updating on the main thread.
Joshua and Dan's solution is correct. It is highly likely that you are performing operations on your model object in a background thread, which then touches the array controller, and hence touches the table.
NSTableView itself is not threadsafe. Simply adding in a "beginUpdates/endUpdates" pair will just avoid the race condition for a bit. However, like Fin noted, it might be good to do the pair of updates for performance reasons, but it won't help with the crash.
To find the sources of the crash, add some assertions in your code on ![NSThread currentThread].mainThread -- particularly any places before you touch the array controller's content. This will help you isolate the problem. Or, subclass NSTableView and add the assertion in somewhere key, like overriding -numberOfRows (which is called frequently on changes), and calling super.
-corbin
AppKit/NSTableView
I solved this by adding this at the very start of my UI initialisation
[myTableView beginUpdates];
and then at the end after the persistant store has been loaded fully:
[myTableView endUpdates];
This also make the app startup a lot better since it does not constantly have to reload all loads from the MOC.

Cannot Save in a Document-Based Application

I created new Document-Based-App.
I implemented dataOfType in subclass of NSDocument
- (NSData*) dataOfType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
return [NSKeyedArchiver archivedDataWithRootObject:bcmwc.bindingsController.arrangedObjects];
}
in xib http://i.minus.com/iH2Rj9v5oOhTn.png
When I click "Save" from menu nothing's gonna happen, any errors in console.
I set a breakpoint in dataOfType, and when I clicked "Save", application didn't stop.
Any suggestions?
EDIT
I think it may be connected with fact I use custom NSWindowController, and custom xib.
I made a test when I load custom xib, everything is fine dataOfType method is invoked etc..
Should I connect in some way my custom xib (window) with subclass of NSDocument?
It looks like your Save menu item is connected properly, so let's concentrate on the code (+1 for having posted it in the first place).
You do nothing in your code to ensure -archivedDataWithRootObject: returns valid data or set an error if it doesn't. My best guess (because you haven't provided nearly enough detail to do anything but guess) is that you're returning nil because your call to -archivedDataWithRootObject: is doing the same. Check to see if you get valid data and set the outError if not.
Why would you get nil? Perhaps one or more of the objects in the object graph created by archiving your array controller's -arrangedObjects array isn't <NSCoding> compliant. This is likely the case if your array controller holds objects of a custom class you created rather than a standard Property List container.
Read the Archives and Serializations Programming Guide (in particular, the Encoding and Decoding Objects section) to learn how to make your custom class <NSCoding> compliant so that it knows how to serialize itself (write itself into a byte stream suitable for NSKeyedArchiver, etc. and create an instance of itself from such a byte stream).
Also, you really need to learn to use the debugger. You're groping around in a dark cave with lots of pitfalls and no flashlight without it. Try setting a breakpoint in methods you expect to be called. If they're not called, you can check outlets/actions, etc. If they are, you can step through each line and make sure everything is executing as you expected. If you write a little more verbose code, you can more easily inspect the results when paused in the debugger. Two lines in your case will help you more than one:
- (NSData*) dataOfType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
NSData * myData = [NSKeyedArchiver archivedDataWithRootObject:bcmwc.bindingsController.arrangedObjects];
// You should be handling nil (setting a descriptive error) here if (!myData)...
return data; // breakpoint here; you should now see myData is likely nil
}

- (void)scrollWheel:(NSEvent)theEvent does not work

I wrote an app in cocoa with a window document with a PDFView inside. I am trying to intercept scrollWheel: events in the PDFView but for some reason that method is never passed to the PDFView. Instead the view scrolls down or up depending on your perspective.
scrollWheel: is part of the responder chain so I expect the method to be called but it doesn't.
Does anyone know why?
If your PDFView is inside a scroll view, the scroll view is likely eating the -scrollWheel: event. If not, then you need to make sure your PDFView subclass accepts first responder:
-(BOOL)acceptsFirstResponder
{
return YES;
}
If that doesn't work, then you'll need to provide more detail about what you're trying to accomplish and how you're trying to accomplish it (ie, post your code).

Where do you put cleanup code for NSDocument sub-classes?

I have a document-based application and I have sub-classed NSDocument and provided the required methods, but my document needs some extensive clean-up (needs to run external tasks etc). Where is the best place to put this? I have tried a few different methods such as:
close
close:
canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo
dealloc
If I put it in dealloc, sometimes it gets called and other times it does not (pressing Command+Q seems to bypass my document's deallocation), but it is mandatory that this code gets called without failure (unless program unexpectedly terminates).
Have each document add itself as an observer in the local notification center for NSApplicationWillTerminateNotification. In its notification method, call its clean-up method (which you should also call from dealloc or close).
The correct answer here didn't fit my use case, but the question does. Hence the extra answer.
My use case: closing a document (which may be one of several that are open) but not closing the application.
In this case (at time of writing and unless I'm just looking in the wrong place) the documentation is not as helpful as it could be.
I added a canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo: override in my NSDocument subclass and called super within it. The documentation doesn't say whether you must call super, but a bit of logging shows that the system is providing a selector and a context. This method is called just before the document is closed.
- (void) canCloseDocumentWithDelegate:(id)delegate shouldCloseSelector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo;
{
if ([self pdfController])
{
[[[self pdfController] window] close];
[self setPdfController: nil];
}
[super canCloseDocumentWithDelegate:delegate shouldCloseSelector: shouldCloseSelector contextInfo: contextInfo];
}
There is some useful discussion of this method on CocoaBuilder. If there's downsides to this approach or better ways of doing this, please comment.

Resources