在Landmark App中,用户应该要能收藏他们喜欢的地方,并过滤列表只显示他们收藏的。要创建这些特性,我们通过在列表中添加一个切换控件让用户只看到他们收藏的那些地标,然后添加一个星形按钮,用户点击之后会将这个地标标记为收藏的。
<下载>启动项目并跟随本教程,或打开完成的项目自己研究代码。
01 标记用户喜欢的地标
通过加强列表为用户显示他们收藏的地标开始。在每个LandmarkRow
中添加一个星标显示收藏的地标。
第一步
打开初始的Xcode项目,然后在项目导航器中选中LandmarkRow.swift
文件。
第二步
在Spacer
控件后,添加一个if
语句判断当前地标是否是收藏的,并在语句中添加星形图片。
在SwiftUI代码块中,我们使用if
语句来根据条件包含视图。
Text(landmark.name)
Spacer()
if landmark.isFavorite {
Image(systemName: "star.fill")
.imageScale(.medium)
}
}
}
第三步
由于系统图片是矢量的,我们可以使用foregroundColor(_:)
修饰器改变它们的颜色。
星形控件会在地标的isFavorite
属性为true
时显示。我们之后会学习如何修改这个属性。
Image(systemName: "star.fill")
.imageScale(.medium)
.foregroundColor(.yellow)
}
}
02 过滤列表视图
我们可以定制列表视图,让它显示所有的地标,或是只显示用户收藏的。要实现这个功能,我们需要在LandmarkList
类中添加一些状态。
状态是指一个值,或是一组值,它们可能会随时变化,并相应地影响视图的表现,内容或布局。我们使用带有@State
标的属性将状态添加到视图中。
第一步
在项目导航器中选中LandmarkList.swift
文件,在LandmarkList
类中添加一个带有@State
标注的变量showFavoritesOnly
,并将其初始值设置为false
。
struct LandmarkList: View {
@State var showFavoritesOnly = false
var body: some View {
NavigationView {
第二步
点击Resume
按钮刷新画布。
当我们对视图的结构做了变更,比如添加或修改了属性时,我们需要手动刷新画布。
第三步
通过检测showFavoritesOnly
属性和每个地标的landmark.isFavorite
值过滤地标列表。
NavigationView {
List(landmarkData) { landmark in
if !self.showFavoritesOnly || landmark.isFavorite {
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
03 添加控件触发状态切换
要让用户可以控制列表过滤与否,我们需要添加一个控件来修改showFavoritesOnly
的值。这里通过绑定一个开关控件来实现。
绑定是与可变状态建立联系。当用户点击开关控件从关闭到打开,再到关闭的时候,控件使用绑定相应的更新视图的状态。
第一步
创建一个嵌套的ForEach
组,将地标转换为行视图。
要组合静态和动态视图到一个列表中,可组合两个或更多不同组的动态视图,使用ForEach
类型而不是将集合数据传入列表。
struct LandmarkList: View {
@State var showFavoritesOnly = true
var body: some View {
NavigationView {
List {
ForEach(landmarkData) { landmark in
if(!self.showFavoritesOnly || landmark.isFavorite) {
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
}
第二步
添加一个Toggle
视图作为列表的第一个子视图,传入一个showFavoritesOnly
绑定。
使用$
前缀来访问状态变量的绑定,或它的一个属性。
NavigationView {
List {
Toggle(isOn: $showFavoritesOnly) {
Text("Favorites only")
}
ForEach(landmarkData) { landmark in
if !self.showFavoritesOnly || landmark.isFavorite {
第三步
使用实时预览并尝试点击新添加的开关控件的功能。
04 为存储使用可观察对象
为了准备让用户控制哪个地标是收藏了的,我们首先需要将地标数据存储在可观察对象中。
可观察对象是一个为数据自定义的对象,在SwiftUI环境中可以将存储与视图绑定。SwiftUI会监视可观察对象可能会导致UI变化的数据改变,并在数据改变后显示正确的视图版本。
第一步
创建一个Swift文件命名为UserData.swift
。
import SwiftUI
第二步
声明一个新的model遵从Combine
框架中的ObservableObject
协议。
SwiftUI可以订阅可观察对象,并在数据改变的时候更新需要刷新的视图。
import SwiftUI
import Combine
final class UserData: ObservableObject {
}
第三步
为showFavoritesOnly
和地标数组添加存储属性,并初始化。
final class UserData: ObservableObject {
var showFavoritesOnly = false
var landmarks = landmarkData
}
一个可观察对象需要公布任何对数据的变更,这样订阅者才能知道数据变更了。
第四步
为model中每个数据添加@Published
标注。
final class UserData: ObservableObject {
@Published var showFavoritesOnly = false
@Published var landmarks = landmarkData
}
05 在视图中使用model对象
现在我们已经创建了UserData
对象,需要更新视图将其作为数据存储。
第一步
在LandmarkList.swift
文件中,用@EnvironmentObject
属性替换showFavoritesOnly
的声明,并在预览中添加environmentObject(_:)
修饰器。
这个userData
属性会在environmentObject(_:)
修饰器应用到父视图中后自动获取它的值。
struct LandmarkList: View {
@EnvironmentObject var userData: UserData
var body: some View {
第二步
使用userData
访问相同的属性替换showFavoritesOnly
的使用。
和@State
标注的属性一样,在userData
对象前添加$
前缀绑定并访问它的成员属性。
NavigationView {
List {
Toggle(isOn: $userData.showFavoritesOnly) {
Text("Favorites only")
}
第三步
使用userData.landmarks
作为数据源创建ForEach
实例。
}
ForEach(userData.landmarks) { landmark in
if !self.userData.showFavoritesOnly || landmark.isFavorite {
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
第四步
在SceneDelegate.swift
文件中,为LandmarkList
添加environmentObject(_:)
修饰器。
如果在模拟器或设备中构建并运行Landmarks App,相比在预览中使用,这个更新确保了LandmarkList
在环境中会拥有UserData
对象。
第五步
更新LandmarkDetail
视图使用环境中的UserData
对象。
在访问或更新地标的收藏状态时,我们会使用landmarkIndex
,这样我们就可以访问正确版本的数据。
struct LandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: { $0.id == landmark.id })!
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
let userData = UserData()
return LandmarkDetail(landmark: userData.landmarks[0])
.environmentObject(userData)
}
}
第六步
切换回LandmarkList.swift
文件并打开实时预览来验证所有功能按预期运行。
06 为每个地标创建收藏按钮
现在Landmarks App可以在过滤和未过滤地标视图中切换了,但是列表的收藏状态还是硬编码的。为了让用户可以添加或移除收藏,我们需要在地标详情页添加收藏按钮。
第一步
在LandmarkDetail.swift
文件中,将地标的名字嵌套进HStack。
VStack(alignment: .leading) {
HStack {
Text(landmark.name)
.font(.title)
}
HStack(alignment: .top) {
Text(landmark.park)
第二步
在地标名字旁边创建一个按钮,使用if-else
条件语句根据地标是否已经收藏显示不同的图片。
在按钮的action
闭包中,我们使用userData
配合landmarkIndex
更新当前的地标数据。
Text(landmark.name)
.font(.title)
Button(action: {
self.userData.landmarks[self.landmarkIndex].isFavorite.toggle()
}) {
if self.userData.landmarks[self.landmarkIndex].isFavorite {
Image(systemName: "star.fill")
.foregroundColor(Color.yellow)
} else {
Image(systemName: "star")
.foregroundColor(Color.gray)
}
}
}
第三步
切换回LandmarkList.swift
文件,并打开实时预览。
当我们从列表导航到详情页并点击了收藏按钮,在返回列表页时这些变更会存储下来。由于这两个视图都会访问环境中的同一个model,它们的状态会保持一致。