On Background Apps

February 16th, 2010

The lack of background applications or multitasking has come into the collective chattersphere again with the announcement of the iPad. It’s the new “non-user-replacable battery”. The ability to run more than one application at a time sounds like a good thing. Two (or twelve) are better than one right? However, multitasking is a means to an end, not a goal in itself. I’m interested as to why everyone is clamoring for the ability to run applications in the background. What exactly do we they to be able to do?

The Why of Background Applications

In an effort to understand the nature of background applications and multitasking from an regular user’s point of view, I tried to come up with some basic use cases.

  • Switching between applications efficiently. Good iPhone (and hopefully iPad) apps solve this by preserving their state when quit and restoring it at launch. If done right it usually achieves the same effect as true multitasking.

  • Viewing several application at once. This is an obvious use-case for desktop OS’s with large 15″+ screens, but a non-issue for iPhone applications. It doesn’t make sense to display more than a single application at a time with such a small screen size. We’ll need to wait and see how this plays out for iPad, but my guess is that the screen size is just small enough.

  • Things that take a long time. There are countless tasks that your computer could do “in the background” — compiling, running a scientific simulation, rendering a large video — but these aren’t usually what you want to be running on your resource-constrained mobile device. Downloading sizable files is the one kind of background task that is common in even mobile devices. iPhone’s built-in iTunes and App Stores already support this use case by downloading media and applications in the background.

    * iPhone OS has no public API that allows for downloading data in the background while your application is no longer running.

  • Notifications. Sometimes we want to be notified when a new email or instant message arrives. Although it’s not perfect, this use case is mostly addressed by the Apple Push Notification service. Again, when done right, the actual user experience is very close to what a real background application could deliver.

  • Audio. Audio is in a slightly different category than everything else. It can be consumed effectively without any on-screen interface and extremely minimal user interaction. On iPhone, the built-in iPod application can play file-based audio content in the background. However, there are many sources of audio that don’t come from files stored on your device, a prime example being streaming internet radio.

    * iPhone OS has no public API that allows for playing streaming audio in the background while your application is no longer running.

Update: Adam Engst posted some similar thoughts on the same subject of background apps after I began writing this post (I’m a slow writer). He brought up one use case that wasn’t in my original list:

  • Inter-application communication. The majority of every-day inter-application communication is via Copy/Paste or Drag/Drop (which is essentially Copy/Paste anyway). As of version 3.0, iPhone OS supports Copy/Paste, and the Drag/Drop metaphor doesn’t make as much sense for iPhone applications, so it’s reasonable that it’s ommitted.

Of the five six general use cases listed above four are either not applicable or addressed in some way. That leaves downloading files and playing streaming audio content unaccounted for. Both downloading files and playing streaming audio have some similarities — they both are retrieving bits from the Internet. Initially, file downloads seem like the simpler case, but may bring some extra complexity to the user.

Arbitrary file downloads suffer from two problems: managing multiple downloads and the “where did I put that file” problem. Multiple background downloads suggests that some UI to manage these downloads will be required — which is exactly the kind of complexity that iPhone OS strives to avoid. Also, if downloads can happen in the background where do they go? Into a shared “Downloads” area, or directly into the application? Neither of these problems are insurmountable, but I bring them up to show that file downloads will require some additional thought and design to get right.

Streaming audio avoids the management problem with one simple restriction: only one audio stream can be active at a time. In general, you only want to play audio from a single source anyway, so this is very reasonable limitation. There are obviously battery life concerns, but let’s put those aside for a moment and consider what supporting background audio could look like from the API level.

An iPhone OS API for Streaming Audio

