How do I use UserDefaults with SwiftUI?

前端 未结 5 1841
无人共我
无人共我 2020-11-30 00:18
struct ContentView: View {
@State var settingsConfiguration: Settings
    struct Settings {
        var passwordLength: Dou         


        
相关标签:
5条回答
  • 2020-11-30 00:30

    Starting from Xcode 12.0 (iOS 14.0) you can use @AppStorage property wrapper for such types: Bool, Int, Double, String, URL and Data. Here is example of usage for storing String value:

    struct ContentView: View {
        
        static let userNameKey = "user_name"
        
        @AppStorage(Self.userNameKey) var userName: String = "Unnamed"
        
        var body: some View {
            VStack {
                Text(userName)
                
                Button("Change automatically ") {
                    userName = "Ivor"
                }
                
                Button("Change manually") {
                    UserDefaults.standard.setValue("John", forKey: Self.userNameKey)
                }
            }
        }
    }
    

    Here you are declaring userName property with default value which isn't going to the UserDefaults itself. When you first mutate it, application will write that value into the UserDefaults and automatically update the view with the new value.

    Also there is possibility to set custom UserDefaults provider if needed via store parameter like this:

    @AppStorage(Self.userNameKey, store: UserDefaults.shared) var userName: String = "Mike"
    

    and

    extension UserDefaults {
        static var shared: UserDefaults {
            let combined = UserDefaults.standard
            combined.addSuite(named: "group.myapp.app")
            return combined
        }
    }
    

    Notice: ff that value will change outside of the Application (let's say manually opening the plist file and changing value), View will not receive that update.

    P.S. Also there is new Extension on View which adds func defaultAppStorage(_ store: UserDefaults) -> some View which allows to change the storage used for the View. This can be helpful if there are a lot of @AppStorage properties and setting custom storage to each of them is cumbersome to do.

    0 讨论(0)
  • 2020-11-30 00:42

    First, create a property wrapper that will allow us to easily make the link between your Settings class and UserDefaults:

    import Foundation
    
    @propertyWrapper
    struct UserDefault<Value: Codable> {    
        let key: String
        let defaultValue: Value
    
        var value: Value {
            get {
                let data = UserDefaults.standard.data(forKey: key)
                let value = data.flatMap { try? JSONDecoder().decode(Value.self, from: $0) }
                return value ?? defaultValue
            }
            set {
                let data = try? JSONEncoder().encode(newValue)
                UserDefaults.standard.set(data, forKey: key)
            }
        }
    }
    

    Then, create a data store that holds your settings:

    import Combine
    import SwiftUI
    
    final class DataStore: BindableObject {
        let didChange = PassthroughSubject<DataStore, Never>()
    
        @UserDefault(key: "Settings", defaultValue: [])
        var settings: [Settings] {
            didSet {
                didChange.send(self)
            }
        }
    }
    

    Now, in your view, access your settings:

    import SwiftUI
    
    struct SettingsView : View {
        @EnvironmentObject var dataStore: DataStore
    
        var body: some View {
            Toggle(isOn: $settings.space) {
                Text("\(settings.space)")
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 00:43

    If you are persisting a one-off struct such that a property wrapper is overkill, you can encode it as JSON. When decoding, use an empty Data instance for the no-data case.

    final class UserData: ObservableObject {
        @Published var profile: Profile? = try? JSONDecoder().decode(Profile.self, from: UserDefaults.standard.data(forKey: "profile") ?? Data()) {
            didSet { UserDefaults.standard.set(try? JSONEncoder().encode(profile), forKey: "profile") }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 00:44

    The approach from caram is in general ok but there are so many problems with the code that SmushyTaco did not get it work. Below you will find an "Out of the Box" working solution.

    1. UserDefaults propertyWrapper

    import Foundation
    import Combine
    
    @propertyWrapper
    struct UserDefault<T> {
        let key: String
        let defaultValue: T
        
        init(_ key: String, defaultValue: T) {
            self.key = key
            self.defaultValue = defaultValue
        }
        
        var wrappedValue: T {
            get {
                return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
            }
            set {
                UserDefaults.standard.set(newValue, forKey: key)
            }
        }
    }
    

    2. UserSettings class

    final class UserSettings: ObservableObject {
    
        let objectWillChange = PassthroughSubject<Void, Never>()
    
        @UserDefault("ShowOnStart", defaultValue: true)
        var showOnStart: Bool {
            willSet {
                objectWillChange.send()
            }
        }
    }
    

    3. SwiftUI view

    struct ContentView: View {
    
    @ObservedObject var settings = UserSettings()
    
    var body: some View {
        VStack {
            Toggle(isOn: $settings.showOnStart) {
                Text("Show welcome text")
            }
            if settings.showOnStart{
                Text("Welcome")
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 00:54

    The code below adapts Mohammad Azam's excellent solution in this video:

    import SwiftUI
    
    struct ContentView: View {
        @ObservedObject var userDefaultsManager = UserDefaultsManager()
    
        var body: some View {
            VStack {
                Toggle(isOn: self.$userDefaultsManager.firstToggle) {
                    Text("First Toggle")
                }
    
                Toggle(isOn: self.$userDefaultsManager.secondToggle) {
                    Text("Second Toggle")
                }
            }
        }
    }
    
    class UserDefaultsManager: ObservableObject {
        @Published var firstToggle: Bool = UserDefaults.standard.bool(forKey: "firstToggle") {
            didSet { UserDefaults.standard.set(self.firstToggle, forKey: "firstToggle") }
        }
    
        @Published var secondToggle: Bool = UserDefaults.standard.bool(forKey: "secondToggle") {
            didSet { UserDefaults.standard.set(self.secondToggle, forKey: "secondToggle") }
        }
    }
    
    0 讨论(0)
提交回复
热议问题