前言
为地标添加一个分类列表,每个分类是一个支持水平滚动的列表。当您构建此视图并将其连接到现有视图时,您将探索组合视图如何适应不同的设备尺寸和方向。
按照步骤构建这个项目,或者下载完成的项目来自己探索。
第一节 添加一个分类视图
您可以创建一个分类视图对地标进行排序,这样可以提供一个浏览地标的不同方式,同时在顶部高亮显示一个特色地标
第一步 创建CategoryHome.swift
在您的项目Views分组下新增一个Categories分组,在Categories分组下创建CategoryHome.swift
import SwiftUI
struct CategoryHome: View {
var body: some View {
Text("Hello, World!")
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
}
}
第二步 添加一个导航视图,用来组织不同的分类
您可以使用导航视图以及NavigationLink实例和相关修饰符在应用程序中构建分层导航结构。
第三步 将导航栏的标题设置为“Featured”。
该视图在顶部展示一个或多个特色地标
import SwiftUI
struct CategoryHome: View {
var body: some View {
NavigationView {
Text("Hello, World!")
.navigationTitle(Text("Feature"))
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
}
}
第二节 创建分类列表
分类列表展示了地标的所有分类,在每一行又展示了某一个分类的所有地标。
要做到这个效果,您需要将水平和垂直的stack结合起来使用,且给列表添加可滑动性
从
landmarkData.json读取分类信息
第一步 添加模型字段
在Landmark.swift中添加category属性,以及Category枚举,Category枚举的内容和landmarkData.json相对应
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 isFavorite: Bool
var category: Category
enum Category: String, CaseIterable, Codable {
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: coordinates.latitude, longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
第二步 修改ModelData.swift
在ModelData.swift中,添加一个计算类别字典,将类别名称作为关键字,并为每个关键字添加一组相关的地标。
final class ModelData: ObservableObject {
@Published var landmarks:[Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
var categories: [String : [Landmark]] {
Dictionary(
grouping: landmarks,
by: {$0.category.rawValue})
}
}
第三步 在CategoryHome.swift中,创建一个modelData环境对象。
现在您可以访问分类信息了,稍后还可以访问其它地标信息
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
Text("Landmarks content")
.navigationTitle(Text("Feature"))
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome().environmentObject(ModelData())
}
}
第四步 将分类信息放入List中
使用列表在地标中显示类别。
Category案例名称标识列表中的每个项目,因为它是一个枚举,所以在其他类别中必须是唯一的。
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List {
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
Text(key)
}
.navigationTitle(Text("Featured"))
}
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome().environmentObject(ModelData())
}
}
第三节 创建分类的行
Landmarks一行展示一个分类,每行都支持滚动。
添加一个新的视图类来表示行,然后在新视图中显示该类别的所有地标。
第一步 创建CategoryRow.swift
添加分类的名称categoryName和该分类下的地标数组items
var categoryName: String
var items: [Landmark]
第二步 展示分类名称
Text(categoryName) .font(.headline)
第三步 展示分类下的地标
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
VStack(alignment: .leading) {
Text(categoryName)
.font(.headline)
HStack(alignment: .top, spacing: 0) {
ForEach(items) { landmark in
Text(landmark.name)
}
}
}
}
}
struct CategoryRow_Previews: PreviewProvider {
static var landmarks = ModelData().landmarks
static var previews: some View {
CategoryRow(categoryName: landmarks[0].category.rawValue, items: Array(landmarks.prefix(3)))
}
}
第四步 布局
通过指定一个高度、添加padding并在滚动视图中包装HStack,为内容提供一些空间。 使用更大的数据采样更新视图预览可以更容易地确保滚动行为是正确的。
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
VStack(alignment: .leading) {
Text(categoryName)
.font(.headline)
.padding(.leading, 15)
.padding(.top, 5)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(items) { landmark in
Text(landmark.name)
}
}
}
.frame(height: 185)
}
}
}
第五步 创建一个自定义视图CategoryItem.swift展示一个地标
import SwiftUI
struct CategoryItem: View {
var landmark: Landmark
var body: some View {
VStack(alignment: .leading) {
landmark.image
.resizable()
.frame(width: 155, height: 155)
.cornerRadius(5)
Text(landmark.name)
.font(.caption)
}
.padding(.leading, 15)
}
}
struct CategoryItem_Previews: PreviewProvider {
static var previews: some View {
CategoryItem(landmark: ModelData().landmarks[0])
}
}
第六步 CategoryRow.swift中使用CategoryItem
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
VStack(alignment: .leading) {
Text(categoryName)
.font(.headline)
.padding(.leading, 15)
.padding(.top, 5)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(items) { landmark in
CategoryItem(landmark: landmark)
}
}
}
.frame(height: 185)
}
}
}
struct CategoryRow_Previews: PreviewProvider {
static var landmarks = ModelData().landmarks
static var previews: some View {
CategoryRow(categoryName: landmarks[0].category.rawValue, items: Array(landmarks.prefix(6)))
}
}
第四节 完成分类视图
将行和特色图像添加到类别主页
第一步 更新CategoryHome中行信息
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List {
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
CategoryRow(categoryName: key, items: modelData.categories[key]!)
}
.navigationTitle(Text("Featured"))
}
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome().environmentObject(ModelData())
}
}
接下来,您将会在顶部添加一个特色地标,您要在landmark数据模型中添加需要的信息
第二步 在Landmark.swift中添加isFeatured属性,该属性代表是否是特色地标
var isFeatured: Bool
第三步 修改ModelData.swift
添加一个计算属性features数组,其中包含所有特色的地标
var features: [Landmark] {
landmarks.filter {$0.isFeatured}
}
第四步 在顶部添加特色地标图片
在后面的教程中,您将把这个视图变成一个交互式旋转木马。目前,它通过缩放和裁剪的预览图像显示其中一个特色地标
modelData.features[0].image
.resizable()
.scaledToFill()
.frame(height: 200)
.clipped()
第五步 充满屏幕
设置两种地标的预览边缘偏移为0,这样整个内容将会充满到屏幕边缘
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List {
modelData.features[0].image
.resizable()
.scaledToFill()
.frame(height: 200)
.clipped()
.listRowInsets(EdgeInsets())
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
CategoryRow(categoryName: key, items: modelData.categories[key]!)
}
.listRowInsets(EdgeInsets())
}
.listStyle(.grouped)
.navigationTitle(Text("Featured"))
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome().environmentObject(ModelData())
}
}
添加导航
为不同分类的地标添加点击跳转功能
第一步 CategoryRow 添加跳转
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
VStack(alignment: .leading) {
Text(categoryName)
.font(.headline)
.padding(.leading, 15)
.padding(.top, 5)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(items) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
CategoryItem(landmark: landmark)
}
}
}
}
.frame(height: 185)
}
}
}
struct CategoryRow_Previews: PreviewProvider {
static var landmarks = ModelData().landmarks
static var previews: some View {
CategoryRow(categoryName: landmarks[0].category.rawValue, items: Array(landmarks.prefix(6)))
}
}
第二步 修改CategoryItem
通过应用renderingMode(:)和foregroundColor(:)修改器来更改CategoryItem的导航外观。
作为导航链接标签传递的文本使用环境的强调色进行渲染,图像可以作为模板图像进行渲染。您可以修改任一行为以最适合您的设计。
接下来,您将修改应用程序的主要内容视图,以显示选项卡视图,用户可以在您刚刚创建的类别视图和现有的地标列表之间进行选择。
第三步 取消固定预览,切换到ContentView并添加要显示的选项卡的枚举。
enum Tab {
case featured
case list
}
第四步 为选项卡选择添加一个状态变量,并为其指定一个默认值。
@State private var selection: Tab = .featured
第五步 添加选项卡
创建一个选项卡视图,用于包装LandmarkList以及CategoryHome。 每个视图上的tag(_:)修饰符与选择属性可以采用的一个可能值相匹配,因此当用户在用户界面中进行选择时,TabView可以协调显示哪个视图。
TabView(selection: $selection) {
CategoryHome()
.tag(Tab.featured)
LandmarkList()
.tag(Tab.list)
}
第六步 给选项卡设置图标和文案
import SwiftUI
struct ContentView: View {
@State private var selection: Tab = .featured
enum Tab {
case featured
case list
}
var body: some View {
TabView(selection: $selection) {
CategoryHome()
.tabItem({
Label("Feature", systemImage: "star")
})
.tag(Tab.featured)
LandmarkList()
.tabItem({
Label("List", systemImage: "list.bullet")
})
.tag(Tab.list)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(ModelData())
}
}