问题
Launching myself into Swift and SwiftUI, I find the process of migrating from UIKit quite hard. Presently stomped by UserDefaults, even after trying to make sense of the many tutorials I found on the web.
Please tell me what I'm doing wrong here : VERY simple code to :
- register a bool value to a UserDefault,
- display that bool in a text !
Doesn't get any simpler than that. But I can't get it to work, as the call to UserDefaults throws this error message :
Instance method 'appendInterpolation' requires that 'Bool' conform to '_FormatSpecifiable'
My "app" is the default single view app with the 2 following changes :
1- In AppDelegate, I register my bool :
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- in ContentView, I try to display it (inside struct ContentView: View) :
let defaults = UserDefaults.standard
var body: some View {
Text("The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1"))")
}
Any ideas ?
Thanks
回答1:
Your issue is that the Text(...)
initializer takes a LocalizedStringKey
rather than a String
which supports different types in its string interpolation than plain strings do (which does not include Bool
apparently).
There's a couple ways you can work around this.
You could use the Text
initializer that takes a String
and just displays it verbatim without attempting to do any localization:
var body: some View {
Text(verbatim: "The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1"))")
}
Alternatively, you could extend LocalizedStringKey.StringInterpolation
to support bools and then your original code should work:
extension LocalizedStringKey.StringInterpolation {
mutating func appendInterpolation(_ value: Bool) {
appendInterpolation(String(value))
}
}
回答2:
To solve your problem, just add description variable, like:
var body: some View {
Text("The BOOL 1 value is : Bool 1 = \(defaults.bool(forKey: "MyBool 1").description)")
}
回答3:
To answer your questions:
1- register a bool value to a UserDefault,
2- display that bool in a text !
I tested the following code and confirm that it works on ios 13.4 and macos using catalyst. Note the String(...) wrapping.
in 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
}
in ContentView
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) ")
}
}
回答4:
Not sure why you use register
, but you can just set the bool value like this:
UserDefaults.standard.set(true, forKey: "MyBool1")
回答5:
in SwiftUI I use these:
UserDefaults.standard.set(true, forKey: "MyBool 1")
let bull = UserDefaults.standard.bool(forKey: "MyBool 1")
回答6:
I figured that the best way to use UserDefaults is inside a class. It helps us subscribe to that class from any model using @ObservedObject
property wrapper.
Boolean method can be used for rest of the 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
}
}
Note: This code is not tested. It is directly typed here.
回答7:
Try this:
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)
}
}
来源:https://stackoverflow.com/questions/61270109/going-crazy-with-userdefaults-in-swiftui