Sunday, 30 August 2009

MonoTouch Settings.bundle

Property Lists (plist) and Bundles are probably so obvious to seasoned Mac/iPhone developers that they're not even worth mentioning, however for someone with a '100% .NET' background you can't take anything for granted. I had a -lot-bit of trouble getting iPhone Settings working with MonoTouch (see here), but now that I've figured it out it seems very simple. Here's a quick guide:

1. Bundles are folders
Within your MonoTouch project, create a new folder (right-click solution → Add → New Folder) and call it Settings.bundle


2. Plist files are some sort of hybrid Xml key-value
(right-click solution → Add → New File... → Empty Text File) and call it Root.plist. You could add an existing plist-xml-formatted file if you like - but we're just going to create one from scratch (in the next step).


3. Edit Root.plist with the Property List Editor
The structure of the plist is very specific - look for some doco or Chapter 10 of Beginning iPhone Development. In this example I've only used PSGroupSpecifier and PSTextFieldSpecifier but there are many other types (PSMultiValueSpecifier, PSToggleSwitchSpecifier, PSChildPaneSpecifier...).
Double-click the Root.plist file inside MonoDevelop to open the Property List Editor - type carefully as there is no 'validation' and if you mis-spell (or mis-capitalize) something it just won't work:


4. Set the correct build action for Root.plist
This is important (and I think the cause of my earlier problem) - you MUST tell the IDE to make sure this file is copied to the phone.


5. Access the settings
Use the NSUserDefaults.StandardUserDefaults property to access the values set by the user.


6. See/Edit "Settings" on the iPhone


It's worthwhile noting that the 'useability' for these kinds of Settings is 'unusual' for desktop applications, but probably familiar to iPhone users... the settings for a whole swag of unrelated applications are grouped under the Settings icon (it's not necessarily obvious from with the application that they are available). The user must also quit the application to get to these settings - which means they're most useful for things that don't change much (eg. the users identity and servers in the Mail.app).

13 comments:

  1. Glad you got it working!

    Quick note - you only need to set the Build Acttion to Content. You don't need to set Copy to Output.

    ReplyDelete
  2. Thanks for your post! I'm one of the 100% .NET people and this is a great help.

    ReplyDelete
  3. Thanks, Craig. The settings on my app were done in less than an hour with your help.

    P.S. I've too been using Jeff LaMarche's book, Beginning iPhone Development. I also highly recommend Jonathan Zdziarski's book, iPhone SDK Application Development, especially for those who prefer not to use Interface Builder.

    ReplyDelete
  4. Something new: my root.plist was working as described in this post for MT 1.1.1. In MT 1.2, I get this new error message on completion of the build:

    Could not load file '/Users/twestley/Dev/iPhone/MT_InaRuwaNoIB/MT_InaRuwaNoIB/Settings.bundle/Root.plist': Text node cannot appear in this state. file:///Users/twestley/Dev/iPhone/MT_InaRuwaNoIB/MT_InaRuwaNoIB/Settings.bundle/Root.plist Line 1, position 1.
    Build: 1 error, 0 warnings

    ReplyDelete
  5. Hey Terry, I saw a comment elsewhere on my blog from someone who found Root.plist worked differently in a 1.2beta... basically delete the file added manually like I suggest and instead "add a new file and pick 'iPhone Application Manifest Template'." and re-create the settings you need using the Plist editor.

    The original comment is on this post.
    HTH (I will d/l and play with 1.2 tonight)

    ReplyDelete
  6. Yes, I recall that comment too. Thanks for the tip.

    But when I add a new file in MD 2.2 RC, I don't see an "iPhone Application Manifest Template." Is an Entitlements plist the same thing?

    ReplyDelete
  7. Doesn't sound the same - I will do some reading later if someone doesn't figure it out first; and update the blog.
    Thx

    ReplyDelete
  8. Hat tip to http://snipplr.com/view/2239/convert-plist-to-and-from-xml/.

    I used this command to convert Root.plist to xml:

    plutil -convert xml1 -o Root.plist.xml Root.plist

    I then saved the original plist (always paranoid) and renamed Root.plist.xml to Root.plist. Then MT 1.2 completes build successfully.

    ReplyDelete
  9. Hi Craig - great tip! - but how do I simply create a local 'file' where I can store a bit of user data - i.e. when I ask the user for their Name I would wish to keep hold of it so next time the program starts I can say Hello 'username' !!
    Or am I forced to use the 'global' iphone settings environment?

    ReplyDelete
  10. I think NSUserDefaults is an appropriate choice for something like 'username'. It is not 'global' - NSUserDefaults are per-application and nicely suited to small pieces of data.

    If you really want to pursue the idea of using a local file to store preferences data, all the regular .NET System.IO functions work as you'd expect (including serialization and deserialization of objects). Check out
    http://github.com/conceptdev/Roget1911/blob/master/MainViewController.cs
    lines 51 - 55 for an example of deserialization from an XML file.

    Alternatively, this code
    http://github.com/conceptdev/iSOFlair/blob/master/Stackoverflow.cs
    lines 60 - 69 shows loading some simple information from a "question-mark-seperated" text file.

    ReplyDelete
  11. I've been searching this for hours. I was missing the include "BundleRessource" setting in file property. Thanks.

    ReplyDelete
  12. great tutorial, but know i have a problem, instead of key a have identifier, is that the same because with StringForKey don't return any thing, thks

    ReplyDelete