Tuesday, 5 January 2010

iPhone: Application preferences/defaults/settings. With defaults.

How to extend NSUserDefaults with default values.

Time for more iPhone shenanigans. Lots of people have contacted me saying these posts are useful - thanks. As I do more iPhone development I discover things that aren't immediately obvious from the SDK documentation (excellent as it is). I'm documenting some of them in my blog partly to find out if I've missed the obvious, and partly to give back to the community - Google has certainly pointed me to plenty of other people's blogs with useful answers.

Today's installment: application settings. Or preferences, if that's what you want to call them. Or settings, if you prefer.

Finding the preferred term

The iPhone SDK provides a neat API for persisting simple sets of application settings. It's easy enough to find, if you know what it's called when you search for it. Don't search for preferences, like I did, or you'll be sent down the Core Foundation dead-end of Preferences Programming Topics. This is a rich and fairly heavyweight API for persisting state. But for a simple iPhone app it's overkill.

What you do need to look for is the Cocoa NSUserDefaults class. That's the puppy you want. It's simple when you know what to look for. This class provides an easy way to read and write fundamental data types (integers, floats, bools, NSStrings, etc).

What's missing? NSUserDefault's defaults.

Again, perhaps I've missed something. But this class is missing something fairly obvious. It has a simple set of APIs to write values against a given NSString "key" name, e.g. setInteger:forKey: It has a set of APIs to read those values back for a given NSString "key" name, e.g. integerForKey:

However, there is no way to discover whether or not a setting has been saved yet. This is important since the first time your application is run no preferences have been saved, and you'll need to fall back on a set of well-chosen default values. The NSUserDefaults accessors return "default initialised" values if a key has not been set yet; e.g. 0 for integers, 0.0 for floats, or a nil string. You can't always inspect an integer for zero and replace it with a default - sometimes zero is a valid setting value.

So what can you do about this?

Adding defaults to "defaults"

We can add this functionality to NSUserDefaults easily enough. The first trick is to determine whether a value has been stored for a particular key or not. It turns out that, no matter what kind of key has been stored, internally everything can be accessed as an object and the objectForKey: method returns nil if no value has been stored.

So you can see whether an integer, for example, has been stored by calling objectForKey: with its key name. The returned id value is nil if the integer has not yet been stored, and non-nil if it has.

Useful.

Now, ideally we'd have a suite of methods that read values from the NSUserDefaults class and return a specified default value if that setting has not yet been made. This can be done neatly using Objective-C's class category facility. Categories allow you to extend existing classes with extra functionality.

Here is some example code showing how to do this for some of the basic types. Once you've put this snippet into your application you can use the methods as if they were an original part of the NSUserDefaults API.

@interface NSUserDefaults (ReadWithDefaults)
- (NSInteger) integerForKey:(NSString *)key withDefault:(NSInteger)value;
- (BOOL) boolForKey:(NSString *)key withDefault:(BOOL)value;
- (float) floatForKey:(NSString *)key withDefault:(float)value;
- (id) objectForKey:(NSString *)key withDefault:(id)value;
@end

@implementation NSUserDefaults (ReadWithDefaults)

- (NSInteger) integerForKey:(NSString *)key withDefault:(NSInteger)value
{
if ([self objectForKey:key])
return [self integerForKey:key];
else
return value;
}

- (BOOL) boolForKey:(NSString *)key withDefault:(BOOL)value
{
if ([self objectForKey:key])
return [self boolForKey:key];
else
return value;
}

- (float) floatForKey:(NSString *)key withDefault:(float)value
{
if ([self objectForKey:key])
return [self floatForKey:key];
else
return value;
}

- (id) objectForKey:(NSString *)key withDefault:(id)value
{
id obj = [self objectForKey:key];
if (obj)
return obj;
else
return value;
}

@end

Writing: Creating a Framework for the iPhone

December's Overload (an ACCU magazine) was published at the end of 2009, containing an expanded article form of my Creating a Framework for the iPhone work.

You can read it online in the Overload Online section of the ACCU website.

If you're a programmer who cares about code, and how to write good code, you should consider joining ACCU. It's not expensive, and is an excellent way to improve your skills in a great international community of programmers. You can read PDF versions of Overload magazine for free if you're not an ACCU member, but to get CVu magazine you have to become a member.

Thursday, 17 December 2009

iPhone: Forcing a UIView to reorientate

In my current iPhone application I need to force a UIView to reorientate when a certain event happens. The SDK doesn't really allow for this to happen.

There's the well-known UIViewController method
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation that is called whenever the user rotates the iPhone. If you say YES to that orientation then the OS does the spade work of rotating the display and your views for you. However, you cannot manually ask to rotate the screen, so if you decide that your shouldAutorotateToInterfaceOrientation: peccadillo has changed, there's no way to tell the OS.

The blind alley

There are a number of blog posts, discussions, and Stack Overflow posts that suggest you use the private setOrientation: method of the UIDevice class. This is all well and good apart from two issues:
  1. It's a private API. You won't get into the App store if you use it.
  2. More importantly, it doesn't work.
This seems the canonical version of that method:
[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setOrientation:UIInterfaceOrientationLandscapeLeft];
It serves to rotate the status bar, but leaves your UIView where it is.

