在 Swift[UI] 中疯狂使用 UserDefaults
Going crazy with UserDefaults in Swift[UI]
开始使用 Swift 和 SwiftUI,我发现从 UIKit 迁移的过程非常困难。
目前被 UserDefaults 所困扰,即使在尝试理解我在网上找到的许多教程之后也是如此。
请告诉我我做错了什么:
非常简单的代码:
- 向 UserDefault 注册一个布尔值,
- 在文本中显示该布尔值!
没有比这更简单的了。
但我无法让它工作,因为调用 UserDefaults 会抛出此错误消息:
Instance method 'appendInterpolation' requires that 'Bool' conform to '_FormatSpecifiable'
我的 "app" 是默认的单视图应用程序,具有以下 2 个更改:
1- 在 AppDelegate 中,我注册了我的布尔值:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UserDefaults.standard.register(defaults: [
"MyBool 1": true
])
return true
}
2- 在 ContentView 中,我尝试显示它(在 struct ContentView: View 中):
let defaults = UserDefaults.standard
var body: some View {
Text("The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1"))")
}
有什么想法吗?
谢谢
不确定为什么要使用 register
,但您可以像这样设置 bool 值:
UserDefaults.standard.set(true, forKey: "MyBool1")
在 SwiftUI 中我使用这些:
UserDefaults.standard.set(true, forKey: "MyBool 1")
let bull = UserDefaults.standard.bool(forKey: "MyBool 1")
试试这个:
struct MyView {
private let userDefaults: UserDefaults
// Allow for dependency injection, should probably be some protocol instead of `UserDefaults` right away
public init(userDefaults: UserDefaults = .standard) {
self.userDefaults = userDefaults
}
}
// MARK: - View
extension MyView: View {
var body: some View {
Text("The BOOL 1 value is: \(self.descriptionOfMyBool1)")
}
}
private extension MyView {
var descriptionOfMyBool1: String {
let key = "MyBool 1"
return "\(boolFromDefaults(key: key))"
}
// should probably not be here... move to some KeyValue protocol type, that you use instead of `UserDefaults`...
func boolFromDefaults(key: String) -> Bool {
userDefaults.bool(forKey: key)
}
}
你的问题是 Text(...)
初始值设定项采用 LocalizedStringKey
而不是 String
,后者在其字符串插值中支持与普通字符串不同的类型(不包括 Bool
显然)。
有几种方法可以解决这个问题。
您可以使用 Text
初始化器,它接受 String
并逐字显示它而不尝试进行任何本地化:
var body: some View {
Text(verbatim: "The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1"))")
}
或者,您可以扩展 LocalizedStringKey.StringInterpolation
以支持布尔值,然后您的原始代码应该可以工作:
extension LocalizedStringKey.StringInterpolation {
mutating func appendInterpolation(_ value: Bool) {
appendInterpolation(String(value))
}
}
要解决您的问题,只需添加描述变量,例如:
var body: some View {
Text("The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1").description)")
}
我认为使用 UserDefaults 的最佳方式是在 class 中。它帮助我们使用 @ObservedObject
属性 包装器从任何模型订阅 class。
布尔方法可用于 types
的其余部分
//
// ContentView.swift
//
import SwiftUI
struct ContentView: View {
@ObservedObject var data = UserData()
var body: some View {
VStack {
Toggle(isOn: $data.isLocked){ Text("Locked") }
List(data.users) { user in
Text(user.name)
if data.isLocked {
Text("User is Locked")
} else {
Text("User is Unlocked")
}
}
}
}
}
//
// Model.swift
//
import SwiftUI
import Combine
let defaults = UserDefaults.standard
let usersData: [User] = loadJSON("Users.json")
// This is custom JSON loader and User struct is to be defined
final class UserData: ObservableObject {
// Saving a Boolean
@Published var isLocked = defaults.bool(forKey: "Locked") {
didSet {
defaults.set(self.isLocked, forKey: "Locked")
}
}
// Saving Object after encoding
@Published var users: [User] {
// didSet will only work if used as Binding variable. Else need to create a save method, which same as the following didSet code.
didSet {
// Encoding Data to UserDefault if value of user data change
if let encoded = try? JSONEncoder().encode(users) {
defaults.set(encoded, forKey: "Users")
}
}
}
init() {
// Decoding Data from UserDefault
if let users = defaults.data(forKey: "Users") {
if let decoded = try? JSONDecoder().decode([User].self, from: users) {
self.users = decoded
return
}
}
// Fallback value if key "Users" is not found
self.users = usersData
}
// resetting UserDefaults to initial values
func resetData() {
defaults.removeObject(forKey: "Users")
self.isLocked = false
self.users = usersData
}
}
注意:此代码未经测试。这里直接打字
回答您的问题:
1- 将布尔值注册到 UserDefault,
2- 在文本中显示布尔值!
我测试了以下代码并确认它可以在 ios 13.4 和使用催化剂的 macos 上运行。注意 String(...) 包装。
在 class AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// UserDefaults.standard.register(defaults: ["MyBool 1": true])
UserDefaults.standard.set(true, forKey: "MyBool 1")
return true
}
在内容视图中
import SwiftUI
struct ContentView: View {
@State var defaultValue = false // for testing
let defaults = UserDefaults.standard
var body: some View {
VStack {
Text("bull = \(String(UserDefaults.standard.bool(forKey: "MyBool 1")))")
Text(" A The BOOL 1 value is Bool 1 = \(String(defaultValue))")
Text(" B The BOOL 1 value is : Bool 1 = \(String(defaults.bool(forKey: "MyBool 1")))")
}
.onAppear(perform: loadData)
}
func loadData() {
defaultValue = defaults.bool(forKey: "MyBool 1")
print("----> defaultValue: \(defaultValue) ")
}
}
开始使用 Swift 和 SwiftUI,我发现从 UIKit 迁移的过程非常困难。 目前被 UserDefaults 所困扰,即使在尝试理解我在网上找到的许多教程之后也是如此。
请告诉我我做错了什么: 非常简单的代码:
- 向 UserDefault 注册一个布尔值,
- 在文本中显示该布尔值!
没有比这更简单的了。 但我无法让它工作,因为调用 UserDefaults 会抛出此错误消息:
Instance method 'appendInterpolation' requires that 'Bool' conform to '_FormatSpecifiable'
我的 "app" 是默认的单视图应用程序,具有以下 2 个更改:
1- 在 AppDelegate 中,我注册了我的布尔值:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UserDefaults.standard.register(defaults: [
"MyBool 1": true
])
return true
}
2- 在 ContentView 中,我尝试显示它(在 struct ContentView: View 中):
let defaults = UserDefaults.standard
var body: some View {
Text("The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1"))")
}
有什么想法吗?
谢谢
不确定为什么要使用 register
,但您可以像这样设置 bool 值:
UserDefaults.standard.set(true, forKey: "MyBool1")
在 SwiftUI 中我使用这些:
UserDefaults.standard.set(true, forKey: "MyBool 1")
let bull = UserDefaults.standard.bool(forKey: "MyBool 1")
试试这个:
struct MyView {
private let userDefaults: UserDefaults
// Allow for dependency injection, should probably be some protocol instead of `UserDefaults` right away
public init(userDefaults: UserDefaults = .standard) {
self.userDefaults = userDefaults
}
}
// MARK: - View
extension MyView: View {
var body: some View {
Text("The BOOL 1 value is: \(self.descriptionOfMyBool1)")
}
}
private extension MyView {
var descriptionOfMyBool1: String {
let key = "MyBool 1"
return "\(boolFromDefaults(key: key))"
}
// should probably not be here... move to some KeyValue protocol type, that you use instead of `UserDefaults`...
func boolFromDefaults(key: String) -> Bool {
userDefaults.bool(forKey: key)
}
}
你的问题是 Text(...)
初始值设定项采用 LocalizedStringKey
而不是 String
,后者在其字符串插值中支持与普通字符串不同的类型(不包括 Bool
显然)。
有几种方法可以解决这个问题。
您可以使用 Text
初始化器,它接受 String
并逐字显示它而不尝试进行任何本地化:
var body: some View {
Text(verbatim: "The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1"))")
}
或者,您可以扩展 LocalizedStringKey.StringInterpolation
以支持布尔值,然后您的原始代码应该可以工作:
extension LocalizedStringKey.StringInterpolation {
mutating func appendInterpolation(_ value: Bool) {
appendInterpolation(String(value))
}
}
要解决您的问题,只需添加描述变量,例如:
var body: some View {
Text("The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1").description)")
}
我认为使用 UserDefaults 的最佳方式是在 class 中。它帮助我们使用 @ObservedObject
属性 包装器从任何模型订阅 class。
布尔方法可用于 types
的其余部分//
// ContentView.swift
//
import SwiftUI
struct ContentView: View {
@ObservedObject var data = UserData()
var body: some View {
VStack {
Toggle(isOn: $data.isLocked){ Text("Locked") }
List(data.users) { user in
Text(user.name)
if data.isLocked {
Text("User is Locked")
} else {
Text("User is Unlocked")
}
}
}
}
}
//
// Model.swift
//
import SwiftUI
import Combine
let defaults = UserDefaults.standard
let usersData: [User] = loadJSON("Users.json")
// This is custom JSON loader and User struct is to be defined
final class UserData: ObservableObject {
// Saving a Boolean
@Published var isLocked = defaults.bool(forKey: "Locked") {
didSet {
defaults.set(self.isLocked, forKey: "Locked")
}
}
// Saving Object after encoding
@Published var users: [User] {
// didSet will only work if used as Binding variable. Else need to create a save method, which same as the following didSet code.
didSet {
// Encoding Data to UserDefault if value of user data change
if let encoded = try? JSONEncoder().encode(users) {
defaults.set(encoded, forKey: "Users")
}
}
}
init() {
// Decoding Data from UserDefault
if let users = defaults.data(forKey: "Users") {
if let decoded = try? JSONDecoder().decode([User].self, from: users) {
self.users = decoded
return
}
}
// Fallback value if key "Users" is not found
self.users = usersData
}
// resetting UserDefaults to initial values
func resetData() {
defaults.removeObject(forKey: "Users")
self.isLocked = false
self.users = usersData
}
}
注意:此代码未经测试。这里直接打字
回答您的问题:
1- 将布尔值注册到 UserDefault,
2- 在文本中显示布尔值!
我测试了以下代码并确认它可以在 ios 13.4 和使用催化剂的 macos 上运行。注意 String(...) 包装。
在 class AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// UserDefaults.standard.register(defaults: ["MyBool 1": true])
UserDefaults.standard.set(true, forKey: "MyBool 1")
return true
}
在内容视图中
import SwiftUI
struct ContentView: View {
@State var defaultValue = false // for testing
let defaults = UserDefaults.standard
var body: some View {
VStack {
Text("bull = \(String(UserDefaults.standard.bool(forKey: "MyBool 1")))")
Text(" A The BOOL 1 value is Bool 1 = \(String(defaultValue))")
Text(" B The BOOL 1 value is : Bool 1 = \(String(defaults.bool(forKey: "MyBool 1")))")
}
.onAppear(perform: loadData)
}
func loadData() {
defaultValue = defaults.bool(forKey: "MyBool 1")
print("----> defaultValue: \(defaultValue) ")
}
}