【问题标题】:Using a design pattern like MVC with SwiftUI将 MVC 之类的设计模式与 SwiftUI 结合使用
【发布时间】:2020-10-18 17:00:58
【问题描述】:

我正在尝试实现类似 MVC 的设计模式,以实现代码不同部分之间的低耦合。网上很少有关于 IOS 或 swift UI 开发和 MVC 模式的资料我个人认为没有帮助。

我想了解的是控制器类应该如何在 Swift UI 中控制或渲染 UI?

例如遵循 MVC 模式 - 视图不应该知道模型的外观,因此将对象从数据库发送回视图以直观地呈现它不是一个好主意。

假设我们有下面的View和Controller,当从DB发回数据时,我应该如何处理控制器和视图之间的交互,以便在视图中直观地呈现它?

查看:

import SwiftUI
import Foundation

struct SwiftUIView: View {

    
    var assignmentController = AssignmentController()
    

    @State var assignmentName : String = ""
    @State var notes : String = ""
  

  
    var body: some View {
        
        NavigationView {
            VStack {
                Form {
                   
                        
                        TextField("Assignment Name", text: $assignmentName)

    
                        TextField("Notes", text: $notes)
                
     
            }
                
                Button(action: {
                                                               
                    self.assignmentController.retrieveFirstAssignment()
                                       }) {
                                                                                                                                                Text("Get The First Assignment !")
                                                                                    }

            }
            .navigationBarTitle("First Assignment")
        }
        
    
}
}

控制器

var assignmentModel = AssignmentModel()

func retrieveFirstAssignment()
{
    var firstAssignment : Assignment

    firstAssignment=assignmentModel.retrieveFirstAssignment()
    
}

目前,它对找到的对象什么都不做。

型号

模型中的一个对象由两个 String 字段组成:“assignmentName”和“notes”。

*我们假设分配模型有一个工作函数,它从数据库中检索一个任务,以便在视图中呈现它。

【问题讨论】:

  • 谢谢。这正是我的问题,您应该如何与视图进行交互,因为视图不应该知道模型的外观。这就是为什么我暂时不使用它的原因。
  • 当然可以使用几乎任何你想要的设计模式,但你考虑过 MVVM 吗?它似乎比 MVC 和 Apple 在他们的教程系列中自己采用它更适合 SwiftUI 做事。
  • 我也对 MVVM 持开放态度,但在这种情况下,我的问题仍然是如何在 View Model 和 View 之间建立适当的绑定连接。
  • ObservableObject/ObservedObject.
  • 如果有人可以使用我使用的演示类(用视图模型替换控制器)提供基本代码示例,将不胜感激

标签: ios swift model-view-controller design-patterns swiftui


【解决方案1】:
struct SwiftUIView: View {

    @State m = AssignmentModel()
   
    var body: some View {
        // use m   
    }
    func loadAssignmentFromDB() {
        m.retrieveFirstAssignment()
    }
}

这就是我所说的“带有内置 MVVM 的增强型 MVC”。

它可以满足您对 MVC 和可能的 MVVM 的追求,同时省力。

现在我将讨论原因:

  1. function,或者更具体地说,mutation是Control。您不需要一个名为“Controller”的对象来在 MVC 中使用 C。否则我们还不如再坚持 UIKit 10 年。

当您的模型是值类型时,这很有意义。没有你的具体说法,没有什么可以改变它,例如; @State 注释。由于改变模型的唯一方法是通过这些指定的端点,因此您的 Control 仅在改变这些端点的函数中生效。

  1. 引用另一个回复:

确实,SwiftUI 与 MVVM 比与 MVC 更接近。然而,Apple 文档中的几乎所有示例代码都非常简单,以至于完全忽略了 ViewModel(和/或 MVC 中的控制器)。一旦你开始创建更大的项目,就需要一些东西来连接你的视图和模型。但是,IMO,SwiftUI 文档并没有(还)以令人满意的方式完全解决这个问题。

SwiftUI,如果有的话,增强了 MVC。拥有 ViewModel 的目的是什么?

a.) 拥有模型视图绑定,它出现在我上面的代码 sn-p 中。

