Having problems reading settings while using InApp

2020-06-20 20:16发布

问题:

I am using the InAppSettingsKit modules to manage my settings.

I can include it in my project to the point where I can build a settings page that interacts correctly with my project. I have a root.plist that I can change and when I first run my app on the simulator I can see the changes reflected.

However, for the life of me, I cannot access the settings in NSUserDefaults through my code. When I run the code below I can loop through the contents of the [NSUserDefaults standardUserDefaults]. However, I only see a bunch of generic settings that are not related to the stuff in my plist file

NSUserDefaults* d = [NSUserDefaults standardUserDefaults];

NSDictionary* dict = [d dictionaryRepresentation];

NSString* descKey;
NSString* descObject;
NSString* classTypeForKey;
NSString* classTypeForObject;
id myObject;

for (id key in dict) {
    myObject = [dict objectForKey:key];
    classTypeForObject = [[myObject class] description];
    classTypeForKey = [[key class] description];
    descKey = [key description];
    descObject = [myObject description];
}

So where does InAppSettingsKit put the settings? The doc says in [NSUserDefaults standardUserDefaults] so I am at a loss.

回答1:

I think your problem relies on a misunderstanding of the concept. Root.plist doesn't store any settings but just defines how Settings.app and InAppSettingsKit work on your userDefaults.

When your app launches the first time, there are no userDefaults. Usually, you start by setting appropriate default settings by reading in a static userDefaults.plist from your Resources:

// Load the default values for the user defaults
NSString* pathToUserDefaultsValues = [[NSBundle mainBundle]
                                      pathForResource:@"userDefaults" 
                                      ofType:@"plist"];
NSDictionary* userDefaultsValues = [NSDictionary dictionaryWithContentsOfFile:pathToUserDefaultsValues];

// Set them in the standard user defaults
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValues];


回答2:

Here is Ojas's code modified a bit further to account for ARC.

You would probably call initializeSettings from your AppDelegate didFinishLaunchingWithOptions.

#import "IASKSettingsReader.h"
...

- (void)initializeSettings
{
    // standard stored preference values
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

    // settings files to process
    NSMutableArray *preferenceFiles = [[NSMutableArray alloc] init];

    // begin with Root file
    [preferenceFiles addObject:@"Root"];

    // as other settings files are discovered will be added to preferencesFiles
    while ([preferenceFiles count] > 0) {

        // init IASKSettingsReader for current settings file
        NSString *file = [preferenceFiles lastObject];
        [preferenceFiles removeLastObject];
        IASKSettingsReader *settingsReader = [[IASKSettingsReader alloc]
                                              initWithFile:file];

        // extract preference specifiers
        NSArray *preferenceSpecifiers = [[settingsReader settingsBundle]
                                         objectForKey:kIASKPreferenceSpecifiers];

        // process each specifier in the current settings file
        for (NSDictionary *specifier in preferenceSpecifiers) {

            // get type of current specifier
            NSString *type = [specifier objectForKey:kIASKType];

            // need to check child pane specifier for additional file
            if ([type isEqualToString:kIASKPSChildPaneSpecifier]) {
                [preferenceFiles addObject:[specifier objectForKey:kIASKFile]];
            }
            else {
                // check if current specifier has a default value
                id defaultValue = [specifier objectForKey:kIASKDefaultValue];

                if (defaultValue) {
                    // get key from specifier and current stored preference value
                    NSString *key = [specifier objectForKey:kIASKKey];
                    id value = [defaults objectForKey:key];

                    // update preference value with default value if necessary
                    if (key && value == nil) {
                        [defaults setObject:defaultValue forKey:key];
                    }
                }
            }

        }

    }

    // synchronize stored preference values
    [defaults synchronize];
}


回答3:

It's all making sense now. I had tried to register the defaults in previous attempts to get things working, but I thought I could do this using the Root.plist file. But of course the registerDefaults methods just sees the high level entries of StringsTable and PreferenceSpecifiers. Thanks for pointing this out Ortwin.

So, instead of building a separate userDefault.plist file, I found the following code that parses the root.plist file to manually add the defaults to NSUserDefaults. I can't remember where I got it, but it seems to work well. If there are reasons why I shouldn't do it this way, feel free to elaborate:

    - (void)setDefaults {

        //get the plist location from the settings bundle
        NSString *settingsPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Settings.bundle"];
        NSString *plistPath = [settingsPath stringByAppendingPathComponent:@"Root.plist"];

        //get the preference specifiers array which contains the settings
        NSDictionary *settingsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath];
        NSArray *preferencesArray = [settingsDictionary objectForKey:@"PreferenceSpecifiers"];

        //use the shared defaults object
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

        //for each preference item, set its default if there is no value set
        for(NSDictionary *item in preferencesArray) {

            //get the item key, if there is no key then we can skip it
            NSString *key = [item objectForKey:@"Key"];
            if (key) {

                //check to see if the value and default value are set
                //if a default value exists and the value is not set, use the default
                id value = [defaults objectForKey:key];
                id defaultValue = [item objectForKey:@"DefaultValue"];
                if(defaultValue && !value) {
                    [defaults setObject:defaultValue forKey:key];
                }
            }
        }

        //write the changes to disk
        [defaults synchronize];
        }
}


