Передача модели ObservableObject через другой ObObject? - PullRequest
3 голосов
/ 11 января 2020

Я чувствую, что могу понять, почему то, что я делаю, не работает, но я все еще пытаюсь обернуть голову вокруг Combine и SwiftUI, поэтому любая помощь здесь будет приветствоваться.

Рассмотрим Пример:

Приложение с одним представлением, которое хранит некоторые строки в UserDefaults и использует эти строки для отображения некоторых текстовых меток. Есть три кнопки, одна для обновления заголовка и одна для обновления двух строк, сохраненных в UserDefaults, до случайной строки.

Представление является представлением тупого средства визуализации, а строка заголовка сохраняется непосредственно в ObservableObject. посмотреть модель. Модель представления имеет опубликованное свойство, которое содержит ссылку на класс UserSettings, который реализует оболочки свойств для хранения пользовательских строк в UserDefaults.

Наблюдения:

• Нажатие «Установить новый заголовок» корректно обновляет представление, чтобы отобразить новое значение

• При нажатии любой из кнопок «Задать значение пользователя» внутренне изменяется значение, однако представление не обновляется sh. Если после одной из этих кнопок нажать «Установить новый заголовок», новые значения отображаются, когда тело представления перестраивается для изменения заголовка.

Представление:

import SwiftUI

struct ContentView: View {
    @ObservedObject var model = ViewModel()

    var body: some View {
        VStack {
            Text(model.title).font(.largeTitle)
            Form {
                Section {
                    Text(model.settings.UserValue1)
                    Text(model.settings.UserValue2)
                }

                Section {
                    Button(action: {
                        self.model.title = "Updated Title"
                    }) { Text("Set A New Title") }
                    Button(action: {
                        self.model.settings.UserValue1 = "\(Int.random(in: 1...100))"
                    }) { Text("Set User Value 1 to Random Integer") }
                    Button(action: {
                        self.model.settings.UserValue2 = "\(Int.random(in: 1...100))"
                    }) { Text("Set User Value 2 to Random Integer") }
                }

                Section {
                    Button(action: {
                        self.model.settings.UserValue1 = "Initial Value One"
                        self.model.settings.UserValue2 = "Initial Value Two"
                        self.model.title = "Initial Title"
                    }) { Text("Reset All") }
                }

            }
        }
    }
}

ViewModel:

import Combine

class ViewModel: ObservableObject {

    @Published var title = "Initial Title"

    @Published var settings = UserSettings()

}

Модель UserSettings:

import Foundation
import Combine

@propertyWrapper struct DefaultsWritable<T> {
    let key: String
    let value: T

    init(key: String, initialValue: T) {
        self.key = key
        self.value = initialValue
    }

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



final class UserSettings: NSObject, ObservableObject {
    let objectWillChange = PassthroughSubject<Void, Never>()

    @DefaultsWritable(key: "UserValue", initialValue: "Initial Value One") var UserValue1: String {
        willSet {
            objectWillChange.send()
        }
    }
    @DefaultsWritable(key: "UserBeacon2", initialValue: "Initial Value Two") var UserValue2: String {
        willSet {
            objectWillChange.send()
        }
    }

}

Когда я ставлю точку останова на willSet { objectWillChange.send() } в UserSettings, я вижу, что сообщение objectWillChange отправляется издателю, когда я ожидаю, что так говорит мне, что проблема, вероятно, в том, что представление или модель представления неправильно подписываются на него. Я знаю, что если бы у меня были UserSettings в качестве @ObservedObject в представлении, это работало бы, но я чувствую, что это должно быть сделано в модели представления с Combine.

Чего мне здесь не хватает? Я уверен, что это действительно очевидно ...

1 Ответ

3 голосов
/ 11 января 2020

ObsesrvedObject прослушивает изменения в свойстве @Published, но не в более глубоких внутренних издателях, поэтому нижеприведенная идея состоит в том, чтобы присоединиться к внутреннему издателю, который PassthroughSubject, с @Published var settings, чтобы указать, что последний обновил .

Протестировано с Xcode 11.2 / iOS 13.2

Только необходимые изменения в ViewModel ...

class ViewModel: ObservableObject {

    @Published var title = "Initial Title"
    @Published var settings = UserSettings()

    private var cancellables = Set<AnyCancellable>()
    init() {
        self.settings.objectWillChange
            .sink { _ in
                self.objectWillChange.send()
            }
            .store(in: &cancellables)
    }
}
...