Writing to your app's plist file

What’s the best way for reading/writing to your application’s plist file? I have a convoluted way of achieving it from years past, but was hoping now that Cocoa is the standard Mac OS X build target in Xojo that I have missed some new method for doing this that was introduced to the IDE over the past couple of years. Any new preferences object I have yet to discover? Or does everyone use a call to NSUserDefaults (and have an example to share)?

Thanks is advance, —> Kelsey

if it is a true .plist file, you can use “defaults” via the cli to read/write to it.

there also is a plist class that you can use (I dont have the link to it off hand - will try to find it for you), and you can update the plist directly from with your application.

Manipulating plist files via the ‘defaults’ command line tool is discouraged and likely to break in a future release. From the man page:

[quote] WARNING: The defaults command will be changed in an upcoming
major release to only operate on preferences domains. General
plist manipulation utilities will be folded into a different
command-line program.[/quote]

It’s fine for in-house build automation, but you shouldn’t use in the wild for such things.

As for Kelsey’s question, the correct solution for OS X preferences is NSUserDefaults or CFPreferences. There’s a CFPreferences implementation in macoslib.

I second Joe’s statement, if you can use NSDictionary or CFDictionary to edit .plist files and for prefs use NSUserDefaults and CFPreferences. All Apple approved methods.

This is the code I use for loading data from NSUserDefaults:

Function LoadBoolean(key As String) As Boolean Declare Function boolForKey Lib FoundationLib Selector "boolForKey:" (NSUserDefaults As Ptr, key As CFStringRef) As Byte Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Return (1 = boolForKey(standardUserDefaultsPtr, key)) End

Function LoadDate(key As String) As Date Declare Function alloc Lib FoundationLib Selector "alloc" (NSClass As Ptr) As Ptr Declare Function init Lib FoundationLib Selector "init" (NSClass As Ptr) As Ptr Declare Function objectForKey Lib FoundationLib Selector "objectForKey:" (NSUserDefaults As Ptr, key As CFStringRef) As Ptr Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Function stringFromDate Lib FoundationLib Selector "stringFromDate:" (NSDateFormatter As Ptr, NSDate As Ptr) As CFStringRef Declare Function systemTimeZone Lib FoundationLib Selector "systemTimeZone" (NSTimeZone As Ptr) As Ptr Declare Sub setDateFormat Lib FoundationLib Selector "setDateFormat:" (NSDateFormatter As Ptr, format As CFStringRef) Declare Sub setTimeZone Lib FoundationLib Selector "setTimeZone:" (NSDateFormatter As Ptr, NSTimeZone As Ptr) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Static dateFormatterClassPtr As Ptr = NSClassFromString("NSDateFormatter") Static timeZoneClassPtr As Ptr = NSClassFromString("NSTimeZone") Static systemTimeZonePtr As Ptr = systemTimeZone(timeZoneClassPtr) Dim dataFormatterPtr As Ptr = init(alloc(dateFormatterClassPtr)) setDateFormat(dataFormatterPtr, "YYYY-MM-dd HH:mm:ss") setTimeZone(dataFormatterPtr, systemTimeZonePtr) Dim datePtr As Ptr = objectForKey(standardUserDefaultsPtr, key) Dim dateString As String = stringFromDate(dataFormatterPtr, datePtr) Dim d As New Date() d.SQLDateTime = dateString Return d End

Function LoadDouble(key As String) As Double Declare Function doubleForKey Lib FoundationLib Selector "doubleForKey:" (NSUserDefaults As Ptr, key As CFStringRef) As Double Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Return doubleForKey(standardUserDefaultsPtr, key) End

Function LoadInteger(key As String) As Integer Declare Function integerForKey Lib FoundationLib Selector "integerForKey:" (NSUserDefaults As Ptr, key As CFStringRef) As Int32 Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Return integerForKey(standardUserDefaultsPtr, key) End