Apple added AVAudioPlayer in iPhone OS 2.2. At first glance, it looks like it solves most of our problem. It has an initializer that takes a URL, an observable property currentTime and delegate methods to provide notifications regarding interruptions and playback completion. It’s a good starting point, but we’ll need to make some changes:

  1. Support for http URLs. Currently AVAudioPlayer can only play local file URLs that are in a format supported by Core Audio. If we extend it to include http URLs (in a format supported by Core Audio), we have a simple streaming player for basic audio types. We could even go one step further and include support for HTTP Live Streaming.

  2. Set the Background Audio Player. Cocoa often provides shared service objects via singletons. In some cases it also allows you to set the shared instance (NSURLCache’s setSharedURLCache: for example). This pattern makes sense for our use case because we only want one background audio player active at a time. We’ll add class methods to AVAudioPlayer to get and set the background player.

    The one restriction is that the background audio player instance should only be accessible to the process that originally set it to maintain cross-application confidentiality. This can be enforced by having AVAudioPlayer store the bundle identifer of the application that started the background audio. Still, other applications may want to know if some background audio is already playing so we’ll create a second flag called hasBackgroundAudioPlayer that can be queried by any process.

    // Returns YES if a background player is active, NO otherwise.
    + (BOOL) hasBackgroundAudioPlayer;

    // Returns nil if called by non-owning process
    + (AVAudioPlayer *) backgroundAudioPlayer;

    // Overrides any existing background audio players
    + (void) setBackgroundAudioPlayer:(AVAudioPLayer *)audioPlayer;

  3. Queue several URLs. An AVAudioPlayer instance was designed to play only a single file. To play multiple files in succession, an AVAudioPlayer must be created for each one. While restricting to a single file simplifies the usage of AVAudioPlayer it would be more useful for our case if we could queue several URLs. This way AVAudioPlayer can work through an entire playlist in the background.

    We can solve this by adding a URL queue property to AVAudioPlayer. The contents of this queue may be directly manipulated, so we’ll just use an NSMutableArray. As AVAudioPlayer finishes playing a URL and then URL queue isn’t empty, it will remove the first URL from the queue and begin playing it.

    Lastly, we should tweak the audioPlayerDidFinishPlaying:successfully: delegate method to also pass the URL that finished.

    @property (readonly) NSMutableArray *URLQueue;

    - (void)audioPlayer:(AVAudioPlayer *)player didFinishPlayingURL:(NSURL *)url successfully:(BOOL)flag

Now we have an enhanced AVAudioPlayer that can play a list of streaming audio sources in the background. Obviously there are a number of ways to provide background audio playback. This is just one approach given to show that providing such a service can be done without full-blown multitasking and moderate API additions.

I think that some mechanism to play back streaming audio in the background would be a great addition to iPhone OS as long as battery life is acceptable. iPad makes this feature even more compelling. With an iPhone, you can start your favorite streaming player, put the iPhone back in your pocket, and go about your business. It’s a sidekick device. However, when you’re on your iPad, you want to use it. You want to be browsing the web, writing, or playing a game — not staring at the progress bar of the current track.


Creating a Default Development Profile to Run Any iPhone App

February 10th, 2010

I’m collaborating on an iPhone project and the topic of sharing device UDIDs for development came up. I was surprised to learn that other developers didn’t have a default development profile that allows you to build and run any application, without installing new provisioning profiles or changing bundle IDs.

Here’s how I set mine up:


Create a wildcard App ID in the iPhone Program Portal:


Create a Development Provisioning Profile with the “wildcard” App ID:


Choose the “iPhone Developer” Automatic Profile Selector


And that’s it

Now you should be able to build and run any project on your iPhone device in development mode. You won’t be able to use this configuration for distribution, but that’s why you can configure separate development and distribution profiles.


My Best of 2009

January 6th, 2010

I decided to share a few of my favorite things of 2009. Yes I know 2009 was so last week, but I think this post still lands within the relevancy window.

My list doesn’t necessarily includes things that were newly created last year, but rather things that were new-to-me, or made a significant impact on me within the year. And we’ll start it off with…

Trip: Prague/Munich/Ghent

I went to Prague, Munich (Oktoberfest), and Ghent (for the third time) in September with two friends. It was, of course, awesome. Amazingly though, I still haven’t posted pictures from the trip. New Year’s resolution: suck less.

Runner-Up: (Tie) AEA/ACBF (Boston), WWDC (San Francisco)

It may seem like I’m cheating by listing every place I went in 2009, but I couldn’t really decide which trip was better. Both included technical conferences, good friends, and great memories.

Local Event: Philly Beer Week 2009

Philly Beer Week came at just the right time for me, and it was a lot of fun. Some standout sub-events were the Voodoo tasting at Doobie’s and the Zythos Belgian Beer Fest to cap off the week.

Runner-Up: BarCamp Philly 2009

Philly’s second BarCamp was twice as good as the first. If this trend continues…

Blog: Daring Fireball

I’ve been a DF reader for years, but this year I decided to support Mr. Gruber and buy a membership. It’s my favorite blog.

Runner-Up: UI&us

UI design is one of the most challenging, but also interesting aspects of software development for me, and this blog put forth a lot of great ideas. Plus Keith Lang is a really nice guy.

