Skip to content

本教程将给你一个机会应用你已经学到的许多关于 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 应用作为发送者,通知视图,以及通知操作的按钮。