在设置好基本的地标概要视图后,您需要提供一种方式让用户查看所有地标列表,并查看每个地点的详细信息。
您将创建可以显示任何地标信息的视图,并动态生成一个滚动列表,用户可以点击该列表查看地标详情视图。为了进一步调整用户界面,您将使用 Xcode 在不同设备尺寸下预览渲染。
下载项目文件以开始构建此项目,并按照以下步骤操作。
第 1 节
创建一个里程碑模型 在第一个教程中,您将所有自定义视图中的信息硬编码。在这里,您将创建一个模型来存储可以传递给视图的数据。
使用上一个教程完成的项目以及本教程项目文件中提供的资源来开始。
步骤 1
将下载文件夹中资源文件夹里的 landmarkData.json 拖放到项目导航栏中;在出现的对话框中,选择“如果需要则复制项目项”和“地标”目标,然后点击完成。
你将在本教程的其余部分以及后续教程中使用这些示例数据。
第 2 步
选择文件 > 新建 > 文件以在项目中创建一个新的 Swift 文件,并将其命名为 Landmark.swift 。
第 3 步
定义一个 Landmark 结构,包含一些与 landmarkData 数据文件中的键名匹配的属性。
添加 Codable 符合性使得结构与数据文件之间的数据移动更加容易。你将在本节稍后依赖 Codable 协议中的 Decodable 组件从文件中读取数据。
在接下来的几步中,你将为每个地标建模其关联的图像。
第 4 步
将项目文件中的资源文件夹中的 JPG 文件拖动到你的项目的资源库中。Xcode 为每个图像创建一个新的图像集。
新图片加入了你在上一个教程中添加的海龟岩图片。
第 5 步
向其添加一个 imageName 属性以读取图片名称,并添加一个计算属性 image 以从资源库加载图片。
你将属性设为私有,因为使用 Landmarks 结构的用户只关心图片本身。
接下来,你将管理地标的位置信息。
第 6 步
在结构中添加一个 coordinates 属性,使用一个嵌套的 Coordinates 类型来反映 JSON 数据结构中的存储。
您将此属性标记为私有,因为您将在下一步中仅使用它来创建一个公共计算属性。
第 7 步
计算一个对于使用 MapKit 框架有用的 locationCoordinate 属性。
最后,你将创建一个从文件初始化的数组。
第 8 步
在项目中创建一个新的 Swift 文件,并命名为 ModelData.swift 。
第 9 步
创建一个 load(_😃 方法,从应用的主要包中根据给定的名称获取 JSON 数据。
The load 方法依赖于返回类型符合 Decodable 协议,这是 Codable 协议的一个组成部分。
第 10 步
创建一个地标数组,并从 landmarkData.json 初始化。
在继续之前,请将相关的文件分组,以便更容易管理不断增长的项目。
第 11 步
将 ContentView 、 CircleImage 和 MapView 放入 Views 组,将 landmarkData 放入 Resources 组,将 Landmark 和 ModelData 放入 Model 组。
提示 您可以通过选择要添加到组中的项,然后在 Xcode 菜单中选择文件 > 新建 > 从选择创建组来创建现有项的组。
第 2 节
创建行视图 在本教程中,您将构建的第一个视图是用于显示每个地标详细信息的行视图。此行视图在显示的地标属性中存储信息,因此一个视图可以显示任何地标。稍后,您将把多个行组合成地标列表。
步骤 1
在 Views 组中创建一个新的 SwiftUI 视图,命名为 LandmarkRow.swift 。
第 2 步
将 landmark 作为 LandmarkRow 的存储属性添加。
当你添加 landmark 属性时,画布中的预览会停止工作,因为 LandmarkRow 类型在初始化时需要一个地标实例。
为了修复预览,你需要修改预览的视图实例化。
第 3 步
在预览宏中,将 landmark 参数添加到 LandmarkRow 初始化器中,并指定 landmarks 数组的第一个元素。
预览显示文本,“Hello, World!”。
修好之后,你可以构建这一行的布局。
第 4 步
将现有的文本视图嵌入到 HStack 中。
第 5 步
修改文本视图以使用 landmark 属性的 name 。
第 6 步
在文本视图之前添加一个图片,之后添加一个间隔。
第三部分
自定义行预览
Xcode 会自动识别你在视图源文件中使用预览宏声明的任何预览。
画布一次只显示一个预览,但你可以定义多个预览并在画布中选择它们。或者,你可以将视图分组以创建一个包含多个视图版本的单个预览。
步骤 1
添加一个使用 landmarks 数组中第二个元素的第二个预览宏。
添加预览使你可以查看你的视图在不同数据下的行为。
第 2 步
使用画布上的控件选择第二个预览。
默认情况下,画布会使用预览出现的行号对其进行标记。
预览宏可以接受一个可选的 name 参数,您可以使用该参数对预览进行标记。
第 3 步
为每个预览指定一个名称,以便区分它们。
第 4 步
使用新标签在两个预览之间导航。再次尝试更改选中的预览。
如果您想并排预览视图的不同版本,可以将它们组合到一个单一的集合视图中。
第 5 步
删除第二个预览,并将两行的版本包装在 Group 中。
Group 是一个用于分组视图内容的容器。Xcode 将组的子视图堆叠在一个预览中显示在画布上。
你在预览中编写的代码只会改变 Xcode 在画布中显示的内容。
第 4 节
创建地标列表 当你使用 SwiftUI 的 List 类型时,你可以显示一个平台特定的视图列表。列表中的元素可以是静态的,比如你到目前为止创建的堆栈的子视图,也可以是动态生成的。甚至可以混合使用静态和动态生成的视图。
步骤 1
在 Views 组中创建一个新的 SwiftUI 视图,命名为 LandmarkList.swift 。
第 2 步
用 List 替换默认的 Text 视图,并提供 LandmarkRow 实例,将前两个地标作为列表的子项。
预览显示了以适用于 iOS 的列表样式渲染的两个地标。
第 5 节
使列表动态化
与其逐个指定列表的元素,你可以直接从集合生成行。
你可以通过传递你的数据集合和一个闭包来创建一个显示集合元素的列表,该闭包为集合中的每个元素提供一个视图。列表通过使用提供的闭包将集合中的每个元素转换为子视图。
步骤 1
移除两个静态地标行,而是将模型数据的 landmarks 数组传递给 List 初始化器。
列表与可识别的数据一起工作。你可以通过两种方式使你的数据可识别:与数据一起传递一个唯一标识每个元素的属性的键路径,或者让你的数据类型遵循 Identifiable 协议。
第 2 步
通过从闭包返回一个 LandmarkRow 来完成动态生成的列表。
这会在 landmarks 数组中的每个元素上创建一个 LandmarkRow 。
接下来,您将通过为 Landmark 类型添加 Identifiable 遵从性来简化 List 代码。
第 3 步
切换到 Landmark.swift 并声明符合 Identifiable 协议。
数据已经具有了 Landmark 协议所需的 id 属性;在读取数据时只需添加一个属性以进行解码。
第 4 步
切换回 LandmarkList.swift 并移除 id 参数。
从现在开始,你可以直接使用 Landmark 元素的集合。
第 6 节
设置列表与详情之间的导航 列表显示正常,但还不能点击单个地标以查看该地标详情页。
通过将列表嵌入到 NavigationSplitView 中,并将每一行嵌套在 NavigationLink 中以设置到目标视图的过渡,可以为列表添加导航功能。
使用上一个教程中创建的内容准备详情视图,并更新主内容视图以显示列表视图。
步骤 1
创建一个新的 SwiftUI 视图名为 LandmarkDetail.swift 。
第 2 步
将 body 属性中的内容从 ContentView 复制到 LandmarkDetail 。
第 3 步
将 ContentView 改为显示 LandmarkList 。
在接下来的几步中,您将添加列表和详细视图之间的导航。
第 4 步
将动态生成的地标列表嵌入到 NavigationSplitView 中。
导航分屏视图需要一个输入参数,该参数是分屏视图中用户选择后出现的视图的占位符。在 iPhone 上,分屏视图不需要占位符,但在 iPad 上,详细视图可以在用户选择之前出现,如本教程稍后部分所示。
第 5 步
在显示列表时,添加 navigationTitle(_😃 修改器以设置导航栏的标题。
第 6 步
在列表的闭包内,将返回的行包裹在 NavigationLink 中,并指定 LandmarkDetail 视图作为目标。
第 7 步
您可以直接在预览中尝试导航。点击一个地标以访问详情页。
第 7 节
将数据传递给子视图 LandmarkDetail 视图仍然使用硬编码的细节来显示其地标。就像 LandmarkRow 一样, LandmarkDetail 类型及其包含的视图需要使用 landmark 属性作为其数据的来源。
从子视图开始,您将转换 CircleImage 、 MapView 和 LandmarkDetail 以显示传入的数据,而不是为每一行硬编码。
步骤 1
在 CircleImage 文件中,向 CircleImage 结构添加一个存储的 image 属性。
在使用 SwiftUI 构建视图时,这是一个常见的模式。您的自定义视图通常会封装一系列特定视图的修饰符。
第 2 步
更新预览以传递海龟岩的图片。
尽管你已经修复了预览逻辑,但由于构建失败,预览无法更新。详细视图在实例化圆形图片时也需要一个输入参数。
第 3 步
在 MapView 文件中,向 coordinate 结构添加一个 MapView 属性,并更新预览以传递一个固定坐标。
这个更改还影响构建,因为详细视图包含一个地图视图,需要新的参数。你很快会修复详细视图。
第 4 步
将坐标传递给区域变量中的中心参数。
SwiftUI 会注意到此视图中的 coordinate 输入发生变化,并重新评估主体以更新视图。这反过来会使用新输入值重新计算 region 。
第 5 步
将地图的初始化器改为一个接受位置输入的初始化器,以便在值更改时更新。
这个新初始化器期望一个 Binding 到位置,这是一个双向连接到值。在这里使用 .constant() 绑定,因为 MapView 不需要检测有人通过地图更改位置。
第 6 步
在 LandmarkDetail 中,为 Landmark 类型添加一个 LandmarkDetail 属性。
第 7 步
在 LandmarkList 中,将当前地标传递给目的地 LandmarkDetail 。
第 8 步
在 LandmarkDetail 文件中,将所需数据传递给您的自定义类型。
在所有连接建立之后,预览将再次开始工作。
第 9 步
将容器从 VStack 更改为 ScrollView ,以便用户可以滚动查看描述性内容,并删除 Spacer ,因为您不再需要它。
第 10 步
最后,调用 navigationTitle(😃 修改器为显示详细视图时的导航栏添加标题,并调用 navigationBarTitleDisplayMode(😃 修改器使标题显示为内联。
导航更改仅在视图属于导航堆栈的一部分时才生效。
第 11 步
回到 LandmarkList 预览并验证导航列表时它是否显示正确的地标。
第 8 节
动态生成预览 接下来,你将为不同的设备配置渲染列表视图的预览。
默认情况下,预览会在活动方案中设备的大小下渲染。你可以通过更改目标或在画布中覆盖设备来为不同的设备渲染。你还可以探索其他预览变体,例如设备方向。
步骤 1
将设备选择器更改为使预览显示 iPad。
在纵向模式下,默认显示详细信息面板,并在工具栏中提供一个按钮以显示侧边栏。
第 2 步
点击工具栏按钮以显示侧边栏,并尝试导航到其中一个地标。
详细视图在侧边栏下更改为所选地标。侧边栏在您在详细视图中点击任何位置之前保持可见。
第 3 步
在画布中,选择设备设置并启用横向左方向。
在横向模式下, NavigationSplitView 显示侧边栏和详细窗格并排。
第 4 步
在设备设置中尝试不同的设备和配置,以查看在其他条件下您的视图会是什么样子。