Programming Site: Stack Overflow

I was a little skeptical at first, but Stack Overflow has definitely changed the Q&A landscape for the better. It’s hard to go back to forums and mailing lists.

Runner-Up: GitHub

I’ve only skimmed the surface, but so far I’m impressed with GitHub. It really delivers on the promise of “Social Coding”.

Local (Philly) Site: (Tie) Technically Philly, Unbreaded

Both blogs are well-written and on-topic. I couldn’t pick a clear favorite, so they’ll share the honor.

Runner-Up: uwishunu

It’s not a new site, but it’s become my favorite place to find fun and interesting things to do in my fair city.

Album: Jaydiohead

Yes, a mashup album. Why? Because it rocks, and made a 30-year-old white guy who never cared for Jay-Z before into a fan.

Runner-Up: Infinity + 1 by A-Track

I picked this up from an Amazon MP3 deal on a whim, not knowing what to expect. Since then slowly but surely embedded itself in my brain. Super fun mix.

Movie: Up

Great film. Squirrel!

Runner-Up: Zombieland

Zombieland produced the most real-life LOLs for of any movie I saw in the theatre all year. Worth it for the Bill Murray cameo alone.

TV Show: Pushing Daisies

I know Pushing Daisies was actually cancelled before 2009 began, but it’s new to me. I loved the characters and the feel of the show. My only gripe was the rushed ending of the second season, but that is obviously attributed to it’s cancellation.

Runner-Up: Dexter

Another new-to-me show. I’m not quite sure what I was expecting, but I was surprised by how much I liked this show. I’ve only finished the first two seasons, but the third is already in my Netflix queue.

Podcast: The MDN Show

The new, unified show from The Mac Developer Network became an instant favorite of mine. I liked all the previous shows, but it’s nice having the content all in one place.

Runner-Up: Stack Overflow

I can’t exactly put my finger on why I like the Stack Overflow podcast so much, but I keep coming back to it. I think there is a good dynamic between Joel and Jeff. It’s also fun to listen to them discuss anything Apple-related. It’s like two polar bears trading tips on life in the rain forest.

Book (Fiction): The Road

Best novel I’ve read in awhile. And I read it before I knew it was being made into a movie, which makes my appreciation more authentic.

Runner-Up: Consider the Lobster and Other Essays

I actually consumed this particular work as an audiobook. I highly recommend it, especially since it’s read by the author, David Foster Wallace (may he rest in peace).

Book (Technical): Learn Objective–C on the Mac

Although I still think Cocoa Programming for Mac OS X is the best text for learning Cocoa, it lacks more foundational information about C and programming basics. We used this book for PhillyCocoa’s Cocoa Programming Foundations Workshop and were very happy with it.

Runner-Up: JavaScript: The Good Parts

Technical books that approach a topic from a particular angle, instead of being just another tutorial or reference really stand out for me. I don’t even write much JavaScript, but I really enjoyed this book. Plus it’s a short read.

Mac Application: (Tie) Acorn, Opacity

I’m not a big fan of Photoshop. It’s expensive, cumbersome, and does much more than I need. I wanted some lighter, cheaper, and more geared towards developers. Enter Acorn, a well-designed, scriptable image editor. It’s usually the first place I go if I need to work with any graphics. However, this year I discovered a super-interesting application called Opacity. It’s obviously designed with the developer in mind, with bindable variables and it’s own “build” system. I do almost all my image work in these two apps, hence the tie.

Runner-Up: DTerm

I was aware of DTerm for awhile, but I didn’t “get it” until recently. It’s great for interacting with git from within Xcode, and supersedes the “Open Terminal Here” applications I’ve written about previously.

iPhone Application: Tweetie 2

Great application all-around. I love the “pull down to refresh” feature.

Runner-Up: Byline

Byline is the best Google Reader client I’ve tried. I love swipe to mark/unmark as read.

iPhone Game: Parachute Panic

Parachute Panic is a fun, casual game, but it’s the hand-drawn graphical style and music that pushed it to the top. ♫ The game is overrrr ♫ But not foreverrrr ♫ Try it again! ♫

Runner-Up: Flight Control

Again, a fun, casual game whith great graphical style and music.

Video Game: House of the Dead: Overkill

Yes, its bloody, campy, and kinda stupid, which is exactly why it won. Very fun in that “turn your brain off and shoot at things” kind of way.

