ObservedObject внутри ObservableObject не обновляет View

Я пытаюсь отобразить индикатор активности при выполнении асинхронного запроса. Я создал объект ActivityTracker, который будет отслеживать жизненный цикл издателя. Этот ActivityTracker является ObservableObject и будет храниться в модели представления, которая также является ObservableObject.

Кажется, что такая настройка не обновляет представление. Вот мой код:

struct ContentView: View {
    @ObservedObject var viewModel = ContentViewModel()

    var body: some View {
        VStack(spacing: 16) {
            Text("Counter: \(viewModel.tracker.count)\nPerforming: \(viewModel.tracker.isPerformingActivity ? "true" : "false")")

            Button(action: {
                _ = request().trackActivity(self.viewModel.tracker).sink { }
            }) {
                Text("Request")
            }
        }
    }
}

class ContentViewModel: ObservableObject {
    @Published var tracker = Publishers.ActivityTracker()
}

private func request() -> AnyPublisher<Void, Never> {
    return Just(()).delay(for: 2.0, scheduler: RunLoop.main)
        .eraseToAnyPublisher()
}

extension Publishers {
    final class ActivityTracker: ObservableObject {
        // MARK: Properties

        @Published var count: Int = 0

        var isPerformingActivity: Bool {
            return count > 0
        }

        private var cancellables: [AnyCancellable] = []
        private let counterSubject = CurrentValueSubject<Int, Never>(0)
        private let lock: NSRecursiveLock = .init()

        init() {
            counterSubject.removeDuplicates()
                .receive(on: RunLoop.main)
                .print()
                .sink { [weak self] counter in
                    self?.count = counter
                }
                .store(in: &cancellables)
        }

        // MARK: Private methods

        fileprivate func trackActivity<Value, Error: Swift.Error>(
            ofPublisher publisher: AnyPublisher<Value, Error>
        ) {
            publisher
                .receive(on: RunLoop.main)
                .handleEvents(
                    receiveSubscription: { _ in self.increment() },
                    receiveOutput: nil,
                    receiveCompletion: { _ in self.decrement() },
                    receiveCancel: { self.decrement() },
                    receiveRequest: nil
                )
                .print()
                .sink(receiveCompletion: { _ in }, receiveValue: { _ in })
                .store(in: &cancellables)
        }

        private func increment() {
            lock.lock()
            defer { lock.unlock() }
            counterSubject.value += 1
        }

        private func decrement() {
            lock.lock()
            defer { lock.unlock() }
            counterSubject.value -= 1
        }
    }
}

extension AnyPublisher {
    func trackActivity(_ activityTracker: Publishers.ActivityTracker) -> AnyPublisher {
        activityTracker.trackActivity(ofPublisher: self)
        return self
    }
}

Я также пытался объявить свой ActivityTracker как @Published, но результат тот же, мой текст не обновляется. Обратите внимание, что сохранение трекера активности непосредственно в представлении будет работать, но это не то, что я ищу.

Я что-то здесь пропустил?


person Yaman    schedule 22.11.2019    source источник


Ответы (2)


Вложенные объекты ObservableObjects пока не поддерживаются. Если вы хотите использовать эти вложенные объекты, вам необходимо самостоятельно уведомить объекты об изменении данных. Надеюсь, следующий код поможет вам решить вашу проблему.

Прежде всего используйте: import Combine

Затем объявите свою модель и подмодели, все они должны использовать свойство @ObservableObject для работы. (Не забывайте свойство @Published aswel)

Я создал родительскую модель с именем Модель и две подмодели Подмодель1 и Подмодель2. Когда вы используете родительскую модель при изменении данных e.x: model.submodel1.count, вам необходимо использовать средство уведомления, чтобы позволить самому представлению обновляться.

AnyCancellables уведомляет саму родительскую модель, в этом случае представление будет обновлено автоматически.

Скопируйте код и используйте его самостоятельно, а затем попробуйте переделать свой код, используя это. Надеюсь это поможет. Удачи!

class Submodel1: ObservableObject {
  @Published var count = 0
}

class Submodel2: ObservableObject {
  @Published var count = 0
}

class Model: ObservableObject {
  @Published var submodel1 = Submodel1()
  @Published var submodel2 = Submodel2()
    
    var anyCancellable: AnyCancellable? = nil
    var anyCancellable2: AnyCancellable? = nil

    init() {
        
        anyCancellable = submodel1.objectWillChange.sink { [weak self] (_) in
            self?.objectWillChange.send()
        }
        
        anyCancellable2 = submodel2.objectWillChange.sink { [weak self] (_) in
            self?.objectWillChange.send()
        }
    }
}

Если вы хотите использовать эту Модель, просто используйте ее как обычное использование ObservedObjects.

struct Example: View {

@ObservedObject var obj: Model

var body: some View {
    Button(action: {
        self.obj.submodel1.count = 123
        // If you've build a complex layout and it still won't work, you can always notify the modal by the following line of code:
        // self.obj.objectWillChange.send()
    }) {
        Text("Change me")
    }
}
person Tobias Hesselink    schedule 22.11.2019
comment
Вау, это отстой, это обязательно нужно поддерживать: / Вы знаете, планировала ли Apple его поддерживать? - person Yaman; 22.11.2019
comment
Да уж, xD. У меня нет информации об обновлениях для SwiftUI, я надеюсь, что они скоро исправят это для облегчения использования. - person Tobias Hesselink; 22.11.2019
comment
@TobiasHesselink, спасибо! Мне это помогло. Одна вещь, которую я просто заметил, что без слабого я модель не будет деинициализирована anyCancellable = submodel1.objectWillChange.sink { [weak self] (_) in self?.objectWillChange.send() } - person user12208004; 17.08.2020
comment
@ user12208004 может быть из-за последних обновлений xcode? - person Tobias Hesselink; 17.08.2020

Если у вас есть коллекция вещей, вы можете сделать это:

import Foundation
import Combine

class Submodel1: ObservableObject {
    @Published var count = 0
}

class Submodel2: ObservableObject {
    var anyCancellable: [AnyCancellable] = []
    @Published var submodels: [Submodel1] = []

    init() {
        submodels.forEach({ submodel in
            anyCancellable.append(submodel.objectWillChange.sink{ [weak self] (_) in
                self?.objectWillChange.send()
            })
        })
    }
}
person visc    schedule 28.09.2020