Function LoadString(key As String) As String Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Function stringForKey Lib FoundationLib Selector "stringForKey:" (NSUserDefaults As Ptr, key As CFStringRef) As CFStringRef Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Return stringForKey(standardUserDefaultsPtr, key) End

Function LoadStringArray(key As String) As String() Declare Function count Lib FoundationLib Selector "count" (NSArray As Ptr) As UInt32 Declare Function objectAtIndex Lib FoundationLib Selector "objectAtIndex:" (NSArray As Ptr, index As UInt32) As CFStringRef Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Function stringArrayForKey Lib FoundationLib Selector "stringArrayForKey:" (NSUserDefaults As Ptr, key As CFStringRef) As Ptr Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Dim stringArrayPtr As Ptr = stringArrayForKey(standardUserDefaultsPtr, key) Dim count As Integer = count(stringArrayPtr) Dim arr() As String For i As Integer = 0 To count - 1 arr.Append(objectAtIndex(stringArrayPtr, i)) Next Return arr End

This is the code I use for loading data from NSUserDefaults:

Sub SaveBoolean(key As String, value As Boolean) Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Sub setBool Lib FoundationLib Selector "setBool:forKey:" (NSUserDefaults As Ptr, value As Byte, key As CFStringRef) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) setBool(standardUserDefaultsPtr, IIf(value, 1, 0), key) Synchronize() End

Sub SaveDate(key As String, value As Date) Declare Function alloc Lib FoundationLib Selector "alloc" (NSClass As Ptr) As Ptr Declare Function dateFromString Lib FoundationLib Selector "dateFromString:" (NSDateFormatter As Ptr, aString As CFStringRef) As Ptr Declare Function init Lib FoundationLib Selector "init" (NSClass As Ptr) As Ptr Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Function systemTimeZone Lib FoundationLib Selector "systemTimeZone" (NSTimeZone As Ptr) As Ptr Declare Sub setDateFormat Lib FoundationLib Selector "setDateFormat:" (NSDateFormatter As Ptr, format As CFStringRef) Declare Sub setObject Lib FoundationLib Selector "setObject:forKey:" (NSUserDefaults As Ptr, NSDate As Ptr, key As CFStringRef) Declare Sub setTimeZone Lib FoundationLib Selector "setTimeZone:" (NSDateFormatter As Ptr, NSTimeZone As Ptr) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Static dateFormatterClassPtr As Ptr = NSClassFromString("NSDateFormatter") Static timeZoneClassPtr As Ptr = NSClassFromString("NSTimeZone") Static systemTimeZonePtr As Ptr = systemTimeZone(timeZoneClassPtr) Dim dataFormatterPtr As Ptr = init(alloc(dateFormatterClassPtr)) setDateFormat(dataFormatterPtr, "YYYY-MM-dd HH:mm:ss") setTimeZone(dataFormatterPtr, systemTimeZonePtr) Dim datePtr As Ptr = dateFromString(dataFormatterPtr, value.SQLDateTime) setObject(standardUserDefaultsPtr, datePtr, key) Synchronize() End

Sub SaveDouble(key As String, value As Double) Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Sub setDouble Lib FoundationLib Selector "setDouble:forKey:" (NSUserDefaults As Ptr, value As Double, key As CFStringRef) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) setDouble(standardUserDefaultsPtr, value, key) Synchronize() End

Sub SaveInteger(key As String, value As Integer) Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Sub setInteger Lib FoundationLib Selector "setInteger:forKey:" (NSUserDefaults As Ptr, value As Int32, key As CFStringRef) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) setInteger(standardUserDefaultsPtr, value, key) Synchronize() End

Sub SaveString(key As String, value As String) Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Sub setObject Lib FoundationLib Selector "setObject:forKey:" (NSUserDefaults As Ptr, value As CFStringRef, key As CFStringRef) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) setObject(standardUserDefaultsPtr, value, key) Synchronize() End

