This blog has moved to rhult.github.com

Keyboard shortcuts in Xcode and Interface Builder

Wednesday, April 29, 2009
If you, like me, are trying to avoid using the mouse (for ergonomic reasons) as much as possible, you probably already have noticed that the Mac is quite alright when it comes to keyboard accessibility. This includes Xcode and Interface Builder, even though the latter by nature requires quite a bit of mouse wrestling. There are also some nice features that can help you having to type less.

Recently, I have been trying to collect the most useful keyboard shortcuts and really learn them so I'm not tempted to use the mouse more than necessary. Here's the list so far:

Xcode
Besides all the normal text editing shortcuts, I often use those:

(⇧ = shift, ⌘ = cmd, ^ = ctrl, ⌥ = option)


  • ⇧⌘E = "Zoom" the editor, hide the list above it

  • ⇧⌘D = Open quickly, very useful for quickly open any file, in the project our elsewhere

  • ^⌘T = Edit all in scope, this saves a lot of tedious editing

  • ^/ = Next placeholder in completions

  • ^. = Toggle between completions

  • ⌥⌘-Up = Toggle between the header/source

  • ^1 = Pop up the file history menu, useful when navigating the project files


The shortcut ^. deserves some extra attention, as it also completes text macros which can save a lot of typing. There is a whole slew of macros that you can use, just a few examples:

  • pim = expands to #import "file", highlighting file so you can change it easily

  • a = expands to the standard alloc/init combination

  • init = expands to a standard init skeleton

  • dealloc = expands to the standard dealloc skeleton



Interface Builder
Interface builder also has a few ones I often use:

  • ⌘ while resizing a window = live autoresizing

  • ^⌘ + up/down = select parent/child of selected view

  • ^⌘ + left/right = select previous/next sibling



Global Shortcuts
Finally I have a tiny list of desktop wide shortcuts (obviously in addition to the well known ones like ⌘-Tab etc) I sometimes find useful:

  • ^F2 = Focus the application menu

  • ... I said it's tiny!



I hope this can be useful for others as well.

Adding a preference to launch your app on login using the Shared File List API

