We all know about the feature to register a custom URL scheme in Info.plist so that when Safari tries to open myapp://some?parameters, your app will be launched. This is very useful, but has it’s clear limitations.
Today I downloaded Apple’s new MobileMe Gallery app. At one point the app asked me if I wanted to always open MobileMe galleries in the app, instead of showing the web version. When I said “sure”, the app launched Safari which greeted me with a page that exclaimed: “Safari is now configured to use the MobileMe Gallery app.”
The result of this magic trick is that now when I go to a URL like this: http://gallery.me.com/something/000000, Safari launches the MobileMe Gallery app.
This is different from the custom URL scheme registration we all know and love:
- A new URL scheme was not registered, it uses http:
- Not all URLs starting with http: were taken over, just those associated with MobileMe galleries.
- The registration was done dynamically.
- I had a say in if I wanted this to happen or not.
- It seems to rely on some undocumented feature in Mobile Safari.
Why would this be useful in your own apps?
- On a web page you can create a link that will launch your app on the device if it has been installed. If the app is not installed you can display a web page that entices the user to purchase the app from the App Store.
- Say you have a news reader app and you share a link to a news story with a friend via email. If your friend also has the app installed then the news story can display nicely in the app. If not, then Safari will be launched and your friend will see the web version of the news story.
With custom URL schemes both of the above scenarios would require two different links and force the user to select the right one based on their situation. Tap the wrong link and you’ll get an error message. Not pretty.
Apple, can you please document and expose this functionality to us mere mortals?
Update: Turns out that this feature does not rely on any private API:s. Read Brian’s comment below to learn how it’s done.
After yesterday’s elaborate tutorial on how to create a Preferences UI, accessing the preferences from your code is a snap.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
delayBeforeDialing = [userDefaults floatForKey:@"delayBeforeDialing"];
The key, “delayBeforeDialing” in this example, needs to match the Key value in Root.plist.
There are different accessor methods depending on the type of variable you want to retrieve.
You don’t have to use a UI for your preferences. If you just want to conveniently store values associated with your application, that you can read back later, you can use the setter methods of NSUserDefaults.
[userDefaults setFloat:delayBeforeDialing forKey:@"delayBeforeDialing"];
Read back the values as above.
- In Finder navigate to your Xcode project.
- Create a new folder called Settings.
- Inside the folder create a new file called Root.plist. See below for an empty Root.plist example.
- Rename the Settings folder to Settings.bundle. Finder will ask if you really want to do this. You do. Root.plist will “disappear” into the Settings.bundle file.
- In Xcode Command-click on Resources in your project. Select Add > Existing Files…
- Select Settings.bundle and click Add twice.
- You should now see the Settings.bundle file in Xcode and if you expand it Root.plist will appear again.
If you know of a less convoluted way to create this structure in Xcode, please let me know in the comments.
Empty Root.plist file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
Preference settings for MyApp
If you double click on Root.plist in Xcode the file will open in a standard text editor. A better alternative is to command-click Root.plist and select Open With Finder. That will open the Property List Editor.
- Expand the Root node and click the New Child button.
- Name the child Title, select the String class and enter the name of your application as the value.
- With the new Title node selected, click the New Sibling button.
- Name the new node PreferenceSpecifiers, and select the Array class. (You can’t enter a value for an Array.)
- With the PreferenceSpecifiers node selected, click the New Child button.
- Select the class Dictionary for the new node. (You can’t change the name of the node nor the value for a Dictionary entry.)
- Expand the new node and click on the New Child button.
- Name the new child Title, leave it as a String class, and give it a value that will have meaning to a user of your application.
- Click on the New Sibling button. Give the node these values: name = Type, class = String, value = PSTextFieldSpecifier. This will allow a user to enter a preference value in a text field.
- Click on the New Sibling button. Give the node these values: name = Key, class = String, value = something that has meaning in your code. This is the key your code will use to lookup this preference value.
- Click on the New Sibling button. Give the node these values: name = DefaultValue, class = String, value = a meaningful default. This value will be used unless the user has set the preference to something else.
- Save the Root.plist file.
If you expand all the nodes in the Property File Editor it should look something like this:
If you view the file in an Xcode text editor it will look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<string>Delay before dialing</string>
Build and run your app. Exit your app and tap the Settings app. You should now see a new entry that matches the name of your app. Tap this entry and your own custom preferences will show up. The user can edit and save these preferences without you having to write any code. Pretty neat.
Multi Value Specifier Preference
If the user should select between multiple pre-defined values you can use a PSMultiValueSpecifier. Enter the visible titles as an Array under the name Titles, and the values your code sees as an Array under the name Values.
Something like this:
Other Types of Preference Values
- PSToggleSwitchSpecifier - for boolean values
- PSSliderSpecifier – for a range of values