私はビルドするための最良の方法を見つけようとしていますserDefaultsにバインドされた簡単な設定画面。
基本的に、私はトグルを持っていて、私は欲しい:
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")
}
このようなものを試してください。また、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)
}
}
}
オブジェクトバインディングを使用して、トグルは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)")
}
}
}
このビデオは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")
}
}
}
必要に応じて、これと呼ばれる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)
}
}
}
}
(またはあなたが望む他の方法で。)
これは、いくつかの実験の後に思いついたもので、通知で何かをしようとする代わりに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)
}
}
}
私が目にする1つの問題は、UserDefaults
から値を設定/取得するために間違ったAPIを使用していることです。あなたは使うべきです:
static var settingActivated: Bool {
get {
defaults?.bool(forKey: Keys.settingActivated) ?? false
}
set {
defaults?.set(newValue, forKey: Keys.settingActivated)
}
}