Thursday, April 16, 2009
I wanted to make my app launch automatically on login, and looked around for ways to do that programmatically. I first found two ways to do it, both a little bit less than ideal: either using Apple Events to talk to System Events, or by using NSUserDefaults (or CFPreferences) to tweak values in the persistent domain "loginwindow" (as opposed to the app's own persistent domain where the preferences for the app are stored). The Apple Events way seemed a bit hackish to me, as did the NSUserDefaults way, considering it meant poking at preferences you don't "own".

Nonetheless, I decided to try the latter, so I went ahead and implemented a simple controller that exposed only one property that I could then bind to a check box button in Interface Builder using Cocoa bindings:

@property BOOL launchOnLogin;


To show more clearly what I did, here's the part that reads the current login items:

NSUserDefaults *defaults;
NSDictionary *domain;

defaults = [NSUserDefaults standardUserDefaults];

domain = [defaults persistentDomainForName:@"loginwindow"];
if (domain) {
NSArray *items = [domain objectForKey:@"AutoLaunchedApplicationDictionary"];

for (NSDictionary *entry in items) {
NSString *entryPath = [entry objectForKey:@"Path"];
// ... do something with the entry ...
}
}


The writing part is similar, getting a copy of the existing array, then adding or removing our own item and then setting as the new array:

[domain setObject:updatedItems forKey:@"AutoLaunchedApplicationDictionary"];

// Update the setting.
[defaults setPersistentDomain:domain forName:@"loginwindow"];

// Write changes to disk so the system settings will be up to date
// if opened before the changes are automatically synced.
[defaults synchronize];


While this approach works in general, there's a problem: the login items list in the system settings panel is not updated when changing the setting in my app's preferences, and the other way around. Maybe not a big problem, but it made me search some more, which led me to the following note in the release notes for the Launch Services framework in Leopard:

The Shared File List API is new to Launch Services in Mac OS X Leopard. This API provides access to several kinds of system-global and per-user persistent lists of file system objects, such as recent documents and applications, favorites, and login items. For details, see the new interface file LSSharedFileList.h.


The mentioned header file is the only documentation for this new API, but it is quite straight-forward: you create an instance of the right list, in this case the login items list for the session. Then there is a bunch of things you can do with the list, what's interesting for our use case is to get a copy of the array in the list, to add or remove an item, or to register an observer callback for a list. The callback is called when the list changes, which makes it possible for the UI to update accordingly.

Since my app already depends on Leopard, I decided to rewrite my code using LSSharedFileList. The UI was already set up to use the simple controller described above, so a quick rewrite of the controller was enough. I create the list in my init method and add an observer:

loginItemsListRef = LSSharedFileListCreate(NULL,
kLSSharedFileListSessionLoginItems,
NULL);
if (loginItemsListRef) {
// Add an observer so we can update the UI if changed externally.
LSSharedFileListAddObserver(loginItemsListRef,
CFRunLoopGetMain(),
kCFRunLoopCommonModes,
loginItemsChanged,
self);
}


The matching tearing down is done in dealloc:

if (loginItemsListRef) {
LSSharedFileListRemoveObserver(loginItemsListRef,
CFRunLoopGetMain(),
kCFRunLoopCommonModes,
loginItemsChanged,
self);
CFRelease(loginItemsListRef);
}


The callback for the observer is a plain old C funtion:

static void
loginItemsChanged(LSSharedFileListRef listRef, void *context)
{
LoginItemsController *controller = context;

// Emit change notification for the bindnings. We can't do will/did
// around the change but this will have to do.
[controller willChangeValueForKey:@"launchOnLogin"];
[controller didChangeValueForKey:@"launchOnLogin"];
}


The rest of the code just needs to check if our app bundle is listed in the login items list in the getter for the property, and add it or remove it in the setter:

// Get an NSArray with the items.
- (NSArray *)loginItems
{
CFArrayRef snapshotRef = LSSharedFileListCopySnapshot(loginItemsListRef, NULL);

// Use toll-free bridging to get an NSArray with nicer API
// and memory management.
return [NSMakeCollectable(snapshotRef) autorelease];
}

// Return a CFRetained item for the app's bundle, if there is one.
- (LSSharedFileListItemRef)mainBundleLoginItemCopy
{
NSArray *loginItems = [self loginItems];
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];

for (id item in loginItems) {
LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)item;
CFURLRef itemURLRef;

if (LSSharedFileListItemResolve(itemRef, 0, &itemURLRef, NULL) == noErr) {
// Again, use toll-free bridging.
NSURL *itemURL = (NSURL *)[NSMakeCollectable(itemURLRef) autorelease];
if ([itemURL isEqual:bundleURL]) {
CFRetain(item);
return (LSSharedFileListItemRef)item;
}
}
}

return NULL;
}

- (void)addMainBundleToLoginItems
{
// We use the URL to the app itself (i.e. the main bundle).
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];

// Ask to be hidden on launch. The key name to use was a bit hard to find, but can
// be found by inspecting the plist ~/Library/Preferences/com.apple.loginwindow.plist
// and looking at some existing entries. Thanks to Anders for the hint!
NSDictionary *properties;
properties = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:@"com.apple.loginitem.HideOnLaunch"];

LSSharedFileListItemRef itemRef;
itemRef = LSSharedFileListInsertItemURL(loginItemsListRef,
kLSSharedFileListItemLast,
NULL,
NULL,
(CFURLRef)bundleURL,
(CFDictionaryRef)properties,
NULL);
if (itemRef) {
CFRelease(itemRef);
}
}

- (void)removeMainBundleFromLoginItems
{
// Try to get the item corresponding to the main bundle URL.
LSSharedFileListItemRef itemRef = [self mainBundleLoginItemCopy];
if (!itemRef)
return;

LSSharedFileListItemRemove(loginItemsListRef, itemRef);

CFRelease(itemRef);
}

#pragma mark Property accessor methods
- (BOOL)launchOnLogin
{
if (!loginItemsListRef)
return NO;

LSSharedFileListItemRef itemRef = [self mainBundleLoginItemCopy];
if (!itemRef)
return NO;

CFRelease(itemRef);
return YES;
}

