ios 在SwiftUI中使用切换绑定UserDefaults

a7qyws3x  于 2022-12-24  发布在  iOS
关注(0)|答案(8)|浏览(135)

我正在尝试找出构建一个绑定到UserDefaults的简单设置屏幕的最佳方法。
基本上,我有一个切换,我想:

  • 此切换更改时要保存的UserDefault值(UserDefault应为真实值的来源)
  • 切换到始终显示UserDefault的值

我已经观看了很多SwiftUI WWDC会议,但我仍然不确定我应该如何使用合并和SwiftUI中提供的不同工具来设置所有内容。我目前的想法是我应该使用BindableObject,这样我就可以使用hat来封装许多不同的设置。
我想我已经接近了,因为它几乎像预期的那样工作,但行为不一致。
当我在设备上构建和运行它时,我打开它并打开Toggle,然后如果我上下滚动视图一点,开关就会关闭(好像它实际上没有在UserDefaults中保存值)。
然而,如果我打开开关,离开应用程序,然后稍后回来它仍然开着,就像它记住了设置一样。
有什么建议吗?我希望这篇文章能帮助其他刚接触SwiftUI和合并的人,因为我在这个主题上找不到任何类似的问题。

import SwiftUI
import Combine

struct ContentView : View {

    @ObjectBinding var settingsStore = SettingsStore()

    var body: some View {
        NavigationView {
            Form {
                Toggle(isOn: $settingsStore.settingActivated) {
                    Text("Setting Activated")
                }
            }
        }.navigationBarTitle(Text("Settings"))
    }
}

class SettingsStore: BindableObject {

    var didChange = NotificationCenter.default.publisher(for: .settingsUpdated).receive(on: RunLoop.main)

    var settingActivated: Bool {
        get {
            UserDefaults.settingActivated
        }
        set {
            UserDefaults.settingActivated = newValue
        }
    }
}

extension UserDefaults {

    private static var defaults: UserDefaults? {
        return UserDefaults.standard
    }

    private struct Keys {
        static let settingActivated = "SettingActivated"
    }

    static var settingActivated: Bool {
        get {
            return defaults?.value(forKey: Keys.settingActivated) as? Bool ?? false
        }
        set {
            defaults?.setValue(newValue, forKey: Keys.settingActivated)
        }
    }
}

extension Notification.Name {
    public static let settingsUpdated = Notification.Name("SettingsUpdated")
}
iszxjhcz

iszxjhcz1#

更新

  • ------iOS 14及更高版本:-—————-*

从iOS 14开始,现在有了一种非常非常简单的方法来读取和写入UserDefaults。
使用名为@AppStorage的新属性 Package 器
以下是它的使用方法:

import SwiftUI

struct ContentView : View {

    @AppStorage("settingActivated") var settingActivated = false

    var body: some View {
        NavigationView {
            Form {
                Toggle(isOn: $settingActivated) {
                    Text("Setting Activated")
                }
            }.navigationBarTitle(Text("Settings"))
        }
    }
}

就是这样!它是如此简单和真正的直接向前。所有您的信息正在保存和读取的UserDefaults。

  • -------iOS 13:-———————-*

Swift 5.1中发生了很多变化。BindableObject已经完全弃用。此外,PassthroughSubject也发生了重大变化。
如果有人想让它工作,下面是同样的工作示例。我重用了"gohnjanotis"的代码,使它简单。

import SwiftUI
import Combine

struct ContentView : View {

    @ObservedObject var settingsStore: SettingsStore

    var body: some View {
        NavigationView {
            Form {
                Toggle(isOn: $settingsStore.settingActivated) {
                    Text("Setting Activated")
                }
            }.navigationBarTitle(Text("Settings"))
        }
    }
}

class SettingsStore: ObservableObject {

    let willChange = PassthroughSubject<Void, Never>()

    var settingActivated: Bool = UserDefaults.settingActivated {
        willSet {

            UserDefaults.settingActivated = newValue

            willChange.send()
        }
    }
}

extension UserDefaults {

    private struct Keys {
        static let settingActivated = "SettingActivated"
    }

    static var settingActivated: Bool {
        get {
            return UserDefaults.standard.bool(forKey: Keys.settingActivated)
        }
        set {
            UserDefaults.standard.set(newValue, forKey: Keys.settingActivated)
        }
    }
}
eeq64g8w

