12

I want to register the default values in NSUserDefaults so that the user settings do not return null values for values not explicitly set by the user, but rather return the default values specified in settings bundle. I read here:

How to register user defaults using NSUserDefaults without overwriting existing values?

that the following should be executed in applicationDidFinishLaunching:

[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Defaults" ofType:@"plist"]]];

How can this be done in MonoTouch?

Community
  • 1
  • 1
BruceHill
  • 6,954
  • 8
  • 62
  • 114

2 Answers2

15

I solved this by using the method detailed in this question:

Can you make the settings in Settings.bundle default even if you don't open the Settings App

I basically translated the function registerDefaultsFromSettingsBundle listed there into MonoTouch. This is a very useful function because it calls registerDefaults for the default values listed in the settings bundle. This ensures that if the user has not entered any values, that the defined default values will be returned rather than null values. This can be called from FinishedLaunching of the AppDelegate.

Here is the MonoTouch version of this function:

public static void RegisterDefaultsFromSettingsBundle() 
{
    string settingsBundle = NSBundle.MainBundle.PathForResource("Settings", @"bundle");
    if(settingsBundle == null) {
        System.Console.WriteLine(@"Could not find Settings.bundle");
        return;
    }
    NSString keyString = new NSString(@"Key");
    NSString defaultString = new NSString(@"DefaultValue");
    NSDictionary settings = NSDictionary.FromFile(Path.Combine(settingsBundle,@"Root.plist"));
    NSArray preferences = (NSArray) settings.ValueForKey(new NSString(@"PreferenceSpecifiers"));
    NSMutableDictionary defaultsToRegister = new NSMutableDictionary();
    for (uint i=0; i<preferences.Count; i++) {
        NSDictionary prefSpecification = new NSDictionary(preferences.ValueAt(i));
        NSString key = (NSString) prefSpecification.ValueForKey(keyString);
        if(key != null) {
            NSObject def = prefSpecification.ValueForKey(defaultString);
            if (def != null) {
                defaultsToRegister.SetValueForKey(def, key);
            }
        }
    }
    NSUserDefaults.StandardUserDefaults.RegisterDefaults(defaultsToRegister);
}

I hope other MonoTouch developers find this useful.

UPDATE:

With the latest version of Xamarin.iOS this code will throw an exception on this line:

NSDictionary prefSpecification = new NSDictionary(preferences.ValueAt(i));

Note that this exception does not occur because of iOS 8 but because Xamarin has decided to make the constructor that the code is depending on private, so the required constructor is not available to be used. I resolved this by subclassing NSDictionary like this:

public class CustomNSDictionary: NSDictionary 
{
    public CustomNSDictionary (IntPtr ptr): base(ptr) {}
}

Then you can replace the problematic line with the following:

CustomNSDictionary prefSpecification = new CustomNSDictionary(preferences.ValueAt(i));
Community
  • 1
  • 1
BruceHill
  • 6,954
  • 8
  • 62
  • 114
  • Very useful, thanks for the snippet! I was using `XDocument` to read out the plist. That worked on the simulator, but not on the device, because the plist is compiled into a binary format. Your code works like a charm on both the simulator and the device :) – Niels van der Rest Aug 09 '11 at 15:31
  • Does what it says on the tin. Thank you for sharing this! – Matt Jacobsen Feb 24 '12 at 10:05
  • 1
    Small tip: you don't need to prefix C# strings with `@`. It's just verbatim string operator that allows you to enter multiline strings, for example, and has nothing to do with `NSString`. – Dan Abramov Jul 13 '12 at 18:46
  • Hi Dan. Thanks for the comment. The `@` signs were actually just left there after the conversion from Objective-C ... I copied and pasted the Objective-C and then did the translation and did not remove them. Yep, they can be removed. :) – BruceHill Jul 13 '12 at 21:20
  • 2
    Thanks for posting the translated code. I am currently using iOS 8 and have enountered a problem on the following line of code `NSDictionary prefSpecification = new NSDictionary(preferences.ValueAt(i));` It seems in the latest version of iOS, NSDictionary no longer accepts an IntPtr in its constructor. It will only accept NSCoder, NSDictionary, NSUrl, or string. After numerous attempts using different parameter types I am unable to get it to work. How can I format the IntPtr in a way the NSDicionary can understand? – Shawn Roser May 20 '15 at 17:45
  • 2
    Hi Shawn. I recently encountered that issue too. Here is the stackoverflow question that I posted about it: http://stackoverflow.com/questions/29959439/cannot-access-protected-member-foundation-nsdictionary-nsdictionarysystem-intp I will post an update showing how I solved it by subclassing NSDictionary and making the required constructor public in the subclass. – BruceHill May 21 '15 at 05:27
2

You could try something along the lines of:

NSUserDefaults.StandardUserDefaults.RegisterDefaults(NSDictionary.FromFile("Defaults.plist"));

(assuming you've created a Defaults.plist file with all your default values in! (and it's in the root of your bundle!))

Luke
  • 3,665
  • 1
  • 19
  • 39
  • Thanks, Luke. I took a slightly different approach, but I found your translation of the code that I posted helpful. – BruceHill May 11 '11 at 16:27