Runner-Up: Tales of Monkey Island

Guybrush Threepwood, Mighty Pirate returns. Man, I miss adventure games.

Brunch: Café Estelle

Excellent food, friendly staff, and human-sized portions.

Runner-Up: Figs

This little BYO has great egg dishes and the thickest pancakes I’ve ever seen.

Lunch Truck: Honest Tom’s Tacos

I love the simplicity of the menu. For lunch, there are two kinds of tacos, coffee, and no step three. The sweet potato tacos are my favorite.

Runner-Up: La Dominique

This unassuming cart serves up delicious, painstakingly-crafted crêpes on Drexel’s campus.

Restaurant: Amada

I’ve actually been to Amada once previously, but it stood out in my mind as the best restaurant experience I had this year. The “Matador” cocktail was awesome.

Runner-Up: The Belgian Café

It gets mixed reviews from fellow Fairmount residents, but I really like The Belgian Café. Tasty veggie burgers and impressive beer list. Done.

Bar: National Mechanics

National Mechanics has become the go-to bar of the geek crowd in Philly. I’ve had several “good times” there this year, so this was an easy choice.

Runner-Up: The P.O.P.E.

I don’t make it out to this South Philly hangout very often, but I have blast every time I do.

Beer: Liefmans Cuvée Brut

This may seem like an odd choice, but I stand by it for a few reasons. First, I was given a tour of the Leifmans brewery in Belgium brewery courtesy of some awesome friends. Second, although sour fruit beers aren’t usually my first choice, this is actually a pretty damn good beer.

Runner-Up: (Tie) Dogfish Head Sah’tea, Palo Santo Marron

And a Dogfish Head double-whammie in the runner-up position. I discovered both these beers in 2009 but under very different circumstances. Palo Santo I first tried during a tour of the Dogfish Head brewery, and Sah’tea at the aforementioned Belgian Café after the first fateful night we spent with the little monster. Friends and I bought and split cases of each.

Predictions for 2010

  • Probably contain around 365 days.
  • Still no jetpacks.
  • Ragnarök.

Wildcard App ID and Push Notifications

July 30th, 2009

Back in the Dark Ages of iPhone Development, when the NDA loomed over us all, getting information was hard. The deployment and code-signing piece was especially tricky to get working correctly. One was often thrilled to get it working at all.

One step of the process is choosing an application ID, which uses the common reverse domain name format. The documentation also stated that if you want to share information across multiple applications, a wildcard app ID (com.yourcompany.*) can be used. No drawbacks were given, and sharing is good, right? So I can only imagine that some people may have used a wildcard app ID for their application. And I can also only imagine that these people had no problems with their wildcard app ID, until iPhone OS 3.0 came out that is.

You see, Apple’s Push Notification service using a wildcard application ID.

Those same people may have been under the impression that it was the app ID that uniquely identified the application, and therefore changing it would yield a new application, and not update the existing one. This means that all ranking in the App Store would be lost, and users would not be notified of the update.

The good news is, this is not the case. An application’s app ID can be changed by generating a new app ID, re-generating a distribution the App Store distribution certificate, and submitting to the App Store.

So if you shipped your app with a wildcard ID, and now want to integrate push notifications, you are not screwed. Just generate a new, non-wildcard app ID.


An Event Apart 09

June 26th, 2009

I attended the recent An Event Apart conference in Boston. An Event Apart is aimed at designers who build websites, and has a standards-oriented slant.

I am not a designer, but I have to play one from time to time. I don’t plan to make designing websites my primary focus, but some basic skills is necessary these days. Also I think the web might not be a passing fad, so investing in some expertise in that realm would probably pay off in the long run AEA seemed like a good way to jump-start that area of my professional skill set.

The talks were very high quality across the board, and the topics ranged from design psychology to CSS nuts and bolts. I found Jason Santa Maria and Dan Cederholm to have the most useful technical content. Whitney Hess’s DIY UX talk and Kristina Halvorson’s Content Strategy talk both had great information from a less technical, but still development-related standpoint. From a general-interest perspective, Jared Spool on Amazon and SimpleScott on the Obama campaign website were extremely interesting.

Beyond the talks, the conference itself was a great experience. The presenters were very down to earth, and I met a lot of cool people. The designers didn’t even seem to mind that I wasn’t “one of them”.

