设置捆绑值返回 nil

Posted

技术标签:

【中文标题】设置捆绑值返回 nil【英文标题】:Settings bundle values returning nil 【发布时间】:2016-07-15 05:16:38 【问题描述】:

我在我的应用中添加了一个设置包,在 Xcode 中它出现在我的项目树视图的根目录中。

Root.plist 文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>StringsTable</key>
    <string>Root</string>
    <key>PreferenceSpecifiers</key>
    <array>
        <dict>
            <key>Type</key>
            <string>PSGroupSpecifier</string>
            <key>Title</key>
            <string>Service</string>
        </dict>
        <dict>
            <key>Type</key>
            <string>PSTextFieldSpecifier</string>
            <key>Title</key>
            <string>Hostname</string>
            <key>Key</key>
            <string>service_hostname</string>
   <!-- and so on -->

当我在 ios 上打开“设置”应用时,该条目会出现在底部,我可以完美地显示和编辑我的设置。

但是我无法从代码中检索这些值。这是我的代码:

static func loadSettings() 

    let ud = NSUserDefaults.standardUserDefaults()
    ud.synchronize()

    Settings.hostName = ud.stringForKey("service_hostname")
    // etc

我还尝试了 ud.objectForKeyud.valueForKey - 两者都返回 nil

设置Settings.hostName 后,Xcode 调试器报告它的值为nil,尽管我在“设置”应用中设置了显式值。

我看到了这个帖子 (iPhone App : How to get default value from root.plist?),其中有人发布了一段 Objective-C 代码,该代码手动将 Root.plist 文件直接加载到 NSMutableDictionary 并调用 NSUserDefaults.standardUserDefaults().registerDefaults 但这似乎是一个 hack(我无法让它在 Swift 中工作,因为编译器说 stringByAppendingPathComponent 不再存在)

为什么NSUserDefaults 没有从“设置”应用中获取设置?

【问题讨论】:

【参考方案1】:

显然原因是如果我在plist 中的设置定义了默认值并且用户没有明确设置值,那么设置应用程序中显示的值将是plist 文件中的默认值,但是NSUserDefaults API 仍将返回 nil

不幸的是,这意味着如果默认值有意义(例如默认 Web 服务地址 URI:“http://www.example.com”)它必须在我的项目中存在两次:作为默认值在 plist 和我的程序中代码:

根.plist:

  <dict>
      <key>Key</key>          <string>mySettingKey</string>
      <key>Title</key>        <string>Some address</string>
      <key>Type</key>         <string>PSTextFieldSpecifier</string>
      <key>DefaultValue</key> <string>http://www.example.com</string>
      <key>IsSecure</key>     <false />
      <key>KeyboardType</key> <string>Alphabet</string>
  </dict>

Program.swift:

let ud = NSUserDefaults.standardUserDefaults()
ud.synchronize()

var mySettingValue = ud.stringForKey("mySettingKey")
if mySettingValue == nil 
    mySettingValue = "http://www.example.com"

这太令人惊讶了。

【讨论】:

我想我快疯了。找了很久的解决方法!为什么会这样?有谁知道吗? @Dai 这对我不起作用,它给出 nil 值.. 我错过了什么吗? @mm24 “它给出了一个 nil 值” - 这就是我的帖子的内容,以及为什么解决方案是检查 nil 值并在代码中提供一个默认值。 @Dai 谢谢!我遇到的问题是我在 Settings.bundle 中提供了一个默认值,那么为什么我还需要以编程方式提供一个默认值呢? @mm24 我不知道我们为什么要这样做,但我们必须:)【参考方案2】:

可以从Settings.bundle 中获取默认值并添加到 UserDefaults。可以在AppDelegate.swift 中从didFinishLaunchingWithOptions 调用以下函数。

func setDefaultsFromSettingsBundle() 
    //Read PreferenceSpecifiers from Root.plist in Settings.Bundle
    if let settingsURL = Bundle.main.url(forResource: "Root", withExtension: "plist", subdirectory: "Settings.bundle"),
        let settingsPlist = NSDictionary(contentsOf: settingsURL),
        let preferences = settingsPlist["PreferenceSpecifiers"] as? [NSDictionary] 

        for prefSpecification in preferences 

            if let key = prefSpecification["Key"] as? String, let value = prefSpecification["DefaultValue"] 

                //If key doesn't exists in userDefaults then register it, else keep original value
                if UserDefaults.standard.value(forKey: key) == nil 

                    UserDefaults.standard.set(value, forKey: key)
                    NSLog("registerDefaultsFromSettingsBundle: Set following to UserDefaults - (key: \(key), value: \(value), type: \(type(of: value)))")
                
            
        
     else 
        NSLog("registerDefaultsFromSettingsBundle: Could not find Settings.bundle")
    