How to make it work

Until Apple bless us with an official API for doing this, here's how I managed to achieve my goal...

Once I have decided that I want to orientate a different way, and arrange to return a different shouldAutorotate answer, you must change your window's subview hierarchy. Doing this forces the OS to ask you about your rotation support and wiggle everything around appropriately.

Based on the fact my app has a parent UINavigationViewController at the top, which puts a single subview into the UIWindow, this is what I do:
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIView *view = [window.subviews objectAtIndex:0];
[view removeFromSuperview];
[window addSubview:view];
It's a bit unpleasant, but it works. I tried variants such as removing the window and then making it the keyWindow again, however that didn't trick the OS into asking for rotation state again.

I hope this helps you. As ever, if you know a better way of doing this, please let me know!

Tuesday, 15 December 2009

The git equivalent of svnversion

I've been happily using git as my version control weapon of choice for some time now. It's integrated into my automatic build, test, and release scripts neatly. The world is a nice place.

Except, I've not really found a compelling replacement for Subversion's svnversion. I used to use svnversion in my release scripts to name the final build disk image nicely, something like "fooproject-SVNVERSION.dmg", as well as versioning some of the files inside the image.
What is svnversion?
svnversion is a cute little command line tool that looks in the current working directory to determine which revision of a subversion repository you have checked out. If your head revision is 215, and you have all files checked out at HEAD, then running svnverison you'll get:
pete@solomon > svnversion
215
If you have some older files, then you'd get something like:
pete@solomon > svnversion
200-215
And, if your working copy contains some files with uncommited local changed, then you'd get a helpful M added to the end:
pete@solomon > svnversion
200-215M
It is possible to get something similar to svnversion using git's git describe command. In some ways it is superior to Subversion's simple monotonically incrementing numbers, in some ways inferior. By necessity, it works differently from svnversion.

git describe looks at the git revision you have checked out, and produces the name of the nearest tag followed by the number of commits since that tag, and a partial SHA-1 value of the current commit. This is relatively cute:
pete@solomon > git describe
v0.7-8-gf20bae60
However, there are issues:
  • Firstly, it doesn't mark whether there are local uncommitted modifications in your local tree.
  • And the partial SHA-1 at the end may not always be valid. If your project gets large enough that more characters are required to unambiguously identify one SHA-1 value all older "git describe"s will no longer uniquely identify a revision.
We can't fix the latter issue, but we can tweak a little to add some "M" love to our git description.

Try this little bit of bash scriptery:
git_version()
{
GITVERSION=`git describe`
GITMODIFIED=`(git st | grep "modified:\|added:\|deleted:" -q) && echo "-M"`
echo $GITVERSION$GITMODIFIED
}

That's a little more useful.

If you know a better way of doing this, I'd love to know!

Friday, 11 December 2009

Speaking: ACCU 2010

The ACCU 2010 conference schedule has been ceremoniously unvieled on the ACCU website, here. It looks as strong as ever.

This year I will be presenting two whole sessions for your delight and delectation.
  • The first, poetically entitled Stood at the Bottom of the Mountain Looking Up, is an investigation into how to quickly get up to speed with new technologies, languages, problems, etc. It'll be an interesting and practical "soft skills" kinda thing. I'm not sure how to weave inappropriate imagery into the talk yet, but I have plenty of time to work on it.
  • The second, snappily entitled iPhone development is a talk about, well, iPhone development. It's a co-presented session with the esteemed Mr Phil Nash. Goodness knows how we'll cover the ground in a mere 90 minutes! I'm really looking forward to this one.
I should probably do something about the biography that's currently up there. "Pete owns some shoes. But won't wear them." is accurate but perhaps not too descriptive.

Thursday, 10 December 2009

Boost on the iPhone

This is the simple way to get Boost into your iPhone code.

I've been porting a large C++ project to the iPhone. It uses the excellent Boost libraries. Building Boost for the iPhone is not impossible, just a bit of a pain in the arse.

There are a number of good examples of how to do this online, for example the Backstage blog entry here and Matt Galloway's blog here. They are useful hints that help you work past the impenetrable Boost Build documentation.

However, the story does not end here. Those instructions allow you to build a set of libraries for the simulator, or for the iPhone OS. But not both. This means that your Xcode project setup gets fiddly with different link paths for the different targets.

You can solve this by creating a "universal" fat library. The lipo tool can be used to shunt the individual libraries together. Not tricky, just another step.

Now, for bonus points it would be sweet to construct a "Framework" for the Boost libraries, allowing you to use them in Xcode like any other iPhone framework. I've already blogged on how to do this here.

Of course, if you were sensible, you'd wrap this up in a script so that anyone can use it. A script a bit like this one.

I've set up a Gitorious project for this script. Feel free to use it.

Friday, 13 November 2009

Writing: Respect the Software Release Process

The November issue of ACCU's CVu magazine is out now. It contains my latest Professionalism in Programming column, "Respect the Software Release Process".

I felt a little Wordly when I designed the cover for this issue. Thanks to the postal strike in the UK, I got the printer's cover proof for the magazine after the full printed magazine landed on my doormat!