Tuesday, June 30, 2009

Safer IFs

I keep trying to use this form:

 if (NSNotFound == index) {

rather than the old:

 if (index == NSNotFound) {

but I keep typing the older form. Gotta teach this old dog a new trick.

P.S. The benefit is that you can't accidentally type:

 if (index = NSNotFound) {

Tuesday, May 27, 2008

Bindings in Cocoa UIs

Using bindings to simplify Cocoa UI implementation is pretty standard stuff these days. And binding a UI control to a preference value is pretty simple too - very common for preference panels.

But occasionally I've need to have my code do something when the preference value changes and Key-Value observing seemed like the obvious way to make this work. Unfortunately I didn't know the right incantations and I'd end up needing to add an action method that was triggered when the user interacted with the control. It worked, but it seemed like I was mixing metaphors and there should be a nicer way to deal with this situation.

It turns out that there is a nice way to live wholly in a bindings world. NSUserDefaultsController provides -sharedUserDefaultsController which returns the right controller to add the observer to. Good so far.

But there is one further little wrinkle to make this work. While you might think that the keyPath you are observing would be the usual key string used to reference the preference, but that isn't right. You need to prefix your key with "values.".

Putting these two pieces of the puzzle together gives you code that looks something like the following:


// use this in your init routine
[[NSUserDefaultsController sharedUserDefaultsController] addObserver: self
forKeyPath: @"values.MyPreferenceKey"
options: 0
context: NULL];

along with implementing this method:

// implement this method to handle the notifications
- (void) observeValueForKeyPath: (NSString *) keyPath
ofObject: (id) object
change: (NSDictionary *) change
context: (void *) context
{
if ([keyPath isEqualToString:@"values.MyPreferenceKey"]) {
// do something in response to that change
}
}

and of course, don't forget to remove this later:

- (void) dealloc
{
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver: self
forKeyPath: @"values.MyPreferenceKey"];
[super dealloc];
}

Tuesday, August 28, 2007

NSImageView and image filenames

One of my pet peeves with the cocoa class NSImageView is that there is no way to directly get the path of image files you drop on this view.

Below is my cure for this. It overrides two methods to do its work.

First it overrides -performDragOperation to grab the filename from the draggingPasteboard, squirreling it away in an ivar for later access.

This may be sufficient for many uses, especially if you can easily access the view from you code. In my last use of this code, I was using bindings, so I wanted the filename to be available to me when -observeValueForKeyPath was called with a keyPath of "selection.image". I didn't have any reference to the NSImageView at that point, so I have the code set the image's name to be the path string. That makes it easy enough to access the image's name later when needed.

Note: this code sets the image's name in the -setImage method because trying to set the name of the view's image inside -performDragOperation results in setting the name for the old image (the one being replaced), rather than the new one.

@interface MyImageView : NSImageView
{
NSString *mImagename;
}
@end

@implementation MyImageView

- (void)setImage:(NSImage *)image
{
[image setName:[[mImagename lastPathComponent] stringByDeletingPathExtension]];
[super setImage:image];
}

- (BOOL)performDragOperation:(id )sender
{
BOOL dragSucceeded = [super performDragOperation:sender];
if (dragSucceeded) {
NSString *filenamesXML = [[sender draggingPasteboard] stringForType:NSFilenamesPboardType];
if (filenamesXML) {
NSArray *filenames = [NSPropertyListSerialization
propertyListFromData:[filenamesXML dataUsingEncoding:NSUTF8StringEncoding]
mutabilityOption:NSPropertyListImmutable
format:nil
errorDescription:nil];
if ([filenames count] >= 1) {
mImagename = [filenames objectAtIndex:0];
} else {
mImagename = nil;
}
}
}
return dragSucceeded;
}

@end

Tuesday, August 21, 2007

Picking up PageSetup Dialog Changes

The standard Page Setup dialog (OK, "sheet") can be customized by implementing -runPageLayout in your NSApplication or NSDocument subclass. It's all nicely documented in Apple's fine Developer Docs.

But what if the standard dialog is fine for your needs but you still want to run some code when the user changes the page setup?

All you need to do is implement -setPrintInfo in your NSDocument subclass. It'll get called with the new printInfo. Something like this:

- (void) setPrintInfo:(NSPrintInfo*)info
{
[super setPrintInfo:info];
[self myCodeThatDoesSomethingWithTheNewPrintInfo];
}

Wednesday, April 18, 2007

Automatically reopen a document on launch

A handy technique during development is to have an application automatically open the last document it used each time it's launched.


It's just that much faster to get back running the app after making a change.


Add this method to the app delegate:


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSArray *urls = [[NSDocumentController sharedDocumentController] recentDocumentURLs];
if ([urls count] > 0) {
[[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:[urls objectAtIndex:0]
display:YES];
}
}

Tuesday, March 6, 2007

if blocks

I've never understood why some programmers continue to write C code in the form:


if (someCondition)
doSomething();
else
doSomethingElse();

as opposed to


if (someCondition) {
doSomething();
} else {
doSomethingElse();
}

Beyond saving a couple of keystrokes, the first example has absolutely no advantage over the second. While the second is much more bullet proof.

When working on code written by others that cling to the first form, I continue to run across code like this:


if (someCondition)
doSomething();
doMore();
doOtherthings();

Where the intention is that doMore() is intended to be executed only when someCondition is true.

When you look at the history of the code it almost always starts out as


if (someCondition)
doSomething();
doOtherthings();

and then later the doMore() is added.

Please, please, please take the extra split second it takes to type in the safe form of the if from day one.

Saturday, February 17, 2007

Timing operations

I'll sometimes want to measure the time a certain operation takes and compare it with another operation or to compare it with another implementation of the same operation. Here's the fragment of Cocoa code I use to do that:

NSDate *startTime = [NSDate date];

{ ... } // the operation that I'm timing

NSTimeInterval elapsedTime = -[startTime timeIntervalSinceNow];

NSLog(@"That took %f seconds", elapsedTime);

This code grabs the current time using NSDate, does something, calculates the time spent doing the operation, and then logs the result.