系统: Mac OS 10.15.1, XCode 11.2.1,swift 5.0
写作时间:2019-11-30
1. 说明
此例子包含元素:List视图(代替以前的TableView), 导航。这个例子接着上一个例子SwiftUI实战二:组合视图和地图视图,把它当作详情页。
细节请查看官网教程
2. 元素以及代码下载
https://github.com/zgpeace/BuildingListsAndNavigation
初始项目请用以下目录StartingPoint > Landmarks
3. 数据模型
Landmark.swift
定义了struct
结构体数据的字段,
landmarkData.json
存储了列表数据
Data.swift
实现加载文件landmarkData.json
里的数据,并构建为Landmark.swift
模型数组let landmarkData: [Landmark] = load("landmarkData.json")
LandmarkDetail.swift
的内容就是上一节ContentView.swift
里面的内容.
Landmark.swift
需要实现协议Identifiable
,否则在List调用会报错。
Lists work with identifiable data. You can make your data identifiable in one of two ways: by passing along with your data a key path to a property that uniquely identifies each element, or by making your data type conform to the Identifiable protocol.
import SwiftUI
import CoreLocation
struct Landmark: Hashable, Codable, Identifiable {
var id: Int
var name: String
fileprivate var imageName: String
fileprivate var coordinates: Coordinates
var state: String
var park: String
var category: Category
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: coordinates.latitude,
longitude: coordinates.longitude)
}
enum Category: String, CaseIterable, Codable, Hashable {
case featured = "Featured"
case lakes = "Lakes"
case rivers = "Rivers"
}
}
extension Landmark {
var image: Image {
ImageStore.shared.image(name: imageName)
}
}
struct Coordinates: Hashable, Codable {
var latitude: Double
var longitude: Double
}
4. 创建Cell行视图
实现结果图如下:
创建SwiftUI view
, 命名为 LandmarkRow.swift
.
//
// LandmarkRow.swift
// Landmarks
//
// Created by zgpeace on 2019/11/30.
// Copyright © 2019 Apple. All rights reserved.
//
import SwiftUI
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
HStack() {
landmark.image
.resizable()
.frame(width: 50, height: 50)
Text(landmark.name)
}
}
}
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
Group {
LandmarkRow(landmark: landmarkData[0])
LandmarkRow(landmark: landmarkData[1])
}
.previewLayout(.fixed(width: 300, height: 70))
}
}
代码解析:
- 预览分组: Group可以同时预览多个视图
- previewLayout: 可以设置大小
预览效果:
5. 创建List视图
新建SwiftUI view
, 命名为 LandmarkList.swift
.
import SwiftUI
struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in
LandmarkList()
.previewDevice(PreviewDevice(rawValue: deviceName))
}
}
}
代码解析:
NavigationView
: 添加导航栏.navigationBarTitle(Text("Landmarks"))
:导航栏设置titleNavigationLink(destination: LandmarkDetail(landmark: landmark))
:点击子项跳转到详情页
预览效果如下:
6. 圆形图片页数据改为动态获取
import SwiftUI
struct CircleImage: View {
var image: Image
var body: some View {
image.clipShape(Circle())
.overlay(
Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
}
}
struct CircleImage_Previews: PreviewProvider {
static var previews: some View {
CircleImage(image: landmarkData[0].image)
}
}
预览效果:
7. 地图页面数据改为动态获取
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var coordinate: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapView(coordinate: landmarkData[0].locationCoordinate)
}
}
需要Live Preview才能看到地图:
8. 详情页数据改为动态获取
import SwiftUI
struct LandmarkDetail: View {
var landmark: Landmark
var body: some View {
VStack {
MapView(coordinate: landmark.locationCoordinate)
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
CircleImage(image: landmark.image)
.offset(x: 0, y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text(landmark.name)
.font(.title)
HStack(alignment: .top) {
Text(landmark.park)
.font(.subheadline)
Spacer()
Text(landmark.state)
.font(.subheadline)
}
}
.padding()
Spacer()
}
.navigationBarTitle(Text(landmark.name), displayMode: .inline)
}
}
struct LandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
LandmarkDetail(landmark: landmarkData[0])
}
}
预览效果:
9. SceneDelegate初始页面更新
变动的函数如下:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Use a UIHostingController as window root view controller
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: LandmarkList())
self.window = window
window.makeKeyAndVisible()
}
}
10. 还可以设置device为iPad预览
参考
https://developer.apple.com/tutorials/swiftui/building-lists-and-navigation
https://developer.apple.com/tutorials/swiftui/tutorials