eeq64g8w2#

this video by azamsharpthis tutorial by Paul Hudson的帮助下,我已经能够生成一个绑定到UserDefaults的切换,并显示您立即分配给它的任何更改。

  • 场景代理:

在"window"变量下添加以下代码行

var settingsStore = SettingsStore()

并修改window. rootViewController以显示此内容

window.rootViewController = UIHostingController(rootView: contentView.environmentObject(settingsStore))
  • 设置存储:
import Foundation

class SettingsStore: ObservableObject {
    @Published var isOn: Bool = UserDefaults.standard.bool(forKey: "isOn") {
        didSet {
            UserDefaults.standard.set(self.isOn, forKey: "isOn")
        }
    }
}
  • 设置商店菜单

如果您愿意,请创建一个名为以下内容的SwiftUI视图并粘贴:

import SwiftUI

struct SettingsStoreMenu: View {
    
    @ObservedObject var settingsStore: SettingsStore
    
    var body: some View {
        Toggle(isOn: self.$settingsStore.isOn) {
            Text("")
        }
    }
}
  • 最后但并非最不重要

不要忘记从您拥有的任何主视图将SettingsStore注入SettingsStoreMenu,例如

import SwiftUI

struct MainView: View {
        
    @EnvironmentObject var settingsStore: SettingsStore

    @State var showingSettingsStoreMenu: Bool = false

    
    var body: some View {
        HStack {
            Button("Go to Settings Store Menu") {
                    self.showingSettingsStoreMenu.toggle()
            }
            .sheet(isPresented: self.$showingSettingsStoreMenu) {
                    SettingsStoreMenu(settingsStore: self.settingsStore)
            }
        }
    }
}

(Or你想用哪种方式都行。)

cs7cruho

cs7cruho3#

此接缝工作良好:

enum BackupLocalisations: String, CaseIterable, Hashable, Identifiable {
    case iPhone = "iPhone"
    case iCloud = "iCloud"
    
    var name: String {
        return self.rawValue
    }
    var id: BackupLocalisations {self}
}

enum Keys {
    static let iCloudIsOn = "iCloudIsOn"
    static let backupLocalisation = "backupLocalisation"
    static let backupsNumber = "backupsNumber"
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    var settings = Settings()

…/…
    let contentView = ContentView()
            .environmentObject(settings)
… }
class Settings: ObservableObject {
    @Published var iCloudIsOn: Bool = UserDefaults.standard.bool(forKey: Keys.iCloudIsOn) {
        didSet { UserDefaults.standard.set(self.iCloudIsOn, forKey: Keys.iCloudIsOn) }
    }
    
    @Published var backupLocalisation: String = UserDefaults.standard.object(forKey: Keys.backupLocalisation) as? String ?? "iPhone" {
        didSet { UserDefaults.standard.set(self.backupLocalisation, forKey: Keys.backupLocalisation) }
    }
    
    @Published var backupsNumber: Int = UserDefaults.standard.integer(forKey: Keys.backupsNumber) {
        didSet { UserDefaults.standard.set(self.backupsNumber, forKey: Keys.backupsNumber) }
    }
}
struct ContentView: View {
    @ObservedObject var settings: Settings

    var body: some View {
        NavigationView {
            Form {
                Section(footer: Text("iCloud is \(UserDefaults.standard.bool(forKey: Keys.iCloudIsOn) ? "on" : "off")")) {
                    Toggle(isOn: self.$settings.iCloudIsOn) { Text("Use iCloud") }
                }
                Section {
                    Picker(selection: $settings.backupLocalisation, label: Text("\(self.settings.backupsNumber) sauvegarde\(self.settings.backupsNumber > 1 ? "s" : "") sur").foregroundColor(Color(.label))) {
                        ForEach(BackupLocalisations.allCases) { b in
                            Text(b.name).tag(b.rawValue)
                        }
                    }
                    
                    Stepper(value: self.$settings.backupsNumber) {
                        Text("Nombre de sauvegardes")
                    }
                }
            }.navigationBarTitle(Text("Settings"))
            
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(Settings())
    }
}

扩展代码11.3.1

rbl8hiat

rbl8hiat4#

你也可以考虑用EnvironmentObject代替ObjectBinding来代替this answer

import Foundation

@propertyWrapper
struct UserDefault<Value: Codable> {
    let key: String
    let defaultValue: Value

