SwiftUI 视图看不到 属性 个标有 @Published 的 ObservableObject

SwiftUI View don't see property of ObservableObject marked with @Published

我正在使用 SwiftUI 和 VIPER 编写我的应用程序。为了保留 viper(可测试性、协议等)和 SwiftUI 反应性的想法,我想再添加 1 层 - ViewModel。我的演示者将从交互器询问数据并将其放入 ViewModel,然后视图将只读取此 value.I 检查将数据放入视图模型的方法是否有效 - 是的。但是我的视图只是看不到视图模型的 属性(显示空列表),即使它符合 ObservableObject 并且 属性 标记为已发布。更有趣的是,如果我将数据存储在演示者中,并用已发布和可观察的对象标记它,它将起作用。提前致谢!

class BeersListPresenter: BeersListPresenterProtocol, ObservableObject{
    var interactor: BeersListInteractorProtocol
    @ObservedObject var viewModel = BeersListViewModel()
    
    init(interactor: BeersListInteractorProtocol){
        self.interactor = interactor
        
    }
    func loadList(at page: Int){
        interactor.loadList(at: page) { beers in
            DispatchQueue.main.async {
                self.viewModel.beers.append(contentsOf: beers)
                print(self.viewModel.beers)
            }
        }
    }


class BeersListViewModel:ObservableObject{
    @Published var beers = [Beer]()
}


struct BeersListView: View{
    var presenter : BeersListPresenterProtocol
    @StateObject var viewModel : BeersListViewModel
    var body: some View {
        NavigationView{
            List{
                ForEach(viewModel.beers, id: \.id){ beer in
                    HStack{
                        VStack(alignment: .leading){
                            Text(beer.name)
                                .font(.headline)
                            Text("Vol: \(presenter.formattedABV(beer.abv))")
                                .font(.subheadline)
                            
                        }

一些注意事项。

您不能链接 ObservableObject,因此 class 中的 @ObservedObject var viewModel = BeersListViewModel() 将不起作用。

第二,你有 2 个 ViewModel,一个在 View 中,一个在 Presenter 中,你必须选择一个。一个人不会知道另一个人在做什么。

以下是如何让您的代码正常工作

import SwiftUI
struct Beer: Identifiable{
    var id: UUID = UUID()
    var name: String = "Hops"
    var abv: String = "H"
}
protocol BeersListInteractorProtocol{
    func loadList(at: Int, completion: ([Beer])->Void)
}

struct BeersListInteractor: BeersListInteractorProtocol{
    func loadList(at: Int, completion: ([Beer]) -> Void) {
        completion([Beer(), Beer(), Beer()])
    }
}
protocol BeersListPresenterProtocol: ObservableObject{
    var interactor: BeersListInteractorProtocol { get set }
    var viewModel : BeersListViewModel { get set }
    
    func formattedABV(_ abv: String) -> String
    func loadList(at page: Int)
}
class BeersListPresenter: BeersListPresenterProtocol, ObservableObject{
    var interactor: BeersListInteractorProtocol
    //You can't chain `ObservedObject`s
    @Published var viewModel = BeersListViewModel()
    
    init(interactor: BeersListInteractorProtocol){
        self.interactor = interactor
        
    }
    func loadList(at page: Int){
        interactor.loadList(at: page) { beers in
            DispatchQueue.main.async {
                self.viewModel.beers.append(contentsOf: beers)
                print(self.viewModel.beers)
            }
        }
    }
    func formattedABV(_ abv: String) -> String{
        "**\(abv)**"
    }
}

//Change to struct
struct BeersListViewModel{
    var beers = [Beer]()
}


struct BeerListView<T: BeersListPresenterProtocol>: View{
    //This is what will trigger view updates
    @StateObject var presenter : T
    //The viewModel is in the Presenter
    //@StateObject var viewModel : BeersListViewModel
    var body: some View {
        NavigationView{
            List{
                Button("load list", action: {
                    presenter.loadList(at: 1)
                })
                ForEach(presenter.viewModel.beers, id: \.id){ beer in
                    HStack{
                        VStack(alignment: .leading){
                            Text(beer.name)
                                .font(.headline)
                            Text("Vol: \(presenter.formattedABV(beer.abv))")
                                .font(.subheadline)
                            
                        }
                    }
                }
            }
        }
    }
}
struct BeerListView_Previews: PreviewProvider {
    static var previews: some View {
        BeerListView(presenter: BeersListPresenter(interactor: BeersListInteractor()))
    }
}

现在我无论如何都不是 VIPER 专家,但我认为你在混淆概念。在 VIPER 中混合 MVVM 和 VIPER.Because presenter 存在于 View/ViewModel 之下,不在同一水平。

我刚才找到了this教程。它适用于 UIKit,但如果我们使用 ObservableObject 替代 UIViewController 并且 SwiftUI View 作为故事板。

它使作为 ObservableObjectViewModel 和作为 SwiftUI structView 成为单个 View VIPER 层。

你会得到这样的代码

protocol BeersListPresenterProtocol{
    var interactor: BeersListInteractorProtocol { get set }
    
    func formattedABV(_ abv: String) -> String
    func loadList(at: Int, completion: ([Beer]) -> Void)
    
}
struct BeersListPresenter: BeersListPresenterProtocol{
    var interactor: BeersListInteractorProtocol
    
    init(interactor: BeersListInteractorProtocol){
        self.interactor = interactor
        
    }
    func loadList(at: Int, completion: ([Beer]) -> Void) {
        
        interactor.loadList(at: at) { beers in
            completion(beers)
        }
    }
    func formattedABV(_ abv: String) -> String{
        "**\(abv)**"
    }
}
protocol BeersListViewProtocol: ObservableObject{
    var presenter: BeersListPresenterProtocol { get set }
    var beers: [Beer] { get set }
    func loadList(at: Int)
    func formattedABV(_ abv: String) -> String
    
}
class BeersListViewModel: BeersListViewProtocol{
    @Published var presenter: BeersListPresenterProtocol
    @Published var beers: [Beer] = []
    
    init(presenter: BeersListPresenterProtocol){
        self.presenter = presenter
    }
    func loadList(at: Int) {
        DispatchQueue.main.async {
            self.presenter.loadList(at: at, completion: {beers in
                self.beers = beers
            })
        }
    }
    
    func formattedABV(_ abv: String) -> String {
        presenter.formattedABV(abv)
    }
}
struct BeerListView<T: BeersListViewProtocol>: View{
    @StateObject var viewModel : T
    
    var body: some View {
        NavigationView{
            List{
                Button("load list", action: {
                    viewModel.loadList(at: 1)
                })
                ForEach(viewModel.beers, id: \.id){ beer in
                    HStack{
                        VStack(alignment: .leading){
                            Text(beer.name)
                                .font(.headline)
                            Text("Vol: \(viewModel.formattedABV(beer.abv))")
                                .font(.subheadline)
                            
                        }
                    }
                }
            }
        }
    }
}
struct BeerListView_Previews: PreviewProvider {
    static var previews: some View {
        BeerListView(viewModel: BeersListViewModel(presenter: BeersListPresenter(interactor: BeersListInteractor())))
    }
}

如果你不想将 VIPER View Layer 分成 ViewModelSwiftUI View 你可以选择像下面的代码那样做更难替换 UI 并且通常不是一个好习惯。因为当 interactor.

有更新时,您将无法从 presenter 调用方法
struct BeerListView<T: BeersListPresenterProtocol>: View, BeersListViewProtocol{
    var presenter: BeersListPresenterProtocol
    @State var beers: [Beer] = []
    
    var body: some View {
        NavigationView{
            List{
                Button("load list", action: {
                    loadList(at: 1)
                })
                ForEach(beers, id: \.id){ beer in
                    HStack{
                        VStack(alignment: .leading){
                            Text(beer.name)
                                .font(.headline)
                            Text("Vol: \(formattedABV(beer.abv))")
                                .font(.subheadline)
                            
                        }
                    }
                }
            }
        }
    }
    func loadList(at: Int) {
        DispatchQueue.main.async {
            self.presenter.loadList(at: at, completion: {beers in
                self.beers = beers
            })
        }
    }
    
    func formattedABV(_ abv: String) -> String {
        presenter.formattedABV(abv)
    }
}
struct BeerListView_Previews: PreviewProvider {
    static var previews: some View {
        BeerListView<BeersListPresenter>(presenter: BeersListPresenter(interactor: BeersListInteractor()))
    }
}