Delaying the shutdown sequence

If the computer is in the process of shutting down, you can delay the shutdown if you need to say, put up a dialog box. If your app has an unsaved document, you need to give the user an opportunity to save, not save, or cancel the shutdown outright. There may be other reasons you need to delay the shutdown. For example, I have a simple program which displays a dialog box reminding my wife and son to turn off the wireless mouse when they shutdown. The dialog doesn’t have an OK button so it can’t be dismissed. Instead, I use a timer and a delegate function -(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender.
When the app is told to shutdown, the delegate function is called and you have three return options:
NSTerminateNow – which allows the app to quit immediately and the shutdown to continue
NSTerminateCancel – which cancels the shutdown sequence immediately (You’ll get the user cancelled dialog box)
NSTerminateLater – which delays the shutdown sequence for a bit, giving you time to do something, such as putting up a dialog box. If the delay is too long, the shutdown will eventually be canceled. NSTerminateLater also causes your app to be placed a new runloop mode, NSModalPanelRunLoopMode. Now, if you want to use a timer to dismiss the dialog you’ve put up, such as in my app, you have to take care to place your timer in the NSModalPanelRunLoopMode, otherwise, your timer will never fire. There are 4 class methods for creating an NSTimer:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
invocation:(NSInvocation *)invocation repeats:(BOOL)repeats

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds
invocation:(NSInvocation *)invocation repeats:(BOOL)repeats

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds
target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

The first two, automatically place the NSTimer in the default runloop, so if you use either of these in this situation, the timer will never fire. Use one of the last two and then you’ll need to manually place the timer in the correct runloop, ala,

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode];

Of course, when you initialize your timer, you need to give it a method to invoke. Within the body of that method, you can just this send this message to NSApp passing YES as the lone parameter:

- (void)replyToApplicationShouldTerminate:(BOOL)shouldTerminate

I have a sample app here. (The crash issue is now fixed)

One other thing to note is the new “Sudden Termination” feature of Snow Leopard (10.6). To speed up shutdown/restart, the OS will immediately terminate an application if it allows for sudden termination. You can read the documentation here in the:

NSProcessInfo docs.

The upshot of this is that if your app should not abruptly quit (in Snow Leopard), you need to disable sudden termination. You can either do this in code ([[NSProcessInfo processInfo] disableSuddenTermination]) or in the app’s plist (NSSupportsSuddenTermination NO). Whatever route you choose, you can re-enable sudden termination in code via [[NSProcessInfo processInfo] enableSuddenTermination].

That’s it.

Capitalize first character of an NSString

While localizing Weather Vane, I found that the non-English forecasts returned from AccuWeather are always returned with the forecast in all lower case. The English forecast returns a string with the first character in the string capitalized; like a sentence. Makes sense to me, so I thought I’d just find the method in NSString that would do that for me. Well, there isn’t one, so what did I do? Category time! Here’s what I did:

-(NSString*) stringWithSentenceCapitalization

NSString *firstCharacterInString = [[self substringToIndex:1] capitalizedString];
NSString *sentenceString = [self stringByReplacingCharactersInRange:NSMakeRange(0,1) withString: firstCharacterInString];

return sentenceString;

The only gotcha here, if there is one, is that

(NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement

is Leopard (10.5) and above only, so if you need to support 10.4 (as I do), you need to do something else.

Here’s what I did in Weather Vane:


// Get the first character in the string and capitalize it.
NSString *firstCapChar = [[str substringToIndex:1] capitalizedString];

NSMutableString * temp = [str mutableCopy];

// Replace the first character with the capitalized version.
[temp replaceCharactersInRange:NSMakeRange(0, 1) withString:firstCapChar];

return [temp autorelease];

As always, if you find a better way, please let me know.