创建列表和导航

地标详情页视图已经创建完成,我们需要提供一种方式让用户可以查看完整的地标列表,并且可以查看每一个地标的详情

下面会创建一个可以展示任何地标信息的视图,并动态生成一个可滚动列表,用户可以点击列表项去查看地标的详细信息。优化视图显示时,可以使用Xcode画布来渲染多个不同设备大小下的预览视图。

下载下面的工程文件,并跟着教程一步步学习构建列表和视图间导航


第一节 了解样本数据

前面的教程中,自定义视图所展示的信息都直接被写死在代码中,这篇教程中会学习给自定义视图传入样本数据进行展示

swiftui-building-list

步骤1 打开项目导航器,选择Models->Landmark.swift文件,这个文件中声明了需要在应用中展示一个地标所需要信息的结构化名称,并通过导入landmarkData.json文件中的数据,生成一个地标信息数组。

building list model

步骤2 在项目导航器中选择Resources->landmarkData.json,在后面的教程中我们都会使用这个样本数据文件

building list sample data

步骤3 注意,之前的ContentView视图,已经被改名为LandmarkDetail了,在本教程和后面的教程中,还会创建一些其它的视图

landmark detail

第二节 创建行视图

本教程中创建的第一个视图就是用来显示每个地标的行视图,行视图把地标的相关信息存储在一个属性中,一行就可以代表一个地标,稍后就会把这些行组合成为一个列表。

swiftui-building-list-landmark-row

步骤1 创建一个名为LandmarkRow.swift的SwiftUI视图

landmark row create

步骤2 如果预览视图没有出现,可以选择菜单编辑器->画布,打开画布,并点击Resume进行预览,或者使用Command+Option+Enter快捷键调出画面,再使用Command+Option+P快捷键开始预览模式

步骤3 添加landmark属性做为LandmarkRow视图的一个存储属性。当添加landmark属性后,预览视图可能会停止工作,因为LandmarkRow视图初始化时需要有一个landmark实例。要想修复预览视图,需要修改Preview Provider

步骤4LandmarkRow_Previews的静态属性previews中给LandmarkRow初始化器中传入landmark参数,这个参数使用landmarkData数组的第一个元素。预览视图当前显示Hello, World

landmark row layout

步骤5 在一个HStack中嵌入一个Text

步骤6 修改这个Text,让它使用landmark属性的name字段

步骤7Text视图前面添加一个图片视图,在Text视图后面添加Spacer视图

landmark layout 1

第三节 自定义行预览

Xcode的画布会自动识别当前代码编辑器中遵循PreviewProvider协议的类型,并将它们渲染并展示在画面上。一个视图预览提供者(preview provider)返回一个或多个视图,这些视图可以配置不同的大小和设备型号。

可以定制从preview provider中返回的视图被渲染在何种场景下。

row preivew

步骤1LandmarkRow_Previews中,把landmark参数更新为landmarkData数组的第二个元素,预览视图会立即刷新反映第二个元素的渲染情况

preivew row 2

步骤2 使用previewLayout(_:)修改器设置一个行视图在列表中显示的尺寸大小。可以使用Group的方式,返回多个不同场景下的预览视图

preview layout size

步骤3 把预览的行视图包裹在Group中,把之前的第一个行视图也加进去。Group是一个容器,它可以把视图内容组织起来,Xcode会把Group内的每个子视图当作画布内一个单独的预览视图处理

preview group size

步骤4 为了简化代码,可以把previewLayout(_:)这个修改器应用到外层的Group上,Group的每一个子视图会继承自己所处环境的配置。对preivew provider的修改只会影响预览画布的表现,对实际的应用不会产生影响。

preview group coniguration

第四节 创建地标列表

使用SwiftUI列表类型可以展示平台相关的列表视图。列表的元素可以是静态的,类似于栈内部的子视图,也可以是动态生成的视图,也可以混合动态和静态的视图。

landmark list

步骤1 创建SwiftUI视图,命名为LandmarkList.swift

步骤2List替换默认创建的Text,并将前两个LandmarkRow实例做为列表的子元素,预览视图中会以列表的形式展示出两个地标

landmark list file create

landmark list landmark list tow rows

第五节 创建动态列表

除了单独列出列表中的每个元素外,列表还可以从一个集合中动态的生成。

landmark list dynamic

创建列表时可以传入一个集合数据和一个闭包,闭包会针对每一个数据元素返回一个视图,这个视图就是列表的行视图。

步骤1 从列表中移除两个静态指定的行视图,给列表初始化器传入landmarkData数据,列表要配合可辨别的数据类型使用。想让数据变成可辨别的数据类型有两种方法:

  1. 传入一个keypath指定数据中哪一个字段用来唯一标识这个数据元素。

  2. 让数据遵循Identifiable协议

步骤2 在闭包中返回一个LandmarkRow视图,List初始化器中指定数据集合landmarkData和唯一标识符**keypath:**\.id,这样列表就会动态生成,如下图所示

keypath identifier list data

