Going crazy with UserDefaults in Swift[UI]

安稳与你 提交于 2020-04-30 07:03:23

问题


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 :

  1. register a bool value to a UserDefault,
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!