A few personal key takeaways:

  • “Websites don’t need to look the same in every browser.” This was the mantra of the conference. We were made to chant it over and over until it permeated our souls (just kidding (sorta)).
  • Grid-based layouts are your friend.
  • Fluid layouts are cool.
  • Sketch. “Sketchbooks are not about being a good artist, they’re about being a good thinker.” – Jason Santa Maria.
  • “The behavior you’re seeing is the behavior you’ve designed for.” – Joshua Porter
  • Charge for the value you bring to a client, not the hours you work.

I didn’t take many pictures but instead outsourced them via the AEA Boston 09 Flickr pool.

Finally, I wanted to put some of my newfound skills and excitement to use. Without further adieu, I give you: doesthissitejustshowonebigword.com.


And the winner is… wait… what?

June 19th, 2009

The iPhone app we built for Wolfgang’s Vault, Concert Vault (iTunes link), was awarded Best iPhone/iPod touch application by Macworld UK.

This is quite the pleasant surprise. I had no idea it was even being considered. It’s an honor to be chosen out of all of the nominees, which are all great apps. I use several of them myself.

Working with Wolfgang’s Vault has been a great experience and we’re excited to continue building apps to deliver their extraordinary content on the iPhone platform.


WWDC 09 Post Mortem

June 18th, 2009

WWDC 09 was my second WWDC, after first attending in 2008. I would have liked to do a more well-constructed breakdown of this year’s conference, but time has been scarce, and I didn’t want the information to get (more) stale. Instead, I give you a brain-dump of some of my experiences:

  • Keynote

    • Cheaper, better laptops? Yes please.
    • Snow Leopard for $29 is great.
    • Boos at AT&T were funny (and warranted).
    • “Find My Phone” was the surprise hit.
    • Too much time in demos (a third of which didn’t work!).
    • Scott Forstall seemed like a stronger presenter than Phil Schiller.
    • My prediction the new iPhone’s video chat feature would be demoed by calling Steve Jobs was wrong.
  • Events and Parties

    • sfMacIndie was worthwhile – bumped into some old friends and made some new ones.
    • First Stump the Experts. Fun, but I don’t know if I’d do it again.
    • Those WebKit guys and girls can throw a good party.
    • The Big Nerd Ranch was, of course, a lot of fun.
    • Beer Bash: Cake was good, but the food, not so much.
      • “Don’t bring hot dogs to a sausage party.” – @themartorana
  • Travel & Lodging

    • First Virgin American experience was positive, though the best part is their attitude, not the in-flight entertainment or wifi.
    • The InterContinental was really convenient and pleasant. Internet will cost you though.
  • Food & Drink

Whew. I think that’s all the non-NDA-encumbered information I have for now. Again, WWDC proved to be a good experience. If you develop Mac or iPhone software, and can afford it, it is definitely worth your time.

Finally, it was awesome to travel with so many local friends this time (even the smelly ones) and of course, make some new ones. That’s what WWDC is really all about, anyway.

Oh, and the few photos I took can be found here.

Now let’s just hope the videos come out more promptly this year. ;-)


Another “Open Terminal Here” Toolbar App

June 5th, 2009

In Learning the Terminal on the Mac – Part 4, I mentioned a mini-application called OpenTerminalHere that opens a new Terminal in the same directory as the current Finder window. Well recently Dan Benjamin linked to another, nearly identical application called cdto.

Both toolbar applications work as advertised, but cdto takes the extra step of clearing the scrollback buffer after changing directories, yielding a slightly cleaner workspace. cdto is also hosted on Google Code, so it’s easy to grab the source on hack on it.

Enjoy.


Terminal Quickie: Kill ‘Em All

June 4th, 2009

More often than I’d like, my Mac gets into a very unhappy state. Running applications won’t respond, but they won’t force quit either. New applications won’t launch, and the unresponsive applications block a proper system restart.

Usually I want to cut my losses, and get my machine back into a good state as soon as possible. As a last resort, I could do a hard restart with the old hold-the-power-button-down-for-5-seconds trick, but then I have to wait for the machine to reboot and risk damaging my filesystem.

There is an alternative method that will kill all of your processes, responsive or not, and get you back working much more quickly – as long as you still have shell access:

$ kill -9 -1

Warning: this really will kill all your running processes immediately, without saving any data. Use wisely.

Discussion

So what’s going on here? Basically, the kill (man page) is used to terminate processes. Usually it is used to kill a single process, like so:

$ kill 1234

