2
votes

I have created a default preferences plist file, and when the app is launched, I register those default values. But I allow the user to change a single setting which changes nearly all of the settings I've registered, in addition to changing each individual setting. Basically, I allow them to change a "theme" which changes almost every other stored setting. When the user does select a theme, instead of calling setObject:forKey for every single setting and manually defining what it should be for the selected theme, I am wondering if it is wise to create another plist file for each theme. I am thinking I could simply overwrite the values stored in NSUserDefaults where the keys match. And in doing so, I could also more easily detect when the app's settings are the same settings of any theme, or if they've customized a theme. I would simply detect if the value stored in NSUserDefaults equals that of the value stored in each theme plist for each key, and if any of them differ I know they have customized it and are not using a built-in theme. If I don't utilize a plist, I would have to compare each stored value against a manually defined value, therefore defining the default value for that theme in two different locations (where I set the settings when they select a theme and where I check to see if the current settings are the same settings of an available theme).

If that's an appropriate implementation, how does one overwrite existing values inNSUserDefaultsusing the values stored in a plist? If not, what would you recommend in this situation?

1
NSUserDefaults is not a database nor a replacement for a DataModel and persistence.zaph
I know. I'm using it for very simple settings (font family, color, weight, size, etc) that can be changed in the app, no more than 12. It just so happens one 'theme' setting will change all other settings, looking for the best way to work with that.Jordan H
I don't know if I would use a plist for this. I would just create a data model that represents a theme, and have it return specific values for each of the theme-specific preferences. Then have a central place where you query the current theme for the specified values, and saves them to NSUserDefaults. Multiple plist files seems overkill for this problem.Craig Otis

1 Answers

0
votes

See, settings is a concept in your application (storing and using a UI textSettings), which is better to design with abstraction from the actual implementation (persisting in .plists, databases et cetera) in mind.

All those individual values are grouped together by a single concept, and thus can be represented by a class in your project. This class "wraps up" implementation details related to the concept. This logic becomes isolated from other parts of your program, and when you need to change it, your changes are confined to a single entity, ergo every change is less likely to break the rest of your code.

This class can be implemented like that:

/////////////
// TextSettings.h

#import <Foundation/Foundation.h>

@interface TextSettings : NSObject <NSCoding>

@property (nonatomic, strong) UIColor *mainColor;
@property (nonatomic, copy) UIFont *font;

+ (instancetype)defaultTextSettings;

@end

/////////////
// TextSettings.m

@implementation TextSettings

+ (instancetype)defaultTextSettings
{
    TextSettings *textSettings = [[TextSettings alloc] init];
    textSettings.font = [UIFont systemFontOfSize:14.0f];
    textSettings.mainColor = [UIColor whiteColor];
    return textSettings;
}

#pragma mark - NSCoding

// Read more about NSCoding on: http://nshipster.com/nscoding/

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        _font = [aDecoder decodeObjectForKey:@"_font"];
        _mainColor = [aDecoder decodeObjectForKey:@"_mainColor"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.font forKey:@"_font"];
    [aCoder encodeObject:self.mainColor forKey:@"_mainColor"];
}

#pragma mark - Equality

- (BOOL)isEqual:(id)object
{
    if (object == nil) {
        return NO;
    }

    if ([object isKindOfClass:[self class]] == NO) {
        return NO;
    }

    TextSettings *otherTextSettings = object;

    return [self.font isEqual:otherTextSettings.font] && [self.mainColor isEqual:otherTextSettings.mainColor];
}

// You must override -hash if you override -isEqual
- (NSUInteger)hash
{
    return self.class.hash ^ self.font.hash ^ self.mainColor.hash;
}

@end

When you have such an object you can:

  • easily test them for equality
  • easily archive and unarchive (serialize and deserialize) them

Equality testing

// TextSettings *myTextSettings
if ([myTextSettings isEqual:[TextSettings defaultTextSettings]]) {
    // User didn't change the textSettings...
} 
else {
    // User changed the textSettings!
}

Serialization/deserialization

// In a controller responsible for displaying something according to textSettings.
// textSettings (self.textSettings) is a @property in this controller.
- (void)saveCurrentTextSettings 
{
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.textSettings];
    [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"currentTextSettings"];
}

- (void)loadTextSettings
{
    NSData *data = [[NSUserDefaults standardUserDefaults] dataForKey:@"currentTextSettings"];
    self.textSettings = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}

That's pretty much it.

When you want to add new fields to TextSettings, you must (1) declare them as @properties, (2) add checks to -isEqual: and -hash implementations and (3) add unarchiving/archiving code to -encodeWithCoder and -initWithCoder.

That's too much! you may say, but I'd say no – it is hardly an overkill. Definitely better than searching and comparing individual values in NSUserDefaults.


UPD:

To use it as just plain settings:

// Called when user chooses new value
- (void)userDidChooseFont:(UIFont *)font
{
    self.textSettings.font = font;
    [self saveTextSettings];
}

- (void)saveTextSettings
{
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.textSettings];
    [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"textSettings"];
}