本教程将给你一个机会应用你已经学到的许多关于 SwiftUI 的知识,并且通过少量的努力,将 Landmarks 应用迁移到 watchOS。
你将首先向项目中添加一个 watchOS 目标,然后复制你在 iOS 应用中创建的共享数据和视图。在所有资源就位后,你将自定义 SwiftUI 视图以在 watchOS 上显示详细信息和列表视图。
请按照步骤构建此项目,或下载已完成的项目以自行探索。
第 1 节
添加一个 watchOS 目标 要创建一个 watchOS 应用,首先向项目中添加一个 watchOS 目标。Xcode 会将 watchOS 应用的文件夹和文件添加到项目中,并添加构建和运行应用所需的方案。
步骤 1
选择文件 > 新建 > 目标。当模板窗口出现时,选择 watchOS 标签页,选择应用模板并点击下一步。
该模板将一个新的 watchOS 应用添加到您的项目中。
第 2 步
在表单中,输入 WatchLandmarks 作为产品名称。选择现有 iOS 应用的 Watch App,然后点击完成。
第 3 步
当 Xcode 提示您激活 WatchLandmarks Watch App 方案时,请点击激活。
这样您就可以开始使用新的目标了。
第 4 步
在 watchOS 模拟器下拉菜单中选择 Apple Watch Series 9 (45mm) 设备。
第 5 步
选择 WatchLandmarks Watch App 目标,并导航到目标的常规选项卡;选择支持在不安装 iOS 应用的情况下运行复选框。
第 2 节
在目标之间共享文件 watchOS 目标设置后,您需要从 iOS 目标共享一些资源。您将重用 Landmark 应用的数据模型、一些资源文件,以及两个平台都可以在不修改的情况下显示的任何视图。
首先,删除 watchOS 应用的入口点。你不需要它,因为你会使用在 LandmarksApp 中定义的入口点代替。
步骤 1
In the Project navigator, delete the WatchLandmarksApp file in the WatchLandmarks Watch App folder; When asked, choose Move to trash.
接下来选择所有文件,包括应用的入口点,这些文件可以由 watchOS 目标与现有的 iOS 目标共享。
第 2 步
在项目导航器中,Command-click 选择以下文件: LandmarksApp , LandmarkList , LandmarkRow , CircleImage , MapView 。
这些中的第一个是共享的应用定义。其他的是该应用可以在 watchOS 上显示的视图,无需进行更改。
第 3 步
继续 Command-click 添加以下模型文件: ModelData , Landmark , Hike , Profile 。
这些项目定义了应用程序的数据模型。你不会使用模型的所有方面,但你需要所有文件才能成功编译应用程序。
第 4 步
完成 Command-click 操作以添加由模型加载的资源文件: landmarkData , hikeData 和 Assets 。
第 5 步
在文件检视器中,在目标成员资格部分选择 WatchLandmarks Watch App 复选框。
这样可以在您的 watchOS 应用中使用您在上一步中选择的符号。
最后,添加一个与你已经有的 iOS 应用图标匹配的 watchOS 应用图标。
第 6 步
选择 WatchLandmarks 监控应用文件夹中的 Assets 文件,导航到空的 AppIcon 项。
第 7 步
将下载的项目中 Resources 文件夹里的单个 png 文件拖放到现有的空 AppIcon 集中。
之后,当你创建一个通知时,系统会展示你应用的图标以帮助识别通知的来源。
第三部分
创建详细视图 现在 iOS 目标资源已经就绪,可以开始处理手表应用了,你需要创建一个特定于手表的视图来显示地标详情。为了测试详细视图,你需要为最大的和最小的手表尺寸创建自定义预览,并对圆圈视图进行一些修改,以便所有内容都能适应手表表盘。
步骤 1
在 WatchLandmarks Watch App 文件夹中添加一个名为 LandmarkDetail.swift 的自定义视图
此文件与 iOS 项目中同名的文件通过目标成员身份来区分——它仅适用于 WatchLandmarks 表盘应用目标。
第 2 步
将 modelData 、 landmark 和 landmarkIndex 属性添加到新的 LandmarkDetail 结构中。
这些与你在处理用户输入时添加的属性完全相同。
import SwiftUI
struct LandmarkDetail: View {
@Environment(ModelData.self) var modelData
var landmark: Landmark
var landmarkIndex: Int {
modelData.landmarks.firstIndex(where: { $0.id == landmark.id })!
}
var body: some View {
Text("Hello, World!")
}
}
#Preview {
LandmarkDetail()
}
第 3 步
在预览中,创建模型数据的实例,并使用该实例将地标对象传递给 LandmarkDetail 结构的初始化器。还需要设置视图的环境。
第 4 步
将设备选择器设置为显示大型手表的预览。
第 5 步
从 CircleImage 方法返回一个 body() 视图。
这里您重用了来自 iOS 项目的 CircleImage 视图。因为您创建了一个可调整大小的图像,所以对 scaledToFill() 的调用会调整圆的大小,使其填满整个显示区域。
第 6 步
将设备选择器更改为预览小型手表。
通过针对最大的和最小的手表表盘进行测试,您可以看到您的应用如何适应显示。如往常一样,您应该在所有支持的设备尺寸上测试您的用户界面。
第 7 步
将圆图嵌入到 VStack 中。在图片下方显示地标名称及其信息。
如您所见,信息无法完全显示在手表屏幕上,但可以通过将 VStack 放置在滚动视图中来解决这个问题。
第 8 步
将垂直堆叠包裹在滚动视图中。
This turns on 视图滚动,但会产生另一个问题:圆图现在会扩展为全尺寸,并且会调整其他 UI 元素以匹配图片尺寸。你需要调整圆图的大小,以便仅显示圆和地标名称。
第 9 步
将 scaleToFill() 更改为 scaleToFit() 并添加填充。
这将圆图缩放以匹配显示屏的宽度,并确保地标名称可以在圆图下方显示。
第 10 步
在分隔符后添加 MapView 。
地图显示在屏幕外,但您可以滚动查看。
第 11 步
为返回按钮添加一个标题。
这将返回按钮的文本设置为“地标”。
第 4 节
添加地标列表 The LandmarkList 你为 iOS 创建的也适用于你的手表应用,并且在编译为 watchOS 时会自动导航到你刚刚创建的手表专用详细视图。接下来,你将连接列表到手表的 ContentView ,使其成为手表应用的顶级视图。
步骤 1
Select ContentView 在 WatchLandmarks Watch App 文件夹中。
像 LandmarkDetail 一样,watchOS 目标的 content view 名称与 iOS 目标的名称相同。保持名称和接口一致使得在不同目标之间共享文件变得很容易。
The watchOS 应用的根视图显示默认的“Hello, World!”消息。
第 2 步
修改 ContentView ,使其显示列表视图。
请确保将模型数据作为环境提供给预览。 LandmarksApp 已经在应用程序级别在运行时提供了这一点,就像它为 iOS 做的那样,但你还需要为任何需要这些数据的预览提供它们。
第 3 步
确保您在实时预览中查看应用程序的行为。
第 5 节
创建自定义通知界面 您的 Landmarks for watchOS 版本即将完成。在本节中,您将创建一个通知界面,当您接近您喜欢的地点之一时,该界面会显示地标信息。
本节仅介绍接收到通知后如何显示通知,不描述如何设置或发送通知。
步骤 1
在 WatchLandmarks Watch App 文件夹中添加一个名为 NotificationView.swift 的新自定义视图,并创建一个显示地标信息、标题和消息的视图。
第 2 步
添加一个预览,设置通知视图的标题、消息和地标属性。
当提供数据时,这将显示通知视图的预览。由于任何通知值都可以是 nil ,因此在未提供数据时保持通知视图的默认预览是有用的。
第 3 步
创建一个新的 Swift 文件,命名为 NotificationController.swift ,并在其中添加一个包含 landmark 、 title 和 message 属性的 hosting 控制器结构。
这些属性存储有关入站通知的值。
import WatchKit
import SwiftUI
import UserNotifications
class NotificationController: WKUserNotificationHostingController<NotificationView> {
var landmark: Landmark?
var title: String?
var message: String?
override var body: NotificationView {
NotificationView()
}
}
第 4 步
更新 body() 方法以使用这些属性。
该方法实例化了您之前创建的通知视图。
import WatchKit
import SwiftUI
import UserNotifications
class NotificationController: WKUserNotificationHostingController<NotificationView> {
var landmark: Landmark?
var title: String?
var message: String?
override var body: NotificationView {
NotificationView(
title: title,
message: message,
landmark: landmark
)
}
}
第 5 步
定义 landmarkIndexKey 。
您使用此密钥从通知中提取地标索引。
import WatchKit
import SwiftUI
import UserNotifications
class NotificationController: WKUserNotificationHostingController<NotificationView> {
var landmark: Landmark?
var title: String?
var message: String?
let landmarkIndexKey = "landmarkIndex"
override var body: NotificationView {
NotificationView(
title: title,
message: message,
landmark: landmark
)
}
}
第 6 步
将 didReceive(_😃 方法添加以解析通知中的数据。
该方法会更新控制器的属性。调用此方法后,系统会撤销控制器的 body 属性,从而更新您的通知视图。然后系统会在 Apple Watch 上显示通知。
import WatchKit
import SwiftUI
import UserNotifications
class NotificationController: WKUserNotificationHostingController<NotificationView> {
var landmark: Landmark?
var title: String?
var message: String?
let landmarkIndexKey = "landmarkIndex"
override var body: NotificationView {
NotificationView(
title: title,
message: message,
landmark: landmark
)
}
override func didReceive(_ notification: UNNotification) {
let modelData = ModelData()
let notificationData =
notification.request.content.userInfo as? [String: Any]
let aps = notificationData?["aps"] as? [String: Any]
let alert = aps?["alert"] as? [String: Any]
title = alert?["title"] as? String
message = alert?["body"] as? String
if let index = notificationData?[landmarkIndexKey] as? Int {
landmark = modelData.landmarks[index]
}
}
}
当 Apple Watch 收到通知时,它会在你的应用中查找与该通知类别相关的场景。
第 7 步
去 LandmarksApp 并使用 LandmarkNear 分类添加一个 WKNotificationScene 。
场景仅适用于 watchOS,因此需要添加条件编译。
import SwiftUI
@main
struct LandmarksApp: App {
@State private var modelData = ModelData()
var body: some Scene {
WindowGroup {
ContentView()
.environment(modelData)
}
#if os(watchOS)
WKNotificationScene(controller: NotificationController.self, category: "LandmarkNear")
#endif
}
}
在您的应用可以显示提示之前,需要请求权限以执行此操作。
第 8 步
前往 ContentView 并请求授权以启用通知中心的通知。
您使用一个异步任务修改器来在 SwiftUI 调用内容视图首次出现时发出请求。
import SwiftUI
import UserNotifications
struct ContentView: View {
var body: some View {
LandmarkList()
.task {
let center = UNUserNotificationCenter.current()
_ = try? await center.requestAuthorization(
options: [.alert, .sound, .badge]
)
}
}
}
#Preview {
ContentView()
.environment(ModelData())
}
配置测试负载以使用 LandmarkNear 类别,并传递通知控制器所需的數據。
第 9 步
在 WatchLandmarks Watch App 文件夹中添加一个新的 Notification Simulation File,名称为 PushNotificationPayload.apns 。
不要将 PushNotificationPayload 文件添加到任何目标中,因为它不属于该应用。
第 10 步
更新 title , body , category , landmarkIndex 和 Simulator Target Bundle 属性。请确保将 category 设置为 LandmarkNear 。你还需要删除任何在教程中未使用的键,例如 subtitle 、 WatchKit Simulator Actions 和 customKey 。
payload 文件模拟了从您的服务器发送到远程通知的数据。
{
"aps": {
"alert": {
"title": "Silver Salmon Creek",
"body": "You are within 5 miles of Silver Salmon Creek."
},
"category": "LandmarkNear",
"thread-id": "5280"
},
"landmarkIndex": 1,
"Simulator Target Bundle": "com.example.apple-samplecode.Landmarks.watchkitapp"
}
第 11 步
在模拟器上构建并运行 WatchLandmarks Watch App 方案。
第一次运行应用时,系统会请求发送通知的权限。请选择允许。
第 12 步
授權後,使用 Xcode 停止應用,然後拖動 PushNotificationPayload 文件到腕表表盤上。
The Simulator 显示一个可滚动的通知,其中包括:应用程序的图标以帮助识别 Landmarks 应用作为发送者,通知视图,以及通知操作的按钮。