如何使用MVVM通过@ObservedObject触发自动SwiftUI更新

kyvafyod  于 2022-10-31  发布在  Swift
关注(0)|答案(3)|浏览(145)

我有一个关于SwiftUI和MVVM组合的问题。
在我们开始之前,我已经阅读了一些讨论SwiftUI和MVVM的结合是否必要的帖子。但是我不想在这里讨论这个,因为它已经在其他地方讨论过了。我只想知道它是否可能,如果可能,如何进行。:)
下面是代码。我尝试在更新的Object类之间添加ViewModel层,该类包含一个当按钮被按下时应该更新的数字。问题是,一旦我将ViewModel层放在它们之间,当按钮被按下时,UI不会自动更新。

检视

struct ContentView: View {

    @ObservedObject var viewModel = ViewModel()
    @ObservedObject var numberStorage = NumberStorage()

    var body: some View {
        VStack {
//            Text("\(viewModel.getNumberObject().number)")
//                .padding()
//            Button("IncreaseNumber") {
//                viewModel.increaseNumber()
//            }
            Text("\(numberStorage.getNumberObject().number)")
                .padding()
            Button("IncreaseNumber") {
                numberStorage.increaseNumber()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

视图模型

class ViewModel: ObservableObject {

    @Published var number: NumberStorage

    init() {
        self.number = NumberStorage()
    }

    func increaseNumber() {
        self.number.increaseNumber()
    }

    func getNumberObject() -> NumberObject {
        self.number.getNumberObject()
    }

}

型号

class NumberStorage:ObservableObject {
    @Published var numberObject: NumberObject

    init() {
        numberObject = NumberObject()
    }

    public func getNumberObject() -> NumberObject {
        return self.numberObject
    }

    public func increaseNumber() {
        self.numberObject.number+=1
    }
}

struct NumberObject: Identifiable {
    let id = UUID()
    var number = 0
} ```

Looking forward to your feedback!
axr492tv

axr492tv1#

我认为你的代码破坏了MVVM,因为你向视图公开了一个存储模型。在MVVM中,你的ViewModel应该只包含两个东西:
1.您的视图应显示的值。这些值应使用绑定系统(在您的情况下为合并)自动更新。
1.视图可能产生的事件(在你的例子中,一个按钮点击)记住这一点,你的ViewModel应该Package适配封装你的模型。我们不希望模型的改变影响视图。这是一个干净的方法:查看方式:

struct ContentView: View {

    @StateObject // When the view creates the object, it must be a state object, or else it'll be recreated every time the view is recreated
    private var viewModel = ViewModel()

    var body: some View {
        VStack {
            Text("\(viewModel.currentNumber)") // We don't want to use functions here, as that will create a new object , as SwiftUI needs the same reference in order to keep track of changes
                .padding()
            Button("IncreaseNumber") {
                viewModel.increaseNumber()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

视图模型:

class ViewModel: ObservableObject {

    @Published
    private(set) var currentNumber: Int = 0 // Private set indicates this should only be mutated by the viewmodel
    private let numberStorage = NumberStorage()

    init() {
        numberStorage.currentNumber
            .map { $0.number }
        .assign(to: &$currentNumber) // Here we're binding the current number on the storage to the published var that the view is listening to.`&$` basically assigns it to the publishers address
    }

    func increaseNumber() {
        self.numberStorage.increaseNumber()
    }
}

产品型号:

class NumberStorage {
    private let currentNumberSubject = CurrentValueSubject<NumberObject, Never>(NumberObject())

    var currentNumber: AnyPublisher<NumberObject, Never> {
        currentNumberSubject.eraseToAnyPublisher()
    }

   func increaseNumber() {
       let currentNumber = currentNumberSubject.value.number
       currentNumberSubject.send(.init(number: currentNumber + 1))
    }
}

struct NumberObject: Identifiable { // I'd not use this, just send and int directly
    let id = UUID()
    var number = 0
}
11dmarpk

11dmarpk2#

这是一个简单的问题。SwiftUI还不支持嵌套的可观察对象。我认为这里不需要ViewModel+Model,因为ViewModel似乎就足够了。
要实现这一点,您必须在触发模型的objectWillChange时手动触发viewModel的objectWillChange

class ViewModel: ObservableObject {
    init() {
        number.objectWillChange.sink { [weak self] (_) in
            self?.objectWillChange.send()
        }.store(in: &cancellables)
    }
}

加:
由于不是注入,而是在视图中初始化viewModel,所以最好使用StateObject而不是ObservedObject。请参阅Apple文档中的参考:Managing model data in your app

57hvy0tb

57hvy0tb3#

处理这个问题的一种方法是观察Storage类中的发布者,并在objectWillChange发布者发生变化时发送它。我在个人项目中通过添加一个类来实现这一点,我的所有视图模型都从这个类继承,它提供了一个很好的接口,并像这样处理合并的东西:

父视图模型

import Combine

class ViewModel: ObservableObject {
    private var cancellables: Set<AnyCancellable> = []

    func publish<T>(on publisher: Published<T>.Publisher) {
        publisher.sink { [weak self] _ in self?.objectWillChange.send() }
            .store(in: &cancellables)
    }
}

特定视图模型

class ContentViewModel: ViewModel {
    private let numberStorage = NumberStorage()

    var number: Int { numberStorage.numberObject.number }

    override init() {
        super.init()
        publish(on: numberStorage.$numberObject)
    }

    func increaseNumber() {
        numberStorage.increaseNumber()
    }
}

检视

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

    var body: some View {
        VStack {
            Text("\(viewModel.number)")
                .padding()
            Button("IncreaseNumber") {
                viewModel.increaseNumber()
            }
        }
    }
}

型号/存储

class NumberStorage:ObservableObject {
    @Published var numberObject: NumberObject

    init() {
        numberObject = NumberObject()
    }

    public func increaseNumber() {
        self.numberObject.number += 1
    }
}

struct NumberObject: Identifiable {
    let id = UUID()
    var number = 0
}

这会导致每次Storage.numberObject更改时视图都会重新呈现。

相关问题