【问题标题】:Can you make the settings in Settings.bundle default even if you don't open the Settings App即使您不打开设置应用程序,您能否将 Settings.bundle 中的设置设为默认值
【发布时间】:2010-10-05 08:15:09
【问题描述】:

我有一个带有 settings.bundle 的 iPhone 应用程序,用于处理我的应用程序的各种设置。我可以在我的 root.plist 文件中设置默认值(使用 DefaultValue 属性),但这些仅在用户第一次打开设置应用程序时使用。有什么方法可以在您的应用程序安装时写出这些值?我知道我可以编写代码来检查我的应用程序的首次启动,然后将它们写出来,但是它们在两个不同的地方。

这里以我的 root.plist 中的一个条目为例:

<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>

最终结果应该是,如果我询问“open_to_top_location”,我会得到“是”,而不是在用户第一次打开“设置”应用程序之前它根本不存在。

有什么想法吗?

【问题讨论】:

    标签: iphone cocoa-touch


    【解决方案1】:

    如果我理解正确,您希望避免两次指定默认值(一次作为 Settings.bundle/Root.plist 文件中的“DefaultValue”键,一次在您的应用程序初始化代码中),因此您不必让它们保持同步。

    由于 Settings.bundle 存储在应用程序包本身中,因此您只需读取那里给出的默认值即可。我整理了一些示例代码,查看设置包并读取那里每个键的默认值。请注意,这不会写出默认键;如果它们不存在,您需要在每次启动时阅读并注册它们(随意更改)。我只做了一些粗略的测试,所以请确保它适用于所有情况。

    - (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];
    }
    

    【讨论】:

    • 我希望有一些我缺少的魔法捆绑设置,比如“WriteDefaultsOnInstall”,但如果没有这个设置,谢谢:)
    • 此解决方案需要注意的一点 - 如果您的默认值是布尔值,则它不起作用,因为如果默认值设置为 NO 或不设置,“if(!name)”将返回 NO不存在。为了解决这个问题,我改用 objectForKey,因为它会返回 0、1 或 null。
    • 值得注意的是registerDefaultsFromSettingsBundle不需要限制为条件执行:它可以一直安全地运行,因为registerDefaults永远不会覆盖现有的设置;它只为没有值的设置提供默认值。然而,避免冗余执行可以节省一点时间。
    • 请注意,使用 nil 键在 defaultsToRegister 上调用 -setObject: 将引发异常。如果Key 存在但DefaultValue 不是给定的偏好,我相信使用上面的代码会发生这种情况。这适用于例如文本字段首选项 (developer.apple.com/library/ios/#documentation/…)。
    • 我必须注意,这些东西不适用于本地化应用程序:它会打开 [settingsBundle stringByAppendingPathComponent:@"Root.plist"]],而对于本地化应用程序,您需要 localeid.lproj/Root.plist。其次,[defaultsToRegister setObject:...] 引发 nil 值异常。
    【解决方案2】:

    这是我基于@PCheese 的answer 的代码,它增加了对没有默认值的键和子窗格的支持。

    - (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;
    }
    

    【讨论】:

      【解决方案3】:

      这里是 Swift 版本: 从以下位置调用它:

      func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
          // Override point for customization after application launch.
          self.registerDefaultsFromSettingsBundle()
      
          return true
      }
      

      转换函数:

      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()
      }
      

      【讨论】:

      • “prefSpecification”可能没有键“DefaultValue”,因为结果 objectToSet 将为 NULL。解决方法:----if let objectToSet: AnyObject = prefSpecification.objectForKey("DefaultValue") as AnyObject?---
      • 此代码在 Swift 2 中不起作用...Cannot convert value of type 'NSMutableDictionary' to expected argument type '[String : AnyObject]' 如何修复?
      • 暂时这样做...: func registerDefaultsFromSettingsBundle(){ //NSLog("Registering default values from Settings.bundle");让 defs: NSUserDefaults = NSUserDefaults.standardUserDefaults() defs.synchronize() 让 settingsBundle: NSString = NSBundle.mainBundle().pathForResource("Settings", ofType: "bundle")! if(settingsBundle.containsString("")){ NSLog("找不到 Settings.bundle");返回; }
      • 让设置:NSDictionary = NSDictionary(contentsOfFile: settingsBundle.stringByAppendingPathComponent("Root.plist"))!让首选项:NSArray = settings.objectForKey("PreferenceSpecifiers") 为! NSArray let defaultsToRegister: NSMutableDictionary = NSMutableDictionary(capacity: preferences.count) for prefSpecification in preferences {
      • if (prefSpecification.objectForKey("Key") != nil) { let key: NSString = prefSpecification.objectForKey("Key")!作为! NSString if !key.containsString("") { let currentObject: AnyObject? = defs.objectForKey(key as String) if currentObject == nil { // 不可读:从 Settings.bundle 设置值
      【解决方案4】:

      Swift 3 版本

          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)
      }
      

      【讨论】:

        【解决方案5】:

        兼容 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()
        }
        

        【讨论】:

          【解决方案6】:

          同一主题的另一个版本。我保留了 Lawrence Johnston 对子窗格的支持,并添加了 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"]];
          }
          

          - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions拨打[self registerDefaultsFromSettingsBundle];

          if(x) {NSAssert(x);return nil;} 看起来很蠢,但我懒得做点什么。

          【讨论】:

            【解决方案7】:

            另一种方法:代码生成

            下面会生成一个带有单个函数的 Objective-C 文件,该函数为 Root.plist 注册默认值。

            xsltproc settings.xslt Settings.bundle/Root.plist > registerDefaults.m
            

            可以使用 XCode 中的“运行脚本”构建阶段自动运行 In。该阶段应放在“编译源”之前。 (xsltproc 随 OS X 一起提供。)

            这有点基本,不处理嵌套文件,但也许有人用它。

            settings.xslt

            <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>
            

            这是基于Benjamin Ragheb的工作。

            【讨论】:

              【解决方案8】:

              更简洁的 swift 2.2 版本,需要对字符串进行快速扩展才能恢复 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)
              }
              

              【讨论】:

                猜你喜欢
                • 2011-05-28
                • 2011-03-19
                • 1970-01-01
                • 2021-06-14
                • 2018-02-07
                • 2011-05-31
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多