b.) 管理与对象关联的状态。如果@State 没有给您留下由您管理状态的印象,那么我不知道会发生什么。有趣的是,有多少 MVVM 开发人员对此视而不见。就像你不需要 View Controller 来控制一样,你不需要 ViewModel 来做 VM。设计模式是一种@State 的思想。与特定的命名和严格的结构无关。

c.) 说我对 MVVM 持开放态度,但没有根据。您认为哪个代码 sn-p 更有机会扩展到更大的项目?我的紧凑型还是另一条回复中建议的? 提示:想想有多少额外的文件、视图模型、observableobjects、粘合代码、pass-around-vm-as-parameters、init 函数接受 vm 作为参数,你将拥有。这些只是供您在另一个对象中编写一些代码。它没有提到减少或简化手头的任务。地狱它甚至告诉你如何重构你的控制代码,所以你很可能只是重复你在 MVC 中做错的任何事情。我有没有提到 ViewModel 是一个具有隐式状态管理的共享引用类型对象?那么当你打算用一个引用类型模型覆盖它时,拥有一个 value 类型模型有什么意义呢?

MVVM 开发人员说 SwiftUI 的基本形式无法扩展到更大的项目,这很有趣。保持简单是扩展的唯一方法。

这是我观察到的 2020 年开发进展路线图。 第1天:初学者 Day2:google了一些,放下MVC Day3:google 更多,SwiftUI 不可扩展 Day4:好的,我需要MVVM+RxSwift+Coordinator+Router+DependencyInjection来避免SDK的缺点。

由于这似乎是一个常见的初学者问题,我的建议是在跑步之前先学会走路。

我亲眼看到 RxSwift 开发人员将控制器代码移动到视图中,使控制器看起来“干净”,并且需要 3 个第三方库(一个是自定义 fork)来发送 http GET。

如果你不能让简单的事情变得简单,那么设计模式就毫无意义。

