筛选用户喜爱的当地
在Landmarks中,有一个这样的需求:
- 有一个按钮,用户点击能够符号成自己喜爱的当地
- 有一个开关,当用户翻开开关时,列表显现的就是用户喜爱的一切当地
现在开始项目文件包含了本次教程需求的初始化项目工程文件
第一节 符号用户喜爱的地标
为了一望而知的显现用户喜爱的地标,您需求Landmark模型中增加一个符号位,当标签位true时,展现一颗星,表示用户保藏即喜爱该地标。
第一步 Landmark模型增加符号
翻开Landmark.swift
增加bool
类型的isFavorite
特点
import Foundation
import SwiftUI
import CoreLocation
struct Landmark: Hashable, Codable, Identifiable{
var id: Int
var name: String
var park: String
var state: String
var description: String
var imageName: String
var image: Image {
Image(imageName)
}
var isFarvorite: Bool
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: coordinates.latitude, longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
第二步 展现的逻辑
在LandmarkRow.swift
中增加if 判断语句,当为true时展现
因为系统图像是根据矢量的,因而您能够运用foregroundColor(_:)
润饰符更改它们的颜色。
import SwiftUI
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
HStack {
landmark.image
.resizable()
.frame(width: 50, height: 50)
Text(landmark.name)
Spacer()
if landmark.isFarvorite {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
}
}
}
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
// LandmarkRow(landmark: landmarks[0])
Group {
LandmarkRow(landmark: landmarks[0])
LandmarkRow(landmark: landmarks[1])
}
.previewLayout(.fixed(width: 300, height: 70))
}
}
第二节 过滤地标列表
您能够自定义列表视图,使其显现一切地标,或仅显现用户的保藏夹。为此,您需求在LandmarkList
中增加一个状况。
状况是一个值,或一组值,能够跟着时刻的推移而改变,并影响视图的行为、内容或布局。您运用具有@State
特点的特点将状况增加到视图中
第一步 增加状况
在LandmarkList.swift
中增加一个showFavoritesOnly
的状况,其初始值是false,用@State
润饰。
因为您运用状况特点来保存特定于视图及其子视图的信息,因而您始终将状况创立为private
。
第二步 改写画布
单击康复来改写画布
当您更改视图的结构(如增加或修正特点)时,您需求手动改写画布。
第三步 过滤逻辑
经过检查showFavoritesOnly
特点和每个landmark.isFavorite
值来核算地标列表的过滤版别。
第四步 展现过滤后的地标列表
运用filteredLandmarks
地标列表显现
showFavoritesOnly
的初始值更改为true
,以检查列表的反应。
import SwiftUI
struct LandmarkList: View {
@State private var showFavoritesOnly = false
var filterLandmarks: [Landmark] {
landmarks.filter { landmark in
(!showFavoritesOnly || landmark.isFavorite)
}
}
var body: some View {
NavigationView {
List(filterLandmarks) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
LandmarkRow(landmark: landmark)
}
}
.navigationTitle("Landmarks")
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
// LandmarkList()
LandmarkList()
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
}
}
第三节 增加一个控件来切换状况
您需求增加一个控件,用来切换showFavorites
的值。您需求给开关控件传递一个绑定。
这个绑定引用了可变状况,当用户点击开关从封闭到翻开,然后再次封闭时,控件运用绑定相应地更新视图的状况
第一步 创立一个嵌套的For
组,将地标转换为行。
要将静态视图和动态视图兼并到列表中,或兼并两组以上的动态视图,请运用For
类型,而不是将数据搜集传递给List
。
第二步 增加Toggle
视图作为List
视图的第一个子项,将绑定传递给showFavoritesOnly
。
您能够运用$
前缀拜访状况变量或其特点之一的绑定。
在继续之前,将showFavoritesOnly
的默认值回来为false
。
mport SwiftUI
struct LandmarkList: View {
@State private var showFavoritesOnly = false
var filterLandmarks: [Landmark] {
landmarks.filter { landmark in
(!showFavoritesOnly || landmark.isFavorite)
}
}
var body: some View {
NavigationView {
List {
Toggle(isOn: $showFavoritesOnly) {
Text("Favorites only")
}
ForEach(filterLandmarks) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
LandmarkRow(landmark: landmark)
}
}
.navigationTitle("Landmarks")
}
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
// LandmarkList()
LandmarkList()
.previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)"))
}
}
第三步 运用实时预览,经过点击开关来测验此新功能。
第四节 运用可调查目标进行存储
为了让用户保藏地标,首先要将地标数据存储在可调查目标傍边。
一个可调查的目标是一个自定义的目标,存储在环境变量中,能够被视图绑定。可调查目标发生改变时更新视图
第一步 修正ModelData.swift
声明ModelData
类,恪守ObservableObject
swiftUI会订阅ObservableObject
,当数据改变时,更新一切需求改写的视图。
将landmarks
数组放入ModelData
类。
第二步
可调查目标需求发布对其数据的任何更改,以便其订阅者能够获取更改。
增加@Published 润饰landmarks
数组
import Foundation
final class ModelData: ObservableObject {
@Published var landmarks:[Landmark] = load("landmarkData.json")
}
func load<T: Decodable>(_ fileName: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: fileName, withExtension: nil)
else {
fatalError("Couldn't find \(fileName) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(fileName) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(fileName) as \(T.self):\n\(error)")
}
}
第五节 视图运用模型目标
现在您现已创立了ModelData
类,您需求更新视图,以将其用作应用程序的数据存储。
第一步 LandmarkList增加ModelData目标
在LandmarkList.swift
中,将@Environment
特点声明增加到视图中,并将environmentObject(_:)
润饰符增加到预览中。
过滤地标时运用model.landmarks
作为数据。
第二步 修正LandmarkDetail
更新LandmarkDetail预览以运用Model
目标。
第三步 更新LandmarkRow预览以运用Model
目标。
第四步 更新Content
预览以将模型目标增加到环境中,使该目标可用于任何子视图。
假如任何子视图在环境中需求模型目标,则预览失败,但您正在预览的视图没有environmentObject(_:)
润饰符。
第五步 更新LandmarksApp
更新LandmarksApp以创立模型实例,并运用environmentObject(_:)
润饰符将其供给给Content
。
在应用程序的生命周期内,运用@StateObject
特点仅初始化给定特点的模型目标一次。当您在应用程序实例中运用该特点时(如图所示)以及在视图中运用它时,状况也是如此。
import SwiftUI
@main
struct LandmarksApp: App {
@StateObject private var modelData = ModelData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(modelData)
}
}
}
第六步 切换回Landmark.swift
并翻开实时预览,以验证一切是否正常作业。
第六节 为每个地标创立一个保藏按钮
地标应用程序现在能够在地标的过滤视图和未过滤视图之间切换,但最喜爱的地标列表仍然是硬编码的。要答应用户增加和删去保藏夹,您需求在地标详细信息视图中增加保藏夹按钮。
第一步 创立一个名为FavoriteButton.swift
的新视图。
1.增加一个isSet
绑定,指示按钮的当前状况,并为预览供给一个恒定值。
2.因为您运用绑定,在此视图中所做的更改会传播回数据源。
3.创立一个带有切换isSet
状况的操作的Button
,并根据状况更改其外观。
4.当您运用iconOnly
标签款式时,您为按钮标签供给的标题字符串不会出现在UI中,但旁白(针对盲人)运用它来改善可拜访性。
import SwiftUI
struct FavoriteButton: View {
@Binding var isSet: Bool
var body: some View {
Button {
isSet.toggle()
} label: {
Label("Toggle Favorite", systemImage: isSet ? "star.fill" : "star")
.labelStyle(.iconOnly)
.foregroundColor(isSet ? .yellow : .gray)
}
}
}
struct FavoriteButton_Previews: PreviewProvider {
static var previews: some View {
FavoriteButton(isSet: .constant(true))
}
}
第二步 修正LandmarkDetail
切换到LandmarkDetail.swift
,经过与模型数据进行比较来核算输入地标的索引。
为了支持这一点,您还需求拜访环境的模型数据。
运用新的FavoriteButton
将地标的名称嵌入到HStack
;运用美元符号($)供给与isFavorite
特点的绑定。
import SwiftUI
struct LandmarkDetail: View {
@EnvironmentObject var modelData: ModelData
var landmark: Landmark
var landmarkIndex: Int {
modelData.landmarks.firstIndex { $0.id == landmark.id}!
}
var body: some View {
ScrollView {
MapView(coordinate: landmark.locationCoordinate)
.ignoresSafeArea(edges: .top)
.frame(height: 300)
CircleImage(image: landmark.image)
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
HStack {
Text(landmark.name)
.font(.title)
.foregroundColor(.black)
FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite)
}
HStack {
Text(landmark.park)
Spacer()
Text(landmark.state)
}
.font(.subheadline)
.foregroundColor(.secondary)
Divider()
Text("About \(landmark.name)")
.font(.title2)
Text(landmark.description)
}
.padding(.leading, 10)
.padding(.trailing, 10)
Spacer()
}
.navigationTitle(landmark.name)
.navigationBarTitleDisplayMode(.inline)
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var landmarks = ModelData().landmarks
static var previews: some View {
LandmarkDetail(landmark: landmarks[0])
.environmentObject(ModelData())
}
}
第三步 检查结果
切换回LandmarkList.swift
,翻开实时预览.
当您从列表导航到详细信息并点击按钮时,当您回来列表时,这些更改会继续存在。因为两个视图在环境中拜访相同的模型目标,因而这两个视图保持一致性。