where 1234 is the process id. The process id can be obtained a number of ways, but the most common is with the ps (man page) command.

The kill command also lets you specify a signal to send to the process. By default, it sends the TERM signal, which ask the program to terminate. However, the TERM signal isn’t always enough. Sn unresponsive program often won’t be able to react to the signal, and programs can even choose to ignore the signal completely.

Luckily, the KILL signal does not suffer from the problem. When this signal is sent, the operating system will stop the process dead in its tracks, whether it wants to stop or not. The KILL signal is very powerful, but don’t only use it as a last resort because it does not allow processes to save data, close resources, and otherwise exit cleanly.

You can send the KILL signal to a process like this:

$ kill -KILL 1234

Or use the numeric value for the signal, which is 9:

$ kill -9 1234

There is one final piece to the puzzle. Notice that in the original command we pass a -1 as the process id. This is a special argument that tells kill to send the signal to all processes belonging to you (or every process on the system, if you’re the super user).

Basically, kill -9 -1 is a quick way or killing every process belonging to you. Running this on a OS X system is a quick a dirty way to clear your environment and start fresh. Remember, this will kill all processes immediately – without saving data – and log you out, so use with care.


Adding Debug-only Preferences in iPhone Applications

November 16th, 2008

Recently I bumped into an iPhone application configuration/deployment problem, and thought someone else might benefit from my findings.

Motivation and Goals

