【问题标题】:SwiftUI MVVM approach with vars in ViewSwiftUI MVVM 方法与视图中的变量
【发布时间】:2021-09-28 07:13:34
【问题描述】:

我正在 SwiftUI 中构建一个基于 MVVM 设计模式的应用程序。我正在做的是这样的:

struct AddInvestment: View {
    
    @ObservedObject private var model = AddInvestmentVM()
    @State private var action: AssetAction?
    @State private var description: String = ""
    @State private var amount: String = ""
    @State private var costPerShare: String = ""
    @State private var searchText: String = ""
    @State var asset: Asset?
    
    var body: some View {
        NavigationView {
            VStack {
                Form {
                    Section("Asset") {
                        NavigationLink(model.chosenAsset?.completeName ?? "Scegli asset") {
                            AssetsSearchView()
                        }
                    }
                    Section {
                        Picker(action?.name ?? "", selection: $action) {
                            ForEach(model.assetsActions) { action in
                                Text(action.name).tag(action as? AssetAction)
                            }
                        }
                        .pickerStyle(.segmented)
                        .listRowBackground(Color.clear)
                    }
                    Section {
                        TextField("", text: $amount, prompt: Text("Unità"))
                            .keyboardType(UIKit.UIKeyboardType.decimalPad)
                        TextField("", text: $costPerShare, prompt: Text("Prezzo per unità"))
                            .keyboardType(UIKit.UIKeyboardType.decimalPad)
                    }
                }
            }
            .navigationTitle("Aggiungi Investimento")
        }
        .environmentObject(model)
        .onAppear {
            model.fetchBaseData()
        }
    }
}

然后我有我的 ViewModel,这个:

class AddInvestmentVM: ObservableObject {
    
    private let airtableApiKey = "..."
    private let airtableBaseID = "..."
    
    private let assetsTableName = "..."
    private let assetsActionsTableName = "..."
    
    private let airtable = Airtable.init("...")
    
    private var tasks = [AnyCancellable]()
    
    @Published var chosenAsset: Asset?
    @Published var assets = [Asset]()
    @Published var assetsActions = [AssetAction]()
    
    init() { }
    
    func fetchBaseData() {
        
        print("Fetching data...")
        
        let assetActionsRequest = AirtableRequest.init(baseID: airtableBaseID, tableName: assetsActionsTableName, view: nil)
        let assetsActionsPublisher: AnyPublisher<[AssetAction], AirtableError> = airtable.fetchRecords(request: assetActionsRequest)
        
        assetsActionsPublisher
            .eraseToAnyPublisher()
            .sink { completion in
                print("** **")
                print(completion)
            } receiveValue: { assetsActions in
                print("** **")
                print(assetsActions)
                self.assetsActions = assetsActions
            }
            .store(in: &tasks)
    }
}

现在,如您所见,我在视图上有一些属性绑定到一些文本字段。让我们考虑这些:

@State private var description: String = ""
@State private var amount: String = ""
@State private var costPerShare: String = ""
@State private var searchText: String = ""

记住 MVVM 模式,这些属性是否应该在 ViewModel 中声明并从那里绑定?或者这是一个正确的方法?

【问题讨论】:

    标签: ios swift design-patterns mvvm swiftui


    【解决方案1】:

    我倾向于将视图视为一个设备或一组设备(iPhone、iPad、Mac 等)的应用程序人机界面的定义。我倾向于将模型视为应用程序逻辑所在的地方,它不属于任何视图(业务逻辑、数据操作、网络操作等)。我认为 ViewModel 是 View 需要使 View 工作的任何逻辑退出的地方,例如格式化数据以在 View 上显示。我认为您正在寻找视图所需的数据元素的单一事实来源。在我看来,当且仅当它特定于 View 时,它才应该存在于 ViewModel 中。任何关于应用程序的逻辑或数据都应该存在于模型中,而不是视图模型中。通过这样做,我使我的模型更可重用,更独立于视图。总是有边缘情况,我想如果我构建一个完全独立的第二个视图,是否需要重新创建这些数据或逻辑,比如通过 HTML/JavaScript/等与浏览器一起工作的视图。如果我必须使用新的视图层重新创建该数据或逻辑,那么我认为数据或逻辑属于模型。如果数据或逻辑特定于该 View 层,那么我会将其放入该 View 的 ViewModel 中。

    使用上面的想法,您的 AddInvestment 类看起来就像我将放在模型中的东西,而不是 ViewModel。当然,这完全是口味问题,我认为这里没有任何一个正确的答案,但我认为我会在自己的应用程序中对此进行切片和切块。

    【讨论】:

      【解决方案2】:

      我不想给你一个具体的答案,你可以将哪个具体变量移到视图模型,我想给你一个更一般的答案,这也可能有助于你决定其他用例,最终应该有助于自己回答你的问题;)

      视图模型发布 绑定(我不是在谈论@Binding here!),完全并且明确 描述 视图应呈现的内容。 如何它实际上看起来可能仍然是视图的一部分。

      提示:定义一个结构,其中包含构成绑定的所有变量,然后将该结构发布到您的视图模型中。您可以将此绑定命名为ViewState

      如果我们采取严格的规定,换句话说,对于绑定的每个可能值,视图的可视化表示形式只有一个。

      但是,在实践中,最好放宽一点,否则您甚至必须指定和处理滚动视图的滚动位置。你能走多远可能取决于你的观点和整个场景的复杂性。但是规则,binding 是视图的最终真实来源,并且决定了它正在呈现的内容不应被违反。

      通常,在您的模型不可变的情况下(即没有改变模型的用户操作),您很少会在 SwiftUI 视图中使用 @State 变量,因为没有什么是 变量

      即使列表中的元素发生变化或元素的顺序或数量发生变化,列表视图总是会收到一个常量元素数组,其元素也不是可变的用户。因此,您在 SwiftUI 视图中使用 let elements: [Element]

      另一方面,您的视图模型可能会使用一个模型,该模型发布值[Element] 并且视图模型观察它,然后相应地改变绑定。

      MVVM 的另一个原则是(实际上是任何 MV* 模式)您的视图不会自行执行逻辑并且它永远不会更改绑定。 相反,您的视图通过回调(也称为操作、事件、意图)向用户的“意图”发出信号,最终将到达视图模型并在那里进行处理,这反过来可能最终影响绑定。

      严格来说,这意味着,如果您有一个用户可以编辑字符串的场景,并且如果您对该字符串使用绑定,则字符串不能通过编辑用户来改变。相反,每个编辑命令都将发送到视图模型,然后视图模型处理更改,并发送回变异的绑定,最终将反映用户的更改。

      此外,在实践中可能有更有利的解决方案:视图可能使用其自己的字符串“编辑缓冲区”,使用@State 变量。当用户编辑文本时,该字符串在视图内“内部”发生变异。只有当用户“提交”更改或用户点击“返回”或“完成”按钮时,模型才会收到通知。当“编辑”本身很简单并且在编辑过程中不需要复杂的验证,或者不需要触发副作用(如更新搜索栏中的建议列表)时,这种方法是有利的。否则,您将通过视图模型进行编辑。

      再次在实践中,您决定使用具有“它自己的行为”的视图走多远。这取决于用例以及在避免不必要的复杂解决方案方面有什么好处,这些解决方案不会因为对模式过于严格而使解决方案变得更好。

      结论:

      在 MVVM 中,仅当您的视图添加的行为并不严格需要由视图模型监控并且不违反绑定是来自视图的视角,并且视图模型仍然是执行逻辑的权威。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-22
        • 2021-08-06
        • 2021-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多