【讨论】:

这里有一个更好的用途。获得preferences 后,将其与UserDefaults register 一起使用。这比循环并显式设置每个值要好得多。 @rmaddy 在这里我检查 UserDefaults 是否已经包含特定的密钥,并且在这种情况下我没有设置。不确定在这种情况下 UserDefaults.register(:) 将如何工作 您可以将整个for 循环替换为对register 的调用。而已。不需要循环。无需显式设置任何值。只需使用那一行注册默认值,您就完成了。简单得多。 @rmaddy 我认为我们在这里并不了解对方。如果我将for 循环替换为检查register 中是否已存在UserDefaults 中的键,则同时更改并位于UserDefaults(相同键)中的所有设置都将替换为Settings.bundle 中的默认值。如果应用程序已经安装并运行了一段时间,则可能存在密钥,用户可能已经更改了设置中的某些内容,并且应该保持不变。但是您是正确的,您的解决方案要简单得多,如果之前未安装应用程序(新应用程序),我会使用您的解决方案。 没有。对register 的调用不会更改任何值。这不是它的工作原理。当您尝试在 UserDefaults 中查找值时,如果有明确设置的值,则返回该值。如果没有明确设置值,则参考注册的默认值。如果那里有一个值,则返回该默认值,否则返回nilregister 对任何现有值都没有任何影响。【参考方案3】:

您应该注册您的默认设置,以便同步。 source from here

// Swift 3
var appDefaults = Dictionary<String, AnyObject>()
appDefaults["mySettingKey"] = "http://www.example.com" // Default Value

UserDefaults.standard.register(appDefaults)
UserDefaults.standard.synchronize()

let mySettingValue = UserDefaults.standard.string(forKey: "mySettingKey")

请记住,当您的应用使用设置包时,您还应该使用 registerDefaults:。由于您已经在设置包的 plist 中指定了默认值,因此您可能希望您的应用程序自动选择这些值。然而,事实并非如此。设置包中包含的信息只能由 iOS Settings.app 读取,而不会由您的应用读取。为了让您的应用使用 Settings.app 中显示的相同默认值,您必须手动将用户默认键及其默认值复制到单独的 plist 文件中,并将其注册到默认数据库,如上所示。

【讨论】:

有趣的是,当我注册并同步我的值时,然后立即转身查询它们的值,我什么也没得到……不得不做旧式的“setObject:forKey:”。 调用synchronize 在这里毫无意义。 1. 调用register 实际上并没有改变任何东西,所以没有什么要同步的。 2. 调用synchronize 从来都不需要。【参考方案4】:

您需要手动从 Settings.bundle 中读取默认值并将其添加到 UserDefaults 注册域,这可以通过 UserDefaults.register() 完成。通过使用注册域,值保存在内存中(易失性),因此不需要调用UserDefaults.synchronize()

斯威夫特 4 和 5

这里有几行可以完成工作的 Swift 代码:

// Register the default values from Settings.bundle
if let settingsURL = Bundle.main.url(forResource: "Root", withExtension: "plist", subdirectory: "Settings.bundle"),
    let settingsRootDict = NSDictionary(contentsOf: settingsURL),
    let prefSpecifiers = settingsRootDict["PreferenceSpecifiers"] as? [NSDictionary],
    let keysAndValues = prefSpecifiers.map( d in (d["Key"], d["DefaultValue"]) ) as? [(String, Any)] 
        UserDefaults.standard.register(defaults: Dictionary(uniqueKeysWithValues: keysAndValues))

【讨论】:

以上是关于设置捆绑值返回 nil的主要内容,如果未能解决你的问题,请参考以下文章

-[NSBundle pathForResource:ofType:inDirectory:] 为本地化资源返回 nil

SwiftKeychainWrapper xctest 返回 nil

解决pathForResource返回nil, 无法读取plist文件问题

Lua 函数如何返回 nil,即使函数内部的返回值不是 nil?

indexPathForItemAtPoint 偶尔返回 nil

核心数据值返回 Nil