    var value: Value {
        get {
            return UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

使用对象绑定,切换将使用键myBoolSetting将用户默认值设置为true/false。您可以看到当前值反映在Text视图的文本中。

import Combine
import SwiftUI

final class SettingsStore: BindableObject {
    let didChange = PassthroughSubject<Void, Never>()

    @UserDefault(key: "myBoolSetting", defaultValue: false)
    var myBoolSetting: Bool {
        didSet {
            didChange.send()
        }
    }
}

struct ContentView : View {
    @ObjectBinding var settingsStore = SettingsStore()

    var body: some View {
        Toggle(isOn: $settingsStore.myBoolSetting) {
            Text("\($settingsStore.myBoolSetting.value.description)")
        }
    }
}
plupiseo

plupiseo5#

我发现的一个问题是,您使用了错误的API来设置/获取UserDefaults的值用途:

static var settingActivated: Bool {
    get {
        defaults?.bool(forKey: Keys.settingActivated) ?? false
    }
    set {
        defaults?.set(newValue, forKey: Keys.settingActivated)
    }
}
6qqygrtg

6qqygrtg6#

这是我经过一些实验后得出的结果,使用PassthroughSubject而不是尝试对通知做一些事情,它似乎工作一致,正如预期的那样。
我猜可能有一些Swift或SwiftUI技术可以简化这一过程,所以请指出其他一些方法。

import SwiftUI
import Combine

struct ContentView : View {

    @ObjectBinding var settingsStore: SettingsStore

    var body: some View {
        NavigationView {
            Form {
                Toggle(isOn: $settingsStore.settingActivated) {
                    Text("Setting Activated")
                }
            }.navigationBarTitle(Text("Settings"))
        }
    }
}

class SettingsStore: BindableObject {

    let didChange = PassthroughSubject<Void, Never>()

    var settingActivated: Bool = UserDefaults.settingActivated {
        didSet {

            UserDefaults.settingActivated = settingActivated

            didChange.send()
        }
    }
}

extension UserDefaults {

    private struct Keys {
        static let settingActivated = "SettingActivated"
    }

    static var settingActivated: Bool {
        get {
            return UserDefaults.standard.bool(forKey: Keys.settingActivated)
        }
        set {
            UserDefaults.standard.set(newValue, forKey: Keys.settingActivated)
        }
    }
}
guz6ccqo

guz6ccqo7#

您可以扩展@Published属性 Package 器以在UserDefaults中存储值(如this answer中所建议的):

private var cancellables = [String: AnyCancellable]()

extension Published {
    init(defaultValue: Value, key: String) {
        let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        self.init(initialValue: value)
        cancellables[key] = projectedValue.sink { val in
            UserDefaults.standard.set(val, forKey: key)
        }
    }
}

下面是基于发布的问题的示例:

import SwiftUI
import Combine

struct ContentView : View {
    @ObservedObject var settingsStore = SettingsStore()

    var body: some View {
        NavigationView {
            Form {
                Toggle(isOn: $settingsStore.settingActivated) {
                    Text("Setting Activated")
                }
            }.navigationBarTitle(Text("Settings"))
        }
    }
}

class SettingsStore: ObservableObject {
    @Published(defaultValue: false, key: "SettingActivated")
    var settingActivated: Bool
}
kq4fsx7k

kq4fsx7k8#

以上选项对我都不起作用。在花了几个小时之后,我的工作诀窍是:

@AppStorage("YourKey") private var usedefaultVaribaleToSaveToggle = false
@State private var stateVariableToUpdateView = false
var body: some View {
VStack{
 HStack{}.opacity(stateVariableToUpdateView ? 1:0)//This View will update on toggle
 Toggle(isOn: $stateVariableToUpdateView){
  Text("Your Text")
 }
  .onChange(of: stateVariableToUpdateView) { value in
    downloadStatus = value
  }
}.onAppear(){
  stateVariableToUpdateView = usedefaultVaribaleToSaveToggle// To update state according to UserDefaults
}

相关问题