【讨论】:

    【解决方案2】:

    对我来说,这是一个很好的问题。的确,SwiftUI 与 MVVM 比与 MVC 更接近。然而,Apple 文档中的几乎所有示例代码都非常简单,以至于完全忽略了 ViewModel(和/或 MVC 中的控制器)。一旦你开始创建更大的项目,就需要一些东西来连接你的视图和模型。然而,IMO,SwiftUI 文档并没有(还)以令人满意的方式完全解决这个问题。我希望其他开发人员纠正我或对此进行扩展(我仍在学习),但这是我迄今为止发现的:

    • 为了在非示例项目中管理更新视图,您几乎总是希望使用 ObservableObject/ObservedObject。
    • 视图应该只在对象发生变化时需要更新时才观察它。最好将更新委托给子视图。
    • 创建一个大的 ObservableObject 并为其所有属性添加@Published 可能很诱人。但是,这意味着即使视图甚至不依赖的属性发生变化,观察该对象的视图也会更新(有时是可见的)。
    • 绑定是表示可以修改数据的控件的视图最自然的接口。请注意,绑定不会触发更新视图。更新视图应该由@State 或@ObservedObject 管理(这可以由控件的父视图完成)。
    • 常量是视图的自然界面,只显示数据(而不是修改数据)。

    以下是我将其应用于您的示例的方法:

    import SwiftUI
    
    //
    // Helper class for observing value types
    //
    class ObservableValue<Value: Hashable>: ObservableObject {
        @Published var value: Value
        
        init(initialValue: Value) {
            value = initialValue
        }
    }
    
    //
    // Model
    //
    struct Assignment {
        let name : String
        let notes: String
    }
    
    //
    // ViewModel?
    //
    // Usually a view model transforms data so it is usable by the view. Strings are already
    // usable in our components. The only change here is to wrap the strings in an
    // ObservableValue so views can listen for changes to the individual properties.
    //
    // Note: In Swift you often see transformations of the data implemented as extensions to
    // the model rather than in a separate ViewModel.
    
    class AssignmentModelView {
        var name : ObservableValue<String>
        var notes: ObservableValue<String>
        
        init(assignment: Assignment) {
            name  = ObservableValue<String>(initialValue: assignment.name)
            notes = ObservableValue<String>(initialValue: assignment.notes)
        }
        
        var assignment: Assignment {
            Assignment(name: name.value, notes: notes.value)
        }
    }
    
    //
    // Controller
    //
    // Publish the first assignment so Views depending on it can update whenever we change
    // the first assignment (**not** update its properties)
    class AssignmentController: ObservableObject {
        @Published var firstAssignment: AssignmentModelView?
    
        func retrieveFirstAssignment() {
            let assignment = Assignment(name: "My First Assignment", notes: "Everyone has to start somewhere...")
            
            firstAssignment = AssignmentModelView(assignment: assignment)
        }
    }
    
    struct ContentView: View {
    
        // In a real app you should use dependency injection here
        // (i.e. provide the assignmentController as a parameter)
        @ObservedObject var assignmentController = AssignmentController()
      
        var body: some View {
            
            NavigationView {
                VStack {
                
                    // I prefer to use `map` instead of conditional views, since it
                    // eliminates the need for forced unwrapping
                    self.assignmentController.firstAssignment.map { assignmentModelView in
                        Form {
                            ObservingTextField(title: "Assignment Name", value:  assignmentModelView.name)
                            ObservingTextField(title: "Notes", value: assignmentModelView.notes)
                        }
                    }
                   
                    Button(action: { self.retrieveFirstAssignment() }) {
                        Text("Get The First Assignment !")
                    }
                }
                .navigationBarTitle("First Assignment")
            }
        }
        
        func retrieveFirstAssignment() {
            assignmentController.retrieveFirstAssignment()
        }
    }
    
    //
    // Wrapper for TextField that correctly updates whenever the value
    // changes
    //
    struct ObservingTextField: View {
        let title: String
        @ObservedObject var value: ObservableValue<String>
        
        var body: some View {
            TextField(title, text: $value.value)
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    

    这对您的应用来说可能是多余的。有一个更直接的版本,但它有 缺点是 TextFields 会更新,即使它们的内容没有改变。在 这个特殊的例子我认为这并不重要。对于较大的项目,它可能 变得重要,而不是(仅)出于性能原因,但更新有时非常重要 可见的。供参考:这是更简单的版本。

    import SwiftUI
    
    // Model
    struct Assignment {
        let name : String
        let notes: String
    }
    
    // ViewModel
    class AssignmentViewModel: ObservableObject {
        @Published var name : String
        @Published var notes: String
        
        init(assignment: Assignment) {
            name  = assignment.name
            notes = assignment.notes
        }
    }
    
    // Controller
    class AssignmentController: ObservableObject {
        @Published var firstAssignment: AssignmentViewModel?
    
        func retrieveFirstAssignment() {
            let assignment = Assignment(name: "My First Assignment", notes: "Everyone has to start somewhere...")
            
            firstAssignment = AssignmentViewModel(assignment: assignment)
        }
    }
    
    struct ContentView: View {
        // In a real app you should use dependency injection here
        // (i.e. provide the assignmentController as a parameter)
        @ObservedObject var assignmentController = AssignmentController()
      
        var body: some View {
            
            NavigationView {
                VStack {
                    self.assignmentController.firstAssignment.map { assignmentModelView in
                        FirstAssignmentView(firstAssignment: assignmentModelView)
                    }
                   
                    Button(action: { self.retrieveFirstAssignment() }) {
                        Text("Get The First Assignment !")
                    }
                }
                    .navigationBarTitle("First Assignment")
            }
        }
        
        func retrieveFirstAssignment() {
            assignmentController.retrieveFirstAssignment()
        }
    }
    
    struct FirstAssignmentView: View {
        @ObservedObject var firstAssignment: AssignmentViewModel
        
        var body: some View {
            Form {
                TextField("Assignment Name", text: $firstAssignment.name)
                TextField("Notes", text: $firstAssignment.notes)
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    【讨论】:

      【解决方案3】:

      我也有同样的问题,我偶然发现了 Matteo Manferdini 的一篇优秀论文,他在其中描述了如何在 SwiftUI 中使用 MVC 模型。 我用他的论文重构了一个使用 CoreData 的Pizza app。即使我还是 SwiftUI 的初学者,它也让我对如何实现 MVC 有了很好的理解。你可以找到 Matteo 的论文here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-30
        • 2022-11-17
        • 2023-03-17
        • 2021-04-24
        • 1970-01-01
        • 2012-02-22
        相关资源
        最近更新 更多