Обновление переменной @Published на основе изменений наблюдаемой переменной - PullRequest
1 голос
/ 01 октября 2019

У меня есть AppState, который можно наблюдать:

class AppState: ObservableObject {

    private init() {}
    static let shared = AppState()

    @Published fileprivate(set) var isLoggedIn = false

}

Модель представления должна решить, какое представление отображать, основываясь на состоянии (isLoggedIn):

class HostViewModel: ObservableObject, Identifiable {

    enum DisplayableContent {
        case welcome
        case navigationWrapper
    }

    @Published var containedView: DisplayableContent = AppState.shared.isLoggedIn ? .navigationWrapper : .welcome

}

Вend a HostView наблюдает за свойством containedView и отображает правильное представление на его основе.

Моя проблема в том, что isLoggedIn не наблюдается с кодом выше, и я не могу понятьспособ сделать это. Я совершенно уверен, что есть простой способ, но после 4 часов проб и ошибок я надеюсь, что сообщество здесь может помочь мне.

Ответы [ 3 ]

2 голосов
/ 01 октября 2019

Когда вы добавляете ObservedObject в View, SwiftUI добавляет получателя для издателя objectWillChange, и вам нужно сделать то же самое. Поскольку objectWillChange отправляется до изменения isLoggedIn, возможно, было бы целесообразно добавить издателя, который отправляет его didSet. Поскольку вас интересует как исходное значение, так и изменения, CurrentValueSubject<Bool, Never>, вероятно, лучше. В вашем HostViewModel вам необходимо подписаться на нового издателя AppState и обновить containedView, используя опубликованное значение. Использование assign может вызвать циклы ссылок, поэтому лучше всего sink со слабой ссылкой на self.

Нет кода, но он очень прост. Последняя ловушка, на которую нужно обратить внимание, - это сохранить возвращенное значение с sink до AnyCancellable?, иначе ваш подписчик исчезнет.

1 голос
/ 03 октября 2019

Рабочее решение:

После двух недель работы с Combine я снова переработал свое предыдущее решение (см. Историю изменений), и это лучшее, что я мог придумать сейчас. Это все еще не совсем то, что я имел в виду, потому что contained не подписчик и издатель одновременно, но я думаю, что AnyCancellable всегда нужен. Если кто-нибудь знает способ достижения моего видения, пожалуйста, все же, дайте мне знать.

class HostViewModel: ObservableObject, Identifiable {

    @Published var contained: DisplayableContent
    private var containedUpdater: AnyCancellable?

    init() {
        self.contained = .welcome
        setupPipelines()
    }

    private func setupPipelines() {
        self.containedUpdater = AppState.shared.$isLoggedIn
            .map { $0 ? DisplayableContent.mainContent : .welcome }
            .assign(to: \.contained, on: self)
    }

}

extension HostViewModel {

    enum DisplayableContent {
        case welcome
        case mainContent
    }

}
1 голос
/ 02 октября 2019

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ :

Это не полное решение проблемы, оно не вызовет objectWillChange, поэтому бесполезно для ObservableObject. Но это может быть полезно для некоторых связанных проблем.

Основная идея заключается в создании propertyWrapper, который будет обновлять значение свойства при изменении в связанном Publisher:

@propertyWrapper
class Subscribed<Value, P: Publisher>: ObservableObject where P.Output == Value, P.Failure == Never {
    private var watcher: AnyCancellable?

    init(wrappedValue value: Value, _ publisher: P) {
        self.wrappedValue = value
        watcher = publisher.assign(to: \.wrappedValue, on: self)
    }

    @Published
    private(set) var wrappedValue: Value {
        willSet {
            objectWillChange.send()
        }
    }

    private(set) lazy var projectedValue = self.$wrappedValue
}

Использование:

class HostViewModel: ObservableObject, Identifiable {

    enum DisplayableContent {
        case welcome
        case navigationWrapper
    }

    @Subscribed(AppState.shared.$isLoggedIn.map({ $0 ? DisplayableContent.navigationWrapper : .welcome }))
    var contained: DisplayableContent = .welcome

    // each time `AppState.shared.isLoggedIn` changes, `contained` will change it's value
    // and there's no other way to change the value of `contained`
}
...