筛选用户喜爱的当地

在Landmarks中,有一个这样的需求:

  1. 有一个按钮,用户点击能够符号成自己喜爱的当地
  2. 有一个开关,当用户翻开开关时,列表显现的就是用户喜爱的一切当地
    现在开始项目文件包含了本次教程需求的初始化项目工程文件

第一节 符号用户喜爱的地标

为了一望而知的显现用户喜爱的地标,您需求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,翻开实时预览. 当您从列表导航到详细信息并点击按钮时,当您回来列表时,这些更改会继续存在。因为两个视图在环境中拜访相同的模型目标,因而这两个视图保持一致性。

第三篇 swiftUI Landmarks 处理用户输入
第三篇 swiftUI Landmarks 处理用户输入