将 MVC 之类的设计模式与 SwiftUI 结合使用
Posted
技术标签:
【中文标题】将 MVC 之类的设计模式与 SwiftUI 结合使用【英文标题】:Using a design pattern like MVC with 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. 如果有人可以使用我使用的演示类(用视图模型替换控制器)提供基本代码示例,将不胜感激 【参考方案1】:struct SwiftUIView: View
@State m = AssignmentModel()
var body: some View
// use m
func loadAssignmentFromDB()
m.retrieveFirstAssignment()
这就是我所说的“带有内置 MVVM 的增强型 MVC”。
它可以满足您对 MVC 和可能的 MVVM 的追求,同时省力。
现在我将讨论原因:
-
function,或者更具体地说,mutation是Control。您不需要一个名为“Controller”的对象来在 MVC 中使用 C。否则我们还不如再坚持 UIKit 10 年。
当您的模型是值类型时,这很有意义。没有你的具体说法,没有什么可以改变它,例如; @State 注释。由于改变模型的唯一方法是通过这些指定的端点,因此您的 Control 仅在改变这些端点的函数中生效。
-
引用另一个回复:
确实,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。
【讨论】:
以上是关于将 MVC 之类的设计模式与 SwiftUI 结合使用的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:如何将 .toggle() not Toggle(isOn..) 与 UserDefault 结合使用
将 SwiftUI 中的文本与背景属性相结合会产生错误,因为无法将“某些视图”类型的值转换为预期的参数类型“文本”?