Here’s the basic scenario: I wanted to expose certain options in my iPhone application that were only available in Debug builds. One choice would be to add the ability to change the options in the application itself, and use the usual conditional compilation (#ifdef DEBUG…) techniques to hide or expose them based on build configuration.

However, this would require adding a whole bunch of custom UI in my application, which didn’t really seem appealing. The application was already using the standard iPhone application Setting system, which is driven by simple plists. I thought to myself, boy it would be nice if my Debug-only options could be automagically added to Settings bundle at build time.

For example, the standard settings pane would look like this:

NoDebug.png

And debug-only options would be added automatically, to produce this:

HasDebug.png DebugPane2.png

I’m a believer in the DRY principle, so I wanted to avoid duplicating and information at all possible. Also I wanted to “set it and forget it” and not require any manual build steps.

After some experimentation, trial, and error, I arrived at the following solution:

  1. Store Debug settings in a separate child pane. The Settings system allows for child panes of the root pane. To keep the main settings and debug settings separate, we keep them on different panes.
  2. Add an reference to the child pane from the root pane at build time. To keep the main root settings clean, we’ll inject the reference to the debug clild pane at build time.

Create the Property List

First, add a Settings Bundle to your project if you haven’t already. Choose File -> New File… -> Settings -> Settings Bundle.

Next we need to add a second plist for our Debug settings. Xcode doesn’t allow you to create a new plist inside the Settings bundle (or any bundle for that matter), so we’ll have to create our Debug.plist by some other means. If you’re familiar with the shell, the easiest way is to navigate to Settings.bundle inside your project and simply duplicate the main Root settings to create Debug settings.

cp Settings.bundle/Root.plist Settings.bundle/Debug.plist

If you prefer to use Finder, navigate to Settings.bundle and then right- or control-click and choose Show Package Contents. Then option-drag to duplicate Root.plist and rename it to Debug.plist.

Once the file has been created, collapse and expand the Settings bundle in your projects Groups & Files section to refresh it’s contents. It should look like this:

AddDebugSettingsExample - Debug.plist .png

Note, you can name the child settings plist something other than “Debug” if you want. We’ll see how to do that in the next section.

The Injection Script

The purpose of the injection script is to add a child pane option to an existing settings plist file. It uses the built in PlistBuddy utility to do all the real work.

The addDebugSettingsChild.sh takes two arguments. The first is of course that path to the target plist file. The second is the name of the new child pane, or ‘Debug’ if unspecified.

Here’s the script:

#!/bin/sh

# addDebugSettingsChild.sh
#
# Simple script to inject a Debug menu in an iPhone Settings plist.
#
# created 10.15.2008 by Andy Mroczkowski, mrox.net

THIS="`basename $0`"
PLISTBUDDY="/usr/libexec/PlistBuddy -x"

set -e

if [ -z "$1" ]; then
    echo "Usage:"
    echo "   $THIS plist_file [child_pane_name]"
    echo "   - If unspecified, child_pane_name is 'Debug'"
    exit 1
fi

if [ ! -e "$1" ]; then
    echo "[$THIS] file not found: '$1'"
    exit 2
fi

if [ ! -z "$2" ]; then
    CHILD_PANE_NAME="$2"
else
    CHILD_PANE_NAME="Debug"
fi

TARGET="$1"
echo "[$THIS] adding '$CHILD_PANE_NAME' child to: $TARGET"

$PLISTBUDDY -c "Add PreferenceSpecifiers:0 dict" "$TARGET"
$PLISTBUDDY -c "Add PreferenceSpecifiers:0:Type string 'PSGroupSpecifier'" "$TARGET"

$PLISTBUDDY -c "Add PreferenceSpecifiers:1 dict" "$TARGET"
$PLISTBUDDY -c "Add PreferenceSpecifiers:1:Type string 'PSChildPaneSpecifier'" "$TARGET"
$PLISTBUDDY -c "Add PreferenceSpecifiers:1:Title string '$CHILD_PANE_NAME Settings'" "$TARGET"
$PLISTBUDDY -c "Add PreferenceSpecifiers:1:File string '$CHILD_PANE_NAME'" "$TARGET"

Download

Creating the Build Step

We’ve created a Debug settings child pane in our settings bundle, and a script to add an option to access our child pane from the root pane. Now we just have to tie the pieces together in Xcode’s build settings.

Copy addDebugSettingsChild.sh to your project’s folder. You don’t need to add it to the project itself. Next add a new “Run Script Build Phase” to the target for your application. Order it so be comes after the “Copy Bundle Resources” phase, and name it something more descriptive if you’d like. I called mine “Run Debug Settings Script”.

Debug.plist - AddDebugSettingsExample-2.png

Next we have to make the our new Run Script phase actually do something. Basically, we need to call the addDebugSettingsChild.sh if we’re building with the “Debug” configuration, and also set up the input and out files so Xcode knows when to run the script and when not to. Let’s set up the input and output files first.

Add the path to your Root settings plist as an input file. If you used the default names and locations, this should be:


$(SRCROOT)/Settings.bundle/Root.plist

The file we will be altering will be in the built products area, not the project source root, so the output file should be:


$(BUILT_PRODUCTS_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/Settings.bundle/Root.plist

Finally create the body of the script. I only want to add my extra settings if I'm building the "Debug" configuration, so my script looks like this:

if [ "$CONFIGURATION" == "Debug" ]; then
    sh addDebugSettingsChild.sh "$SCRIPTOUTPUTFILE_0"
fi

You may notice that the script's input is specified as the output file. This is not a mistake. The addDebugSettingsChild.sh script modifies the specified plist in place, and we want to alter the one in our built-products area, not the source tree.

Again it's important to specify the input and output files. If you don't, Xcode may run the script on every build, which will result in repeated Debug child pane choices in our main settings plist.

Once you're done, it should look like this:

Run Script Phase for “AddDebugSettingsExample” Info.png

Conclusion

And that's it. You can now add edit the Root and Debug plists and they will be updated appropriately and everything will be happy. Well, mostly happy (see Caveats). I hope you found if useful. If not, I'm sorry. Here's some pictures of Robocop on a Unicorn to cheer you up.

Sample Project

You can download a sample project with all the above steps already completed here.

Caveats and Future Work

  • Be careful when switching configurations and using Build and Run. The products in the build folder for each configuration should always be correct, but Xcode won't necessarily re-install the right Setting bundle if it doesn't detect a change. The workaround is to just build clean and re-build when switching from Debug to Release or vice versa. Any suggestions on how to improve this are welcome.
  • Child pane options are always injected at the top. I'd prefer if I could add the "Debug Settings" option at the end of the main preferences, but I could not find a simple way to append an element to the end of an array with PlistBuddy. I considered using PlistBuddy to loop through the array to determine the size, and then use that to append it to the end, but I didn't want to add all that complexity to the script for a minor issue just yet.
  • Debug.plist still exists, but is inaccessible, in Release builds. This method does not prevent the installation of the Debug.plist, but there will be no way to get to it, since it's not referenced as a child from the root plist. If this bothers you, you'll probably have to set up another script to remove Debug.plist.
  • What if I'm not using a Settings bundle in my Release builds? This technique assumes that you're already using a Settings bundle to your application, and you want to add extra stuff to it based on build configuration. If you'd like to only have Settings bundle for Debug builds, this isn't going to work for you, though hopefully some of the information can help you get started on a similar solution.