Search code examples
objective-cmacruby

MacRuby: How to create an app that can be configured from the outside?


I have a MacRuby app that is meant to be customized and distributed by other people. The app really only has a few things that can be customized, but I would like to figure out a way to package the application so that a person could run a script, specify a few configuration options, and have it set up the application configuration so that it is ready to distribute to others.

For example, I have a string that points to a URL. I want someone to be able to change this URL without needing to open the project in XCode, and without having to re-build (or re-compile) so that someone on Windows or Linux could make this change.

Is this sort of thing possible? I'm new to MacRuby and Objective-C, so there may be an obvious solution to this that I am not aware of.

My Solution:

I used a plist file with the name AppConfig.plist that looked 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">
<plist version="1.0">
<dict>
    <key>homepage</key>
    <string>http://google.com</string>
</dict>
</plist>

And my App Delegate could access it like this:

config_path = NSBundle.mainBundle.pathForResource('AppConfig', ofType: 'plist')
@config = load_plist File.read(config_path)
# then access the homepage key like this: @config['homepage']

Solution

  • The easiest way, as jkh said, is to use a plist. For example, you can store the needed customizations in Stuff.plist in the root of your project and access it using the following:

    stuff = load_plist File.read(NSBundle.mainBundle.pathForResource('Stuff', ofType: 'plist'))
    

    or if, for instance, Stuff.plist is in your Resources folder (where it probably should be)

    stuff = load_plist File.read(NSBundle.mainBundle.pathForResource('Stuff', ofType:'plist', inDirectory:'Resources'))
    

    stuff is now a Hash (or NSMutableDictionary) of your stuff. For example if Stuff.plist looked 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">
    <plist version="1.0">
    <dict>
      <key>my_stuff</key>
      <dict>
        <key>favorite_color</key>
        <string>green</string>
        <key>first_car</key>
        <string>Reliant K</string>
            </dict>
            <key>his_stuff</key>
            <dict>
        <key>favorite_color</key>
        <string>blue</string>
        <key>first_car</key>
        <string>240D</string>
            </dict>
      </dict>
      </plist>
    

    You should be able to access the values in the following way:

        my_favorite_color = stuff[:my_stuff][:favorite_color]
    

    I didn't actually test this in an application bundle, but I did test it using macirb. To play around with it yourself, you can load a plist file from macirb using the following:

    stuff = load_plist File.read('/path/to/Stuff.plist')
    

    MacRuby implements a load_plist on Kernel, but no write_plist or anything like that, however MacRuby does implement a to_plist on Object, so anything can be written to disk as a plist!

    File.open('/path/to/new_plist.plist','w'){|f| f.write(['a','b','c'].to_plist)}
    

    which gives you:

    <?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">
    <plist version="1.0">
    <array>
        <string>a</string>
        <string>b</string>
        <string>c</string>
    </array>
    </plist>
    

    Now the user can define the customizations directly via plist and the already built app will read the values at runtime. Be careful with this, since you don't want to accidentally eval any rm *s.