J'ai une application iPhone avec un settings.bundle qui gère divers paramètres pour mon application. Je peux définir des valeurs par défaut dans mon fichier root.plist (à l'aide de la propriété DefaultValue), mais celles-ci ne sont utilisées que la première fois que l'utilisateur ouvre l'application de configuration. Existe-t-il un moyen d’écrire ces valeurs lors de l’installation de votre application? Je sais que je peux simplement écrire du code qui vérifie le premier lancement de mon application, puis les écrire, mais ils se trouvent ensuite à deux endroits différents.
Voici une entrée de mon root.plist à titre d'exemple:
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Open To Top Location</string>
<key>Key</key>
<string>open_top_location</string>
<key>DefaultValue</key>
<string>YES</string>
<key>TrueValue</key>
<string>YES</string>
<key>FalseValue</key>
<string>NO</string>
</dict>
Le résultat final devrait être que si je demande 'open_to_top_location', je reçois un OUI, au lieu qu'il ne soit pas disponible du tout jusqu'à la première fois que l'utilisateur ouvre l'application Paramètres.
Des idées?
Si je vous ai bien compris, vous voulez éviter de spécifier deux fois les valeurs par défaut (une fois en tant que clés "DefaultValue" dans votre fichier Settings.bundle/Root.plist et une fois dans le code d'initialisation de votre application) afin que vous n'ayez pas à les conserver sync.
Comme Settings.bundle est stocké dans l’application elle-même, il vous suffit de lire les valeurs par défaut indiquées ici. J'ai mis en place un exemple de code qui examine l'ensemble de paramètres et lit les valeurs par défaut pour chaque clé. Notez que cela n’écrit pas les clés par défaut; s'ils n'existent pas, vous devrez les lire et les enregistrer à chaque lancement (n'hésitez pas à changer cela). J'ai seulement fait quelques tests superficiels, alors assurez-vous que cela fonctionne pour vous dans tous les cas.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:@"name"];
NSLog(@"name before is %@", name);
// Note: this will not work for boolean values as noted by bpapa below.
// If you use booleans, you should use objectForKey above and check for null
if(!name) {
[self registerDefaultsFromSettingsBundle];
name = [[NSUserDefaults standardUserDefaults] stringForKey:@"name"];
}
NSLog(@"name after is %@", name);
}
- (void)registerDefaultsFromSettingsBundle {
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
if(!settingsBundle) {
NSLog(@"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:@"Root.plist"]];
NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:@"Key"];
if(key && [[prefSpecification allKeys] containsObject:@"DefaultValue"]) {
[defaultsToRegister setObject:[prefSpecification objectForKey:@"DefaultValue"] forKey:key];
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
[defaultsToRegister release];
}
Voici mon code basé sur answer de @ PCheese qui ajoute le support pour les clés sans valeur par défaut et les sous-fenêtres enfants.
- (void)registerDefaultsFromSettingsBundle {
[[NSUserDefaults standardUserDefaults] registerDefaults:[self defaultsFromPlistNamed:@"Root"]];
}
- (NSDictionary *)defaultsFromPlistNamed:(NSString *)plistName {
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
NSAssert(settingsBundle, @"Could not find Settings.bundle while loading defaults.");
NSString *plistFullName = [NSString stringWithFormat:@"%@.plist", plistName];
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:plistFullName]];
NSAssert1(settings, @"Could not load plist '%@' while loading defaults.", plistFullName);
NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
NSAssert1(preferences, @"Could not find preferences entry in plist '%@' while loading defaults.", plistFullName);
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:@"Key"];
id value = [prefSpecification objectForKey:@"DefaultValue"];
if(key && value) {
[defaults setObject:value forKey:key];
}
NSString *type = [prefSpecification objectForKey:@"Type"];
if ([type isEqualToString:@"PSChildPaneSpecifier"]) {
NSString *file = [prefSpecification objectForKey:@"File"];
NSAssert1(file, @"Unable to get child plist name from plist '%@'", plistFullName);
[defaults addEntriesFromDictionary:[self defaultsFromPlistNamed:file]];
}
}
return defaults;
}
Voici la version de Swift: Appelez-la de:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.registerDefaultsFromSettingsBundle()
return true
}
fonction convertie:
func registerDefaultsFromSettingsBundle(){
//NSLog("Registering default values from Settings.bundle");
let defs: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defs.synchronize()
var settingsBundle: NSString = NSBundle.mainBundle().pathForResource("Settings", ofType: "bundle")!
if(settingsBundle.containsString("")){
NSLog("Could not find Settings.bundle");
return;
}
var settings: NSDictionary = NSDictionary(contentsOfFile: settingsBundle.stringByAppendingPathComponent("Root.plist"))!
var preferences: NSArray = settings.objectForKey("PreferenceSpecifiers") as NSArray
var defaultsToRegister: NSMutableDictionary = NSMutableDictionary(capacity: preferences.count)
for prefSpecification in preferences {
if (prefSpecification.objectForKey("Key") != nil) {
let key: NSString = prefSpecification.objectForKey("Key")! as NSString
if !key.containsString("") {
let currentObject: AnyObject? = defs.objectForKey(key)
if currentObject == nil {
// not readable: set value from Settings.bundle
let objectToSet: AnyObject? = prefSpecification.objectForKey("DefaultValue")
defaultsToRegister.setObject(objectToSet!, forKey: key)
NSLog("Setting object \(objectToSet) for key \(key)")
}else{
//already readable: don't touch
//NSLog("Key \(key) is readable (value: \(currentObject)), nothing written to defaults.");
}
}
}
}
defs.registerDefaults(defaultsToRegister)
defs.synchronize()
}
Swift 3 version
func registerDefaultsFromSettingsBundle(){
guard let settingsBundle = Bundle.main.path(forResource: "Settings", ofType: "bundle") else {
print("Could not locate Settings.bundle")
return
}
guard let settings = NSDictionary(contentsOfFile: settingsBundle+"/Root.plist") else {
print("Could not read Root.plist")
return
}
let preferences = settings["PreferenceSpecifiers"] as! NSArray
var defaultsToRegister = [String: AnyObject]()
for prefSpecification in preferences {
if let post = prefSpecification as? [String: AnyObject] {
guard let key = post["Key"] as? String,
let defaultValue = post["DefaultValue"] else {
continue
}
defaultsToRegister[key] = defaultValue
}
}
UserDefaults.standard.register(defaults: defaultsToRegister)
}
Une version compatible Swift 2
func registerDefaultsFromSettingsBundle(){
let defaults = NSUserDefaults.standardUserDefaults()
defaults.synchronize()
let settingsBundle: NSString = NSBundle.mainBundle().pathForResource("Settings", ofType: "bundle")!
if(settingsBundle.containsString("")){
NSLog("Could not find Settings.bundle");
return;
}
let settings = NSDictionary(contentsOfFile: settingsBundle.stringByAppendingPathComponent("Root.plist"))!
let preferences = settings.objectForKey("PreferenceSpecifiers") as! NSArray;
var defaultsToRegister = [String: AnyObject](minimumCapacity: preferences.count);
for prefSpecification in preferences {
if (prefSpecification.objectForKey("Key") != nil) {
let key = prefSpecification.objectForKey("Key")! as! String
if !key.containsString("") {
let currentObject = defaults.objectForKey(key)
if currentObject == nil {
// not readable: set value from Settings.bundle
let objectToSet = prefSpecification.objectForKey("DefaultValue")
defaultsToRegister[key] = objectToSet!
NSLog("Setting object \(objectToSet) for key \(key)")
}
}
}
}
defaults.registerDefaults(defaultsToRegister)
defaults.synchronize()
}
Une version beaucoup plus propre de Swift 2.2 nécessite une extension rapide sur une chaîne pour restaurer stringByAppendingPathComponent
:
extension String {
func stringByAppendingPathComponent(path: String) -> String {
let nsSt = self as NSString
return nsSt.stringByAppendingPathComponent(path)
}
}
func registerDefaultsFromSettingsBundle() {
guard let settingsBundle = NSBundle.mainBundle().pathForResource("Settings", ofType: "bundle") else {
log.debug("Could not find Settings.bundle")
return
}
let settings = NSDictionary(contentsOfFile: settingsBundle.stringByAppendingPathComponent("Root.plist"))!
let preferences = settings["PreferenceSpecifiers"] as! NSArray
var defaultsToRegister = [String: AnyObject]()
for prefSpecification in preferences {
guard let key = prefSpecification["Key"] as? String,
let defaultValue = prefSpecification["DefaultValue"] else {
continue
}
defaultsToRegister[key] = defaultValue
}
NSUserDefaults.standardUserDefaults().registerDefaults(defaultsToRegister)
}
Une approche différente: la génération de code
Ce qui suit génère un fichier Objective-C avec une fonction unique qui enregistre les valeurs par défaut pour Root.plist.
xsltproc settings.xslt Settings.bundle/Root.plist > registerDefaults.m
In peut être exécuté automatiquement en utilisant une phase de construction "Run Script" dans XCode. La phase doit être placée avant "Compiler les sources". (xsltproc est fourni avec OS X.)
Ceci est quelque peu basique et ne gère pas les fichiers imbriqués, mais peut-être que quelqu'un en a une utilisation.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes" indent="no" />
<xsl:template match="dict">
<xsl:choose>
<xsl:when test="key[.='DefaultValue']/following-sibling::*[position()=1 and self::true]">
@"YES",
</xsl:when>
<xsl:when test="key[.='DefaultValue']/following-sibling::*[position()=1 and self::false]">
@"NO",
</xsl:when>
<xsl:otherwise>
@"<xsl:value-of select="key[.='DefaultValue']/following-sibling::*[1]"/>",
</xsl:otherwise>
</xsl:choose>
@"<xsl:value-of select="key[.='Key']/following-sibling::*[1]"/>",
</xsl:template>
<xsl:template match="/">
void registerDefaults() {
NSDictionary *defaults =
[NSDictionary dictionaryWithObjectsAndKeys:
<xsl:apply-templates select="descendant::key[.='DefaultValue']/.."/>
nil];
[[NSUserDefaults standardUserDefaults] registerDefaults: defaults];
}
</xsl:template>
</xsl:stylesheet>
Le est basé sur le travail de Benjamin Ragheb .
Une version de plus du même thème. J'ai gardé le soutien de Lawrence Johnston pour les vitrages enfants et ajouté le support i18n/l10n.
// This code is folklore, first created by an unknown person and copied, pasted
// and published by many different programmers, each (hopefully) of whom added
// some improvemrnts. (c) the People of the Earth
- (NSDictionary *)defaultsFromPlistNamed:(NSString *)plistName {
NSString *settingsBundlePath = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
if (!settingsBundlePath) {
NSAssert(settingsBundlePath, @"Could not find Settings.bundle while loading defaults.");
return nil;
}
NSBundle *settingsBundle = [NSBundle bundleWithPath:settingsBundlePath];
if (!settingsBundlePath) {
NSAssert(settingsBundle, @"Could not load Settings.bundle while loading defaults.");
return nil;
}
NSString *plistFullName = [settingsBundle pathForResource:plistName ofType:@"plist"];
if (!plistName) {
NSAssert1(settings, @"Could not find plist '%@' while loading defaults.", plistFullName);
return nil;
}
NSDictionary *settings_dic = [NSDictionary dictionaryWithContentsOfFile:plistFullName];
if (!settings_dic) {
NSAssert1(settings_dic, @"Could not load plist '%@' while loading defaults.", plistFullName);
return nil;
}
NSArray *preferences = [settings_dic objectForKey:@"PreferenceSpecifiers"];
NSAssert1(preferences, @"Could not find preferences entry in plist '%@' while loading defaults.", plistFullName);
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:@"Key"];
if (key) {
id value = [prefSpecification objectForKey:@"DefaultValue"];
if(value) {
[defaults setObject:value forKey:key];
NSLog(@"setting %@ = %@",key,value);
}
}
NSString *type = [prefSpecification objectForKey:@"Type"];
if ([type isEqualToString:@"PSChildPaneSpecifier"]) {
NSString *file = [prefSpecification objectForKey:@"File"];
NSAssert1(file, @"Unable to get child plist name from plist '%@'", plistFullName);
if (file) {
[defaults addEntriesFromDictionary:[self defaultsFromPlistNamed:file]];
}
}
}
return defaults;
}
- (void)registerDefaultsFromSettingsBundle {
[[NSUserDefaults standardUserDefaults] registerDefaults:[self defaultsFromPlistNamed:@"Root"]];
}
Appelez [self registerDefaultsFromSettingsBundle];
à partir de - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
if(x) {NSAssert(x);return nil;}
a l'air stupide, mais je me sens paresseux de faire quelque chose à ce sujet.