ObservableObject内部的ObservedObject不刷新视图

时间:2019-11-22 14:34:31

标签: ios swift swiftui combine

我正在尝试执行异步请求时显示活动指示器。 我所做的是创建一个ActivityTracker对象,该对象将跟踪发布者的生命周期。 此ActivityTracker是一个ObservableObject,将存储在也是ObservableObject的视图模型中。

似乎这种设置并没有刷新View。这是我的代码:

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,但结果相同,我的文本未更新。 请注意,将活动跟踪器直接存储在视图中将起作用,但这不是我想要的。

我在这里错过了什么吗?

2 个答案:

答案 0 :(得分:2)

尚不支持嵌套的ObservableObjects。 当您要使用这些嵌套对象时,需要在数据更改时自行通知这些对象。 希望以下代码可以帮助您解决问题。

首先使用:import Combine

然后声明您的模型和子模型,它们都需要使用@ObservableObject属性来工作。 (不要忘记@Published属性aswel)

我制作了一个名为 Model 的父模型和两个子模型 Submodel1&Submodel2 。当您在更改数据e.x时使用父模型时:model.submodel1.count,您需要使用一个通知程序,以便让View自行更新。

AnyCancellables 会通知父模型本身,在这种情况下,视图将自动更新。

复制代码并自己使用,然后在使用时尝试重新制作代码。希望这会有所帮助,祝你好运!

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

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

class Model: ObservableObject {
  @Published var submodel1: Submodel1 = Submodel1()
  @Published var submodel2: Submodel2 = Submodel2()

    var anyCancellable: AnyCancellable? = nil
    var anyCancellable2: AnyCancellable? = nil

    init() {

        anyCancellable = submodel1.objectWillChange.sink { (_) in
            self.objectWillChange.send()
        }

        anyCancellable2 = submodel2.objectWillChange.sink { (_) 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")
    }
}

答案 1 :(得分:1)

如果您有很多东西,可以这样做:

.as-console-wrapper { max-height: 100% !important; top: 0; }