步骤3 切换到文件Landmark.swfit,声明Landmark类型遵循Identifiable协议,因为Landmark类型已经定义了id属性,正好满足Identifiable协议,所以不需要添加其它代码

identifiable data

步骤4 现在切换回文件LandmarkList.swift,移除keypath\.id,因为landmarkData数据集合的元素已经遵循了Identifiable协议,所以在列表初始化器中可以直接使用,不需要手动标明数据的唯一标识符了

identifiable list

第六节 设置从列表页到详情页的页面导航

地标列表可以正常渲染展示,但是列表的元素点击后没有反应,跳转不到地标详情页。现在就要给列表添加导航能力,把列表视图嵌套到NavigationView视图中,然后把列表的每一个行视图嵌套进NavigationLink视图中,就可以建立起从地标列表视图到地标详情页的跳转。

landmark list to detail

步骤1 把动态生成的列表视图嵌套进一个NavigationView视图中

embed in navigation view

步骤2 调用navigationBarTitle(_:)修改器设置地标列表显示时的导航条标题

landmark list navigation view

步骤3 在列表的闭包中,将每一个行元素包裹在NavigationLink中返回,并指定LandmarkDetail视图为目标视图

navigation link

步骤4 切换到实时预览模式下可以直接点击地标列表的任意一行,现在就可以跳转到地标详情页了。

list navigation

第七节 子视图传入数据

LandmarkDetail视图目前还是使用写死的数据进行展示,与LandmarkRow视图一样,LandmarkDetail视图及它内部的子视图也需要传入landmark数据,并使用它来进行实际的展示

LandmarkDetail的子视图(CircleImageMapView)开始,需要把它们都改造成为使用传入的数据进行展示,而不是在布局代码中写死数据展示

pass data

步骤1CircleImage.swift文件中,添加一个存储属性,命名为image。这是一种在构建SwiftUI视图中很常用的模式,常常会包裹或封装一些属性修改器。

circle image data

步骤2 更新CirleImage的预览结构体,并传入Turtle Rock这个图片进行预览

circle image preview

步骤3MapView.swift中添加一个coordinate属性,并使用这个属性来替换写死的经纬度坐标

map view data

步骤4 更新MapView的预览结构体,并传入每一个地标的经纬度数据

map view preview

步骤5LandmarkDetail.swift中添加landmark属性。

步骤6 更新LandmarkDetail预览结构体,并传入第一个地标的数据

步骤7 把对应子视图的数据传入

landmark detail

步骤8 最后调用navigationBarTitle(_:displayMode:)修改器为地标详情页展示时在导航条上设置一个标题

landmark detail preview

步骤9SceneDelegate.swift中把应用的根视图替换为LandmarkList。应用在模拟器中独立启动时使用SceneDelegate的根视图做为第一个展示的视图

scene delegate root view

步骤10LandmarkList.swift中,传入当前行的地标数据到地标详情页LandmarkDetail

landmark list data

步骤11 切换到实时预览模式下去查看从地标列表页对应的行跳转到对应地标详情页是否正常

landmark list preview

第八节 动态生成预览视图

dynamic preivew

接下来要在不同尺寸设备上展示不同的预览视图,默认情况下,预览视图会选择当前Scheme选中的设备尺寸进行渲染,可以使用previewDevice(_:)修改器来改变预览视图的设备

步骤1 改变当前预览列表,让它渲染在iPhone SE设备上。可以使用Xcode Scheme菜单上的设备名称来指定渲染设备。

iPhone SE Preview

步骤2 在列表的预览视图中,还可以把LandmarkList嵌套进入ForEach实例中,使用设备数组名作为数据。ForEach运算作用在集合类型的数据上,就和列表使用集合类型数据一样,可以在子视图使用的任何场景下使用ForEach,例如:stacklistgroup等。当元素数据是简单值类型时(例如字符串类型),可以使用\.self作为keypath去标识

preiview multiple device

步骤3 使用previewDisplayName(_:)修改器可以给预览视图添加设备标签

步骤4 可以在画布上多设置几个设备进行预览,比较不同设备下视图的展示情况

preivew multiple devices

检查是否理解

问题1 除了List外,下面哪种类型可以从集合数据中展示动态列表视图

  • Group
  • ForEach
  • UITableView

问题2 可以从遵循了Identifiable协议的集合数据创建列表视图。但如果集合数据不遵循Identifiable协议,还有什么办法可以创建列表视图?

  • 在集合数据上调用map(_:)方法
  • 在集合数据上调用sorted(by:)方法
  • List(_:id:)类型传入集合数据的同时,使用keypath指定一个唯一标识符字段

问题3 使用什么类型才能让列表的行实现点击跳转到其它视图页面?

  • NavigationLink
  • UITableViewDelegate
  • NavigationView

问题4 下面哪种方式不是用来设置预览设备的?

  • 改变活动scheme中选中的模拟器
  • 在画面设置中设置一个不同的预览设备
  • 使用previewDevice(_:)指定一个或多个预览设备
  • 连接开发机并点击设备预览按钮