Sub SaveStringArray(key As String, arr() As String) Declare Function alloc Lib FoundationLib Selector "alloc" (NSClass As Ptr) As Ptr Declare Function init Lib FoundationLib Selector "init" (NSClass As Ptr) As Ptr Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Sub addObject Lib FoundationLib Selector "addObject:" (NSMutableArrayClass As Ptr, anObject As CFStringRef) Declare Sub setObject Lib FoundationLib Selector "setObject:forKey:" (NSUserDefaults As Ptr, NSArray As Ptr, key As CFStringRef) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) Static mutableArrayClassPtr As Ptr = NSClassFromString("NSMutableArray") Dim mutableArrayPtr As Ptr = init(alloc(mutableArrayClassPtr)) For i As Integer = 0 To arr.Ubound addObject(mutableArrayPtr, arr(i)) Next setObject(standardUserDefaultsPtr, mutableArrayPtr, key) Synchronize() End

For what it’s worth, synchronize is usually not necessary these days. From the Lion release notes:

And from the Apple engineer responsible for CFPreferences/NSUserDefaults:

Utility functions I use for accessing NSUserDefaults:

Sub Remove(key As String) Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Sub removeObjectForKey Lib FoundationLib Selector "removeObjectForKey:" (NSUserDefaults As Ptr, name As CFStringRef) Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) removeObjectForKey(standardUserDefaultsPtr, key) Synchronize() End

Private Sub Synchronize() Declare Function standardUserDefaults Lib FoundationLib Selector "standardUserDefaults" (NSUserDefaultsClass As Ptr) As Ptr Declare Function synchronize Lib FoundationLib Selector "synchronize" (NSUserDefaults As Ptr) As Byte Static standardUserDefaultsPtr As Ptr = standardUserDefaults(NSClassFromString("NSUserDefaults")) If synchronize(standardUserDefaultsPtr) = 0 Then Raise New RuntimeException() // RuntimeException could be subclassed an deliver more details End End

Yes, definetely check out MacOSLib. It even contains a higher level class “TTsSmartPreferences” which makes getting and setting prefs very easy, and functions even x-platform, like this:

App.Prefs..Value ("EnableSomething") = true

and

dim i as Integer = App.Prefs..Value ("Some value", 0) // get value with default

See the “How to use” Note for more.

Thanks all!

Much appreciated. —> Kelsey

[quote=12884:@Thomas Tempelmann]Yes, definetely check out MacOSLib. It even contains a higher level class “TTsSmartPreferences” which makes getting and setting prefs very easy, and functions even x-platform, like this:

App.Prefs..Value ("EnableSomething") = true

and

dim i as Integer = App.Prefs..Value ("Some value", 0) // get value with default

See the “How to use” Note for more.[/quote]
Thank you for the tip!
It’s safe to use this for apps in Mac App Store?

And what should we use in build scripts instead of the defaults command? There MacOSLib or even the MBS stuff won’t work.

I use PlistBuddy. It gets installed by Xcode, as I recall.

Thanks! I had forgotten about this.

[quote=24153:@Albin Kiland]Thank you for the tip!
It’s safe to use this for apps in Mac App Store?[/quote]
Yes, I use this myself in two apps I have in the MAS (Find Any File and iClip)

Great! Couldn’t be simpler to use either. It’s great :wink:

I believe that messing with an app’s plist file might break code-signatures on future OS X releases.

This needs clarification:
The original poster asked, I hope, about the preferences file, i.e. the plist file that exists in the ~/Library/Preferences folder. That’s what all the early answers relate to as far as I can tell.

Michael seems to relate to the Info.plist file inside the app’s bundle, though.
And to comment on that: Changing this file while the app runs is not “safe”, especially because Apple tends to write-protect applications, so that changing them would require root permissions.

Good point : I assumed from the OP’s first post that they were talking about “The application’s plist” file as meaning the Info.plist file that’s within the App’s bundle - reading it a second time, clearly they were talking about the app’s preferences plist file which is not the same thing. Nevermind.