回答4:

I modified Sam's code to process child panes specified in separate files:

#import "IASKSettingsReader.h"

- (void)initializeSettings
{
    // standard stored preference values
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

    // settings files to process
    NSMutableArray *preferenceFiles = [[NSMutableArray alloc] init];

    // begin with Root file
    [preferenceFiles addObject:@"Root"];

    // as other settings files are discovered will be added to preferencesFiles
    while (![preferenceFiles isEmpty]) {

        // init IASKSettingsReader for current settings file
        NSString *file = [[preferenceFiles lastObject] retain];
        [preferenceFiles removeLastObject];
        IASKSettingsReader *settingsReader = [[IASKSettingsReader alloc]
                                              initWithFile:file];
        [file release];

        // extract preference specifiers
        NSArray *preferenceSpecifiers = [[settingsReader settingsBundle] 
                                         objectForKey:kIASKPreferenceSpecifiers]; 

        // process each specifier in the current settings file
        for (NSDictionary *specifier in preferenceSpecifiers) {

            // get type of current specifier
            NSString *type = [specifier objectForKey:kIASKType];

            // need to check child pane specifier for additional file
            if ([type isEqualToString:kIASKPSChildPaneSpecifier]) {
                [preferenceFiles addObject:[specifier objectForKey:kIASKFile]];
            }
            else {
                // check if current specifier has a default value
                id defaultValue = [specifier objectForKey:kIASKDefaultValue];

                if (defaultValue) {
                    // get key from specifier and current stored preference value
                    NSString *key = [specifier objectForKey:kIASKKey];
                    id value = [defaults objectForKey:key];

                    // update preference value with default value if necessary
                    if (key && value == nil) {
                        [defaults setObject:defaultValue forKey:key];                        
                    }
                }
            }

        }

        [settingsReader release];
    }

    [preferenceFiles release];

    // synchornize stored preference values
    [defaults synchronize];
}


回答5:

I have based my answer on the one given by @Ojas. But I converted it to Swift. I hope this is useful for people using Swift

func processDefaultSettings() {

    let defaults = IASKSettingsStoreUserDefaults().defaults

    // settings files to process
    var preferenceFiles = [String]()

    // begin with Root file
    preferenceFiles.append("Root")

    // as other settings files are discovered will be added to preferencesFiles
    while !preferenceFiles.isEmpty {

        // init IASKSettingsReader for current settings file
        let file = preferenceFiles.last
        let settingsReader = IASKSettingsReader(file: file)
        preferenceFiles.removeLast()

        // extract preference specifiers
        if let preferenceSpecfiers = settingsReader.settingsDictionary[kIASKPreferenceSpecifiers] as? NSArray {

            // process each specifier in the current settings file
            for specifier in preferenceSpecfiers {

                // get type of current specifier
                let type = specifier[kIASKType] as! String

                // need to check child pane specifier for additional file
                if type == kIASKPSChildPaneSpecifier {
                    preferenceFiles.append(specifier[kIASKFile] as! String)
                }
                else {
                    // check if current specifier has a default value
                    if let defaultValue = specifier[kIASKDefaultValue] as AnyObject? {

                        // get key from specifier and current stored preference value
                        if let key = specifier[kIASKKey] as? String {

                            if let value = defaults.objectForKey(key) {
                                // Nothing to do - already set
                            }
                            else {
                                let appDefaults = [key: defaultValue]
                                defaults.registerDefaults(appDefaults)
                            }
                        }
                    }
                }
            }
        }
        // synchornize stored preference values
        defaults.synchronize()
    }
}


回答6:

I amended Steve's answer to his own question above and the following seems to work for me:

In your IASKAppSettingsViewController subclass-

- (void)viewDidLoad
{
    [super viewDidLoad];


    NSDictionary* settingsBundle = [self.settingsReader settingsBundle];
    NSArray *preferencesArray = [settingsBundle objectForKey:@"PreferenceSpecifiers"];
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

    //for each preference item, set its default if there is no value set
    for(NSDictionary *item in preferencesArray) {

        //get the item key, if there is no key then we can skip it
        NSString *key = [item objectForKey:@"Key"];
        if (key) {

            //check to see if the value and default value are set
            //if a default value exists and the value is not set, use the default
            id value = [defaults objectForKey:key];
            id defaultValue = [item objectForKey:@"DefaultValue"];
            if(defaultValue && !value) {
                [defaults setObject:defaultValue forKey:key];
            }
        }
    }

    //write the changes to disk
    [defaults synchronize];

}