Связывание UserDefaults с переключателем в SwiftUI - PullRequest
1 голос
/ 30 июня 2019

Я пытаюсь найти лучший способ построить простой экран настроек, привязанный к UserDefaults .

По сути, у меня есть Toggle, и я хочу:

  • значение 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")
}

Ответы [ 3 ]

1 голос
/ 01 июля 2019

Попробуйте что-нибудь подобное. Вы также можете рассмотреть возможность использования 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)")
        }
    }
}
0 голосов
/ 07 июля 2019

Вот что я придумал после некоторых экспериментов, используя 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 голосов
/ 01 июля 2019

Одна проблема, которую я вижу, заключается в том, что вы используете неправильные API для установки / получения значения из UserDefaults. Вы должны использовать:

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