模型-视图-视图模型(MVVM)是一种结构化的设计模式,将对象分成三个不同的组。
模型持有应用程序数据。它们通常是结构或简单的类。
视图在屏幕上显示视觉元素和控件。它们通常是UIView的子类。
视图模型将模型信息转化为可在视图上显示的值。它们通常是类,所以它们可以作为引用被传递。
这种模式听起来很熟悉吗?是的,它与模型-视图-控制器(MVC)非常相似。请注意,本页顶部的类图包括一个视图控制器;视图控制器在MVVM中确实存在,但它们的作用被最小化了。
在本章中,你将学习如何实现视图模型,并组织你的项目以包括它们。你将从一个简单的例子开始,了解视图模型的作用,然后你将采用一个MVC项目并将其重构为MVVM。
当你需要将模型转换为视图的另一种表示时,请使用这种模式。例如,你可以使用视图模型将Date转换成数据格式的String,将小数转换成货币格式的String,或者许多其他有用的转换。
这种模式对MVC特别有利。如果没有视图模型,你可能会把模型到视图的转换代码放在你的视图控制器中。然而,视图控制器已经做了很多事情:处理viewDidLoad和其他视图生命周期事件,通过IBActions处理视图回调以及其他一些任务。
这就导致了开发者戏称的 "MVC:大规模的视图控制器"。
如何避免视图控制器的过度构建?很简单--使用MVC以外的其他模式! MVVM是瘦身的好方法,它能使需要进行多次模型到视图转换的庞大的视图控制器变得更小。
打开Starter目录下的IntermediateDesignPatterns.xcworkspace,然后打开MVVM页面。
对于这个例子,你将制作一个 "宠物视图",作为收养宠物的应用程序的一部分。在代码示例后添加以下内容。
在这里,你定义了一个名为 Pet 的模型。每只宠物都有名字、生日、稀有度和形象。你需要在视图上显示这些属性,但不能直接显示生日和稀有度。它们首先需要通过视图模型进行转换。
接下来,将以下代码添加到 Playground 的末尾:
以下是你在上面所做的:
首先,你创建了两个名为 pet 和 calendar 的私有属性,并在 init(pet:) 中进行了设置。
接下来,为 name 和 image 声明了两个计算属性,分别返回宠物的 name 和 image。这是你可以执行的最简单的转换:不加修改地返回一个值。如果你想更改设计为每只宠物的名称添加前缀,你可以通过在此处修改名称来轻松实现。
接下来,你将 ageText 声明为另一个计算属性,在这里你使用日历计算从今天开始到宠物生日之间的年差,并将其作为字符串返回,后跟“岁”。你将能够直接在视图上显示此值,而无需执行任何其他字符串格式化。
最后,你创建了 admissionFeeText 作为最终计算属性,你可以在其中根据宠物的稀有性确定宠物的收养成本。同样,你将其作为字符串返回,以便你可以直接显示它。
现在你需要一个 UIView 来显示宠物的信息。将以下代码添加到操场的末尾:
在这里,你创建了一个带有四个子视图的 PetView:一个用于显示宠物图像的 imageView 和三个用于显示宠物名称、年龄和收养费的标签。
你在 init(frame:) 中创建和定位每个视图。最后,你在 init?(coder:) 中抛出一个 fatalError 来表明它不被支持。
你已经准备好将这些班级付诸行动了! 在Playground的末尾添加以下代码。
以下是你的做法。
首先,你创建了一个名为stuart的新宠物。
接下来,你用stuart创建了一个viewModel。
接下来,你通过在iOS上传递一个普通的框架尺寸来创建一个视图。
接下来,你用viewModel配置了视图的子视图。
最后,你将视图设置为PlaygroundPage.current.liveView,这告诉Playground在标准的助理编辑器中渲染它。
要看这个动作,请选择Editor ▸ Live View来查看渲染的视图。
斯图尔特究竟是什么类型的宠物?当然,他是一只饼干怪兽。它们非常罕见。
你可以对这个例子做最后的改进。在PetViewModel的类关闭大括号后添加以下扩展。
你将用这个方法来配置视图,而不是用视图模型来做这个内联。
找到你之前输入的以下代码。
// 4
view.nameLabel.text = viewModel.name
view.imageView.image = viewModel.image
view.ageLabel.text = viewModel.ageText
view.adoptionFeeLabel.text = viewModel.adoptionFeeText
...并将该代码替换为以下内容。
viewModel.configure(view)
这是一个将所有的视图配置逻辑放入视图模型的好方法。在实践中,你可能想这样做,也可能不想。如果你只对一个视图使用视图模型,那么把configure方法放到视图模型中会很有用。
但是,如果你在使用视图模型的时候有一个以上的视图,那么你可能会发现把所有的逻辑都放在视图模型中会很麻烦。在这种情况下,为每个视图单独配置代码可能更简单。
你的输出应该和以前一样。
嘿,斯图尔特,你要分享那个饼干吗?不分享吗?噢,来吧...!
如果你的应用程序需要许多模型到视图的转换,那么MVVM的效果很好。然而,不是每个对象都能整齐地归入模型、视图或视图模型的类别。相反,你应该将MVVM与其他设计模式结合起来使用。
此外,MVVM在你刚创建应用程序时可能不是很有用。MVC可能是一个更好的起点。当你的应用程序的要求发生变化时,你可能需要根据你不断变化的要求来选择不同的设计模式。当你真正需要它时,可以在应用程序的后期引入MVVM。
不要害怕变化--相反,要提前做好计划。
在本节中,你将向一个名为Coffee Quest的应用程序添加功能。
在Starter目录下,在Xcode中打开CoffeeQuest ▸ CoffeeQuest.xcworkspace(不是.xcodeproj)。
这个应用程序显示由Yelp提供的附近的咖啡店。它使用CocoaPods来拉入YelpAPI,这是一个用于搜索Yelp的辅助库。如果你以前没有使用过CocoaPods,那也没关系!你所需要的一切都已经包含在其中了。你所需要的一切都已包含在启动项目中。你唯一需要记住的是打开CoffeeQuest.xcworkspace,而不是CoffeeQuest.xcodeproj文件。
注意:如果你想了解更多关于CocoaPods的信息,请阅读我们关于它的教程:http://bit.ly/cocoapods-tutorial。
在你运行该应用程序之前,你首先需要注册一个Yelp API密钥。
在你的网络浏览器中导航到这个URL。
如果你没有账户,请创建一个账户,或者登录。接下来,在 "创建应用程序 "表格中输入以下内容(如果你以前创建过一个应用程序,则使用你现有的API密钥)。
应用程序名称。"咖啡探索"
应用程序网站。(留空)
行业。选择 "商业"
公司。(留此空白)
联系电子邮件。(你的电子邮件地址)
描述。"咖啡搜索应用"
我已阅读并接受Yelp API条款:请勾选 你的表格应该如下所示。
按 "创建新应用程序 "继续,你应该看到一个成功的消息。
复制你的API密钥并返回Xcode中的CoffeeQuest.xcworkspace。
打开CoffeeQuest\Resources\APIKeys.swift,将你的API密钥粘贴到指定位置。
模拟器的默认位置被设置为旧金山。哇,那座城市有很多咖啡馆啊
注意:你可以通过点击Debug ▸ Location,然后选择不同的选项来改变模拟器的位置。
这些地图针有点无聊。如果它们能显示出哪些咖啡店是真正好的,那不是很好吗?
打开CoffeeQuest\Models\MapPin.swift。MapPin需要一个坐标、标题和评级,然后将这些转换为地图视图可以显示的东西......这听起来很熟悉吗?是的,它实际上是一个视图模型
首先,你需要给这个类起一个更好的名字。在文件顶部的MapPin上点击右键,选择Refactor ▸ Rename。
这让其他开发者明显感觉到你在使用MVVM模式。明确性是好的!
BusinessMapViewModel需要更多的属性,以显示令人兴奋的地图注释,而不是由MapKit提供的普通图钉。
还是在BusinessMapViewModel.swift中,用以下内容替换import Foundation。
import UIKit
你在这里导入UIKit,因为你要添加需要UIKit的组件。
接下来,在现有的属性之后添加以下属性;暂时忽略由此产生的编译器错误。
public let image: UIImage
public let ratingDescription: String
你将使用图像而不是默认的针状图像,并且当用户点击注释时,你将以副标题的形式显示评级描述。
接下来,把init(coordinate:name:rating:)替换成以下内容。
你通过这个初始化器接受图像,并从评级中设置ratingDescription。
接下来,在MKAnnotation扩展的末尾添加以下计算属性。
public var subtitle: String? {
return ratingDescription
}
这告诉地图在选择注解呼出时使用评级描述作为副标题。现在你可以消除编译器的错误了。打开ViewController.swift,向下滚动到文件的末尾。
将addAnnotations()改为以下内容。
建立并运行你的应用程序。它现在应该看起来......一样?怎么会这样?
地图并不了解图像。相反,你应该覆盖一个委托方法来提供自定义的针脚注释图像。这就是为什么它看起来和以前一样了。
在addAnnotations()之后添加以下方法。
这只是创建了一个MKAnnotationView,它为给定的注释显示正确的图像;这是你的BusinessMapViewModel对象之一。
构建并运行,你应该看到自定义的图像 点击一个,你会看到咖啡店的名字和评价。
看起来大多数旧金山的咖啡店实际上都是四星级或以上的,你现在可以一目了然地找到最好的商店。
你在本章中了解了模型-视图-视图模型(MVVM)模式。以下是它的关键点。
MVVM有助于精简视图控制器,使其更容易操作。从而解决了 "庞大的视图控制器 "问题。
视图模型是将对象转化为不同对象的类,这些对象可以被传入视图控制器并显示在视图上。它们对于将计算属性(如Date或Decimal)转换为String或其他实际可以在UILabel或UIView中显示的东西特别有用。
如果你只在一个视图中使用视图模型,那么把所有的配置放到视图模型中是很好的。然而,如果你使用多个视图,你可能会发现把所有的逻辑都放在视图模型中会很混乱。在每个视图中分开配置代码可能会更简单。
如果你的应用程序很小,MVC可能是一个更好的起点。当你的应用程序的需求发生变化时,你很可能需要根据你不断变化的需求来选择不同的设计模式。
你为Coffee Quest添加了一个非常好的功能,按评级显示咖啡店!但是,仍然有很多问题需要解决。然而,你还可以用这个应用程序做很多事情。继续下一章,学习工厂模式,继续构建Coffee Quest。
上一章 | 目录 | 下一章 |
---|