添加分类视图
创建文件
创建CategoryHome.swift的文件
swift
import SwiftUI
struct CategoryHome: View {
var body: some View {
NavigationView {
Text("Hello, World!1")
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
}
}
import SwiftUI
struct CategoryHome: View {
var body: some View {
NavigationView {
Text("Hello, World!1")
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
}
}
调整数据源并展示分类别表
1). 修改Landmark数据模型,增加枚举类型:
swift
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 isFavorite: Bool
var category: Category
enum Category: String, CaseIterable, Codable {
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
private var imageName: String
var image: Image {
Image(imageName)
}
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
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 isFavorite: Bool
var category: Category
enum Category: String, CaseIterable, Codable {
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
private var imageName: String
var image: Image {
Image(imageName)
}
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
2). 在 ModelData.swift 中,增加计算属性:
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 }
)
}
}
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 }
)
}
}
3). 修改新创建的 CategoryHome.swift
swift
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
Text("Landmarks Content")
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
.environmentObject(ModelData())
}
}
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
Text("Landmarks Content")
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
.environmentObject(ModelData())
}
}
4). 为 CateoryHome.swift
增加列表
swift
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
Text(key)
}
})
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
.environmentObject(ModelData())
}
}
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
Text(key)
}
})
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
.environmentObject(ModelData())
}
}
效果如图:
创建"分类"行
我们需要在水平滚动的列表中展示每个分类。
1). 创建文件CategoryRow.swift
swift
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
Text("Hello, World!")
}
}
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)))
}
}
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
Text("Hello, World!")
}
}
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)))
}
}
2). 创建基本的布局
swift
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
VStack{
Text(categoryName)
.font(.headline)
.padding(.leading, 15)
.padding(.top, 5)
.background(.green)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 2) {
ForEach(items) { landmark in
Text(landmark.name)
}.background(.red)
}
}
// 高度调高,留出足够的空间
.frame(height: 185)
.background(.yellow)
}
}
}
struct CategoryRow_Previews: PreviewProvider {
static var landmarks = ModelData().landmarks
static var previews: some View {
CategoryRow(categoryName: landmarks[0].category.rawValue,
items: Array(landmarks.prefix(4)))
}
}
import SwiftUI
struct CategoryRow: View {
var categoryName: String
var items: [Landmark]
var body: some View {
VStack{
Text(categoryName)
.font(.headline)
.padding(.leading, 15)
.padding(.top, 5)
.background(.green)
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 2) {
ForEach(items) { landmark in
Text(landmark.name)
}.background(.red)
}
}
// 高度调高,留出足够的空间
.frame(height: 185)
.background(.yellow)
}
}
}
struct CategoryRow_Previews: PreviewProvider {
static var landmarks = ModelData().landmarks
static var previews: some View {
CategoryRow(categoryName: landmarks[0].category.rawValue,
items: Array(landmarks.prefix(4)))
}
}
3). 创建 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(15)
}
}
struct CategoryItem_Previews: PreviewProvider {
static var previews: some View {
CategoryItem(landmark: ModelData().landmarks[0])
}
}
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(15)
}
}
struct CategoryItem_Previews: PreviewProvider {
static var previews: some View {
CategoryItem(landmark: ModelData().landmarks[0])
}
}
4). 调整CategoryRow页面
swift
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: 2) {
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(4)))
}
}
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: 2) {
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(4)))
}
}
完成分类页面
1). 调整 CategoryHome.swift 页面,引入CategoryRow
swift
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
CategoryRow(categoryName: key, items: modelData.categories[key]!)
}
})
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
.environmentObject(ModelData())
}
}
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
CategoryRow(categoryName: key, items: modelData.categories[key]!)
}
})
.navigationTitle("Featured")
}
}
}
struct CategoryHome_Previews: PreviewProvider {
static var previews: some View {
CategoryHome()
.environmentObject(ModelData())
}
}
2). 调整数据模型
接下来,将在视图顶部添加一个特色地标。为此,需要从地标数据中获取更多信息。
调整数据模型:
swift
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 isFavorite: Bool
var isFeatured: Bool
var category: Category
enum Category: String, CaseIterable, Codable {
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
// 这里额外增加了imageName, 从数据中读取图像的名称
// 这个属性是私有的,因为Landmarks的构建只关心图像本身
private var imageName: String
// 增加了计算属性的image
var image: Image {
Image(imageName)
}
// 增加了经纬度信息以及对应的结构体
private var coordinates: Coordinates
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
}
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 isFavorite: Bool
var isFeatured: Bool
var category: Category
enum Category: String, CaseIterable, Codable {
case lakes = "Lakes"
case rivers = "Rivers"
case mountains = "Mountains"
}
// 这里额外增加了imageName, 从数据中读取图像的名称
// 这个属性是私有的,因为Landmarks的构建只关心图像本身
private var imageName: String
// 增加了计算属性的image
var image: Image {
Image(imageName)
}
// 增加了经纬度信息以及对应的结构体
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
final class ModelData: ObservableObject {
// 可观察对象需要发布对其数据的任何更改,以便其订阅者能够接收更改。
@Published var landmarks: [Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
var features: [Landmark] {
landmarks.filter { $0.isFeatured }
}
var categories: [String: [Landmark]] {
Dictionary(
grouping: landmarks,
by: { $0.category.rawValue }
)
}
}
final class ModelData: ObservableObject {
// 可观察对象需要发布对其数据的任何更改,以便其订阅者能够接收更改。
@Published var landmarks: [Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
var features: [Landmark] {
landmarks.filter { $0.isFeatured }
}
var categories: [String: [Landmark]] {
Dictionary(
grouping: landmarks,
by: { $0.category.rawValue }
)
}
}
3). 调整分类页面
swift
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
modelData.features[0].image
.resizable()
.scaledToFill()
.frame(height: 200)
.clipped()
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
CategoryRow(categoryName: key, items: modelData.categories[key]!)
}
})
.navigationTitle("Featured")
}
}
}
import SwiftUI
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
modelData.features[0].image
.resizable()
.scaledToFill()
.frame(height: 200)
.clipped()
ForEach(modelData.categories.keys.sorted(), id: \.self) { key in
CategoryRow(categoryName: key, items: modelData.categories[key]!)
}
})
.navigationTitle("Featured")
}
}
}
4). 美化页面,使用了listRowInsets
将‘头图’充满宽度
swift
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
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]!)
}
})
.navigationTitle("Featured")
}
}
}
struct CategoryHome: View {
@EnvironmentObject var modelData: ModelData
var body: some View {
NavigationView {
List(content: {
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]!)
}
})
.navigationTitle("Featured")
}
}
}
增加导航进行页面跳转
1). 增加页面跳转逻辑
swift
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: 2) {
ForEach(items) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
CategoryItem(landmark: landmark)
}
}
}
}
// 高度调高,留出足够的空间
.frame(height: 185)
}
}
}
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: 2) {
ForEach(items) { landmark in
NavigationLink {
LandmarkDetail(landmark: landmark)
} label: {
CategoryItem(landmark: landmark)
}
}
}
}
// 高度调高,留出足够的空间
.frame(height: 185)
}
}
}
2). 增加Tab分别展示普通列表和分类视图
swift
struct ContentView: View {
enum Tab {
case featured
case list
}
@State private var selection: Tab = .featured
@StateObject private var modelData = ModelData()
var body: some View {
// LandmarkList().environmentObject(modelData)
TabView(selection: $selection) {
CategoryHome()
.tabItem {
Label("Featured", 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())
}
}
struct ContentView: View {
enum Tab {
case featured
case list
}
@State private var selection: Tab = .featured
@StateObject private var modelData = ModelData()
var body: some View {
// LandmarkList().environmentObject(modelData)
TabView(selection: $selection) {
CategoryHome()
.tabItem {
Label("Featured", 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())
}
}