web-dev-qa-db-ja.com

SwiftUIのToggleを使用したUserDefaultsバインディング

私はビルドするための最良の方法を見つけようとしていますserDefaultsにバインドされた簡単な設定画面

基本的に、私はトグルを持っていて、私は欲しい:

  • このトグルが変更されるたびに保存されるUserDefaultの値(UserDefaultが真のソースである必要があります)
  • 常にUserDefaultの値を表示するトグル

Settings screen with Toggle

SwiftUI WWDCセッションの多くを見てきましたが、CombineおよびSwiftUI内で使用可能なさまざまなツールを使用してすべてをどのように設定するべきかについては、まだよくわかりません。私の現在の考えでは、BindableObjectを使用する必要があるので、帽子を使用してさまざまな設定をカプセル化できます。

ほぼ期待どおりに動作するため、近いと思いますが、動作に一貫性がありません。

これをデバイスでビルドして実行するときは、デバイスを開いてToggleをオンにしてから、ビューを少し上下にスクロールすると、スイッチがオフに切り替わります(UserDefaultsに実際に値を保存していないかのように)。

ただし、スイッチをオンにしてアプリを終了し、後で戻った場合は、設定が記憶されているように、まだオンのままです。

助言がありますか?このトピックに関する同様の質問を見つけることができなかったため、SwiftUIとCombineを初めて使用する他のユーザーの役に立つことを期待して、これを投稿しています。

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")
}
5
gohnjanotis

このようなものを試してください。また、EnvironmentObjectの代わりにObjectBindingを使用することを検討することもできます この回答

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)
        }
    }
}

オブジェクトバインディングを使用して、トグルはmyBoolSettingtrue/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)")
        }
    }
}
1
JWK

このビデオはazamsharpによるこのチュートリアルはPaul Hudsonによる の両方の助けを借りて、UserDefaultsにバインドし、割り当てた変更を表示するトグルを作成できました瞬時にそれに。

  • シーンデリゲート:

このコード行を「ウィンドウ」定数の下に追加します

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")
        }
    }
}
  • SettingsStoreMenu

必要に応じて、これと呼ばれるSwiftUIビューを作成して貼り付けます。

import SwiftUI

struct SettingsStoreMenu: View {

    @ObservedObject var settingsStore: SettingsStore

    var body: some View {
        Toggle(isOn: self.$settingsStore.isOn) {
            Text("")
        }
    }
}
  • 最後だが大事なことは

次のようなメインビューからSettingsStoreMenuにSettingsStoreを注入することを忘れないでください。

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)
            }
        }
    }
}

(またはあなたが望む他の方法で。)

0
esedege

これは、いくつかの実験の後に思いついたもので、通知で何かをしようとする代わりに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)
        }
    }
}
0
gohnjanotis

私が目にする1つの問題は、UserDefaultsから値を設定/取得するために間違ったAPIを使用していることです。あなたは使うべきです:

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