设置捆绑值返回 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.objectForKey
和 ud.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
中查找值时,如果有明确设置的值,则返回该值。如果没有明确设置值,则参考注册的默认值。如果那里有一个值,则返回该默认值,否则返回nil
。 register
对任何现有值都没有任何影响。【参考方案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?