- (void)setLaunchOnLogin:(BOOL)value
{
if (!loginItemsListRef)
return;

if (!value) {
[self removeMainBundleFromLoginItems];
} else {
[self addMainBundleToLoginItems];
}
}


That's all! Not only does this give you synchronization between your preferences panel and the system settings, but also feels more correct than poking at a different application's or subsystem's persistent domain.

Note: There is also a "seed" for each list that can be used to check if the list has changed. Using that you can make sure that any change notification is not emitted unless necessary (see LSSharedFileListGetSeedValue()). The example above doesn't do that, so there will be notification sent out when toggling the setting from the app's preferences.

Registering defaults for NSUserDefaults using a property list

Thursday, April 9, 2009
In a previous post about using property lists, I wrote a little about property lists and a use case I had for them. Another one I ran in to recently is related to user defaults:

NSUserDefaults is the system in Mac OS X that handles user preferences. Applications usually register default values at launch time, so that all preferences have a sane default value in case the user hasn't set one for a particular preference. Most examples I've found on the subject do something along the lines of:

// Register user defaults in the class initializer.
+ (void)initialize
{
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
@"YES", @"ShowToolBar",
@"NO", @"AutoSaveEnabled",
// ... lots of objects and keys here,
nil];

[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
}


However, if you have many keys, or just want to make it possible to change them without recompiling, they can be managed through a property list file instead of doing it programmatically. To do that, first create a property list by selecting File → New File... or pressing cmd-N in Xcode and selecting Property List in the Other category. Then add the default values using the property list editor.

Assuming the plist file is named UserDefaults.plist, the code can then be changed to:

+ (void)initialize
{
NSString *defaultsPath = [[NSBundle mainBundle] pathForResource:@"UserDefaults"
ofType:@"plist"];
NSDictionary *appDefaults = [NSDictionary dictionaryWithContentsOfFile:defaultsPath];

[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
}

Using property lists

Wednesday, April 8, 2009
Before entering the Cocoa world, whenever I needed to store and retrieve some small amount of data in an application, I usually handcrafted an XML format and wrote a matching XML parser using either DOM or SAX. This often meant a lot of uninspiring code duplication. On Mac OS X, there is a standardized format that is used throughout the system called property list or "plist". Not only are there APIs to parse plists into the familiar data structures in Cocoa, but there is also a builtin editor for plists in Xcode.

This came in handy recently in some code I was working on. I needed to store 20 or 30 pairs of strings and select one pair randomly from time to time. Instead of entering the strings in the code, they were put into a plist file.

Create and edit a plist
It's easy to create a property list. In Xcode, just select File → New File... or press cmd-N, and select the template Property List in the Other category. Then add the data you wish to, using the property list editor in Xcode. You can of course also handwrite the XML using any text editor.

Use the data
Assume that the property list is structured with a top-level key called Pairs, whose value is an array of our string pairs. Each pair in turn is also an array, with two strings in each. The code to read the list could then look as follows:

- (void)readStringPairs
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"MyFileName"
ofType:@"plist"];
NSDictionary *toplevelDict =
[NSDictionary dictionaryWithContentsOfFile:path];

// Get the array of pairs, retain it as we need it later.
pairs = [[toplevelDict valueForKey:@"Pairs"] retain];
}

- (void)randomizeStrings
{
// Get a random pair, represented by an array.
NSArray *pair = [pairs objectAtIndex:arc4random() % [pairs count]];

// Get the two strings.
NSString *name = [pair objectAtIndex:0];
NSString *description = [pair objectAtIndex:1];

// Do something with the strings here.
}


If you need something a little bit more flexible or complex, you can nest dictionaries and arrays in the plist as well. As a matter of fact, my original code doesn't only have two strings per pair, but one string and an array of strings.

About me

Welcome to my blog about programming, primarily on Mac OS X using Cocoa and related technologies. I plan to write about problems I encounter, interesting APIs I run into, or simply tips and tricks that I think are worth sharing.

I am quite new to application development on Mac, coming from the GNOME community where I have been developing and maintaining various pieces of software for roughly ten years. Being a maintainer of the native Mac port of GTK+ for a couple of years led me into the direction of Cocoa which finally got me hooked.