「Apple Watch 应用开发系列」初识 Apple Watch App

「Apple Watch 应用开发系列」初识 Apple Watch App

初识 Apple Watch App

2015 年 4 月 24 日,Apple 发布了第一代 Apple Watch。 无论我们对 Apple Watch 看法如何,watchOS 肯定是我们要支持的 Apple 生态系统的一部分,确保我们的应用获得更大的曝光率。

在本章中,我们将创建一个 watchOS 应用程序,并将我们现有的 iOS 开发知识直接转移到 watchOS 上来。

Swift & SwiftUI

新项目

打开 Xcode 通过选择 File ▸ New ▸ Project 或使用快捷键 Shift-Command-N 创建一个新项目。 在我们选择项目类型的对话框窗口中,选择 watchOS 选项卡,然后选择 iOS App with Watch App 模板:

image

在开发支持 Apple Watch 的项目时,我们可以选择是否提供配套的 iOS 应用。 需要配套 iOS 应用会包含 iOS 项目。

接下来,我们输入 Product Name,例如 watchOS&SwiftUI,可以选中底部标题为 Include Notification Scene 的勾选框。在后面的章节中,我们将探索如何向 Apple Watch 发送推送通知。

image

Hello, World!

选择 Product ▸ Run 或使用 Command-R 来构建和运行应用程序,模拟器加载 watchOS 应用程序通常需要一些时间,所以如果应用程序没有立即启动。 稍等片刻,Apple Watch 模拟器将出现,我们会看到“Hello, World!” :

image

打开项目的 ContentView.swift。如果你曾经接触过 SwiftUI 项目,那么你现在可能会非常兴奋。 我们会看非常熟悉的 SwiftUI 代码!我们只需编写 SwiftUI 代码,就像编写 iOS 项目一样。

相对传统的 WatchKit 特定类仍然是支持的,但和 SwiftUI 相比,如果我们只需要针对 watchOS 6.0 及更高版本的设备,那么没有理由在新应用程序上使用传统的方案。

项目结构

Target

image

我们来看一下项目的 TARGETS,现在我们有三个 template:

  1. watchOS&SwiftUI 是我们项目的 Target。它充当项目的包装器,以便我们可以将 App 提交到 App Store。

  2. watchOS&SwiftUI WatchKit App 是构建 watchOS App 的 Target。 包含我们的 watchOS App 的 storyboard 和 storyboard 使用的 assets。

  3. watchOS&SwiftUI WatchKit Extension 是构建 watchOS extension 的 Target。 包含我们的 watchOS App 的代码。

我们很少与前两个 Target 互动。 当我们想到 Apple Watch App 开发时,一般指的是 WatchKit Extension。

Scheme

image

打开 scheme 选择器,除去 iOS App 以外,我们会看到三个默认 scheme,这些 scheme 不直接映射到三个 Target,它们控制我们在模拟器或物理设备上运行时希望执行的执行类型:

  1. watchOS&SwiftUI WatchKit App 是我们最常使用的方案。我们可以将此方案视为“运行我的应用程序”。当我们使用该方案构建和运行时,模拟器或物理设备将执行最终用户在启动我们的应用程序时看到的内容。

  2. watchOS&SwiftUI WatchKit App (Notification) 当我们在创建项目时选中包Include Notification Scene时,项目即创建该 scheme。可让我们直接向设备发送通知。如果我们选择该 scheme 并构建运行 App,将直接跳转到我们定义的 notification view。只有在测试本地或远程推送通知时我们才会使用此 scheme。

image

  1. watchOS&SwiftUI WatchKit App (Complication) 复杂功能是我们在手表表面看到的信息。例如计时器、仪表和活动环等。在调试手表应用程序的复杂功能时,我们将使用该 scheme。

添加 iOS App

如果我们需要同时开发 iOS App 和 watchOS App,请在创建项目时选择 iOS App with Watch App 模板。 虽然许多 watchOS App 需要一个配套的 iOS 项目,但 watchOS App 也可以在没有配套的 iOS App 的情况下运行。 默认情况下,Xcode 假定我们的 watchOS App 可以在没有安装配套 iOS App 的情况下运行。

如果我们的手表应用在没有相应 iOS 应用的情况下无法运行,我们需要进行一些配置更改:

image

  1. 选择项目导航中的项目;

  2. 选择 Extension target;

  3. 选择 General tab;

  4. 在 Deployment Info 部分,选中 Supports Running without iOS App Installation

如果我们忘记包含对 watchOS App 的支持,只需通过 File ▸ New ▸ Target 选择 Watch App 模板添加一个新 target。

我们无法将 watchOS 目标命名为与 iOS 目标相同。

应用命名

运行我们的 watchOS App 后,打开设置,选择 App View 并更改为 List View。然后返回主屏幕并向下滚动到 App 列表的底部,我们会看到 App 显示了它的名字:

image

image

image

默认情况下,Xcode 会将 WatchKit App 附加到我们 App 的名称中。

如果需要进行更改,选择 watchOS&SwiftUI WatchKit App Target,然后切换到 General Tab,只需通过编辑“Display Name”来输入要显示的名字即可。

image

构建并重新运行应用程序。 我们讲看到我们为 watchOS App 设置的新名称:

image

App 与 Extension

我们为 App 分配图标时,需要在以 App 结尾的目录中操作,例如 watchOS&SwiftUI WatchKit App/Assets.xcassets。但不要在该文件夹结构内执行任何其他操作。对于所有其他 asset 以及我们将执行的所有代码,我们将防治在 Extensions 目录中。

这是由于以前版本的 watchOS 工作方式不同。 此外,WWDC 22 在此处页做了更新。

欢迎参考:

幸运表情

我们将实现一个简单的 Watch App Demo “幸运表情”。

添加图标与图像

首先,将 AppIcon.appiconset 添加到 watchOS&SwiftUI WatchKit App 的 Assets 中。运行项目并切换到主屏幕查看 App,图标已经进行了替换:

image

image

接着,我们将 luck_icon.png 添加到 watchOS&SwiftUI WatchKit Extension 的 Assets 中。现在我们的应用程序可以使用该图像。

image

打开 watchOS&SwiftUI WatchKit ExtensionContentView.swift,将 body 的内容替换为以下代码,并更新预览,四叶草将展示。

Image("luck_icon")
    .resizable()
    .scaledToFit()
复制代码

image

添加幸运表情

我们在 watchOS&SwiftUI WatchKit Extension 中,新建一个 SwiftUI 文件并命名为 EmojiModel.swift,将内容进行替换:

import SwiftUI

@MainActor
class EmojiModel: ObservableObject {
    
    @Published var text = ""
    @Published var emoji = ""

    private let sentences = [
        (text: "Lucky animal", emoji: "🐷"),
        (text: "Lucky drink", emoji: "☕️"),
        (text: "Lucky sport", emoji: "🏊♀️"),
    ]
    
    private var index = 0

    init() {
        update()
    }
    
    private func update() {
        text = sentences[index].text
        emoji = sentences[index].emoji
    }

    func next() {
        index += 1
        if index == sentences.count {
            index = 0
        }
        update()
    }
}
复制代码

简单查看代码,符合 ObservableObject 协议的 EmojiModelMainActor 提供了一些 Published 包装的 textemoji,属性在发生改变时会通知自身的订阅者。

回到 ContentView.swift,在 ContentView 中添加一个新属性,ContentView 实例将持有一个 StateObject 包装的 model,使后续添加的控件可作为 model 的订阅者:

@StateObject private var model: EmojiModel = EmojiModel()
复制代码

.scaledToFit() 后添加代码:

.overlay {
    Text(model.emoji)
        .font(.title)
        .padding()
        .buttonStyle(.automatic)
}
复制代码

我们使用 .overlay 将一个视图叠加到另一个视图之上。这里将表情符号作为文本添加到四叶草之上。然后我们使用一些修饰符来更改字体大小和位置。更新 Canvas,我们会看到幸运表情:

image

我们使用 Command + TextText 放入 Vstack 中,并调整代码:

image

更新 Canvas 查看最新样式。

image

最后在 VStack 后添加以下代码来添加点击手势,并运行项目。

.onTapGesture {
    model.next()
 }
复制代码

image

image

image

现在,我嘛点击幸运表情和描述,将进行更换。

Swift & Storyboard

我们来了解下如何使用 WatchKit 开发 watchOS App。

WatchKit 框架包括用于控制我们在 Storyboard 中布局的用户界面元素。基于 Storyboard 的布局通常需要每个 scene 的 controller。每个 controller 可以管理一个或多个控件和子视图,例如表格、按钮、滑块和其他视觉元素。

因为使用 SwiftUI 构建的应用程序比在故事板中设计的应用程序具有更多的自由、强大和对用户界面的控制,所以在创建 watchOS 应用程序时强烈考虑使用 SwiftUI。相关详细信息请参阅:

相关背景

早期,在 WatchOS 时,负责逻辑部分的 WatchKit Extension 将随 iOS app 的主 target 被一同安装到 iPhone 中,而负责界面部分的 WatchKit App 将会在主程序安装后,由 iPhone 检测有没有配对的 Apple Watch,并提示安装到 Apple Watch 中。所以在实际使用时,所有的运算、逻辑以及控制实际上都是在 iPhone 中完成的。在需要界面刷新时,由 iPhone 向 Watch 发送指令进行描画并在手表盘面上显示。反过来,用户触摸手表交互时的信息也由手表传回给 iPhone 并进行处理。这个过程 WatchKit 会在幕后为我们完成,并不需要开发者操心。我们需要知道的就是,原则上来说,我们应该将界面相关的内容放在 Watch App 的 target 中,而将所有代码逻辑等放到 Extension 里。

以上在 WWDC19 时迎来了改变。watchOS 6 允许完全独立的应用程序和专为 Apple Watch 构建的应用程序,并将 App Store 引入 Apple Watch,从而实现全新级别的 watchOS 体验。所以现在 App Store 服务器将在 Apple Watch 上安装它需要安装的所有东西。这意味着 iOS 应用程序不再包含 watchOS App,watchOS App 不再计入我们的 iOS App 的蜂窝下载限制。

新项目

选择 iOS App with Watch App 模板后,我们输入 Product Name,例如 watchOS&Storyboard。这里,我们将 Interface 选择为 Storyboard

image

主要类

WKInterfaceController

WKInterfaceController 像是 WatchKit 中的 UIViewController。每个 WKInterfaceController 或者其子类对应手表上的一个整屏内容。但需要记住整个 WatchKit 是独立于 UIKit,WKInterfaceController 是一个直接继承自 NSObject 的类,没有像 UIKit 中 UIResponser 那样完备的功能。

image

在生命周期上 UIViewController 也简单很多。每个 WKInterfaceController 对象重要的生命周期方法有五个:

  1. 对象被初始化时的 awake(withContext:) 使用此方法完成界面的初始化。super 方法的实现什么都不做。

developer.apple.com/documentati…

在 iOS 8.2 之前,该 Api 为 init(withContext:) ,现已废弃。

stackoverflow.com/questions/2…

  1. 内容将于活动状态 willActivate()。该方法让我们知道界面将变为活动状态。使用该方法执行任何“最后一分钟”的任务,例如检查内容的更新。系统可能会提前调用此方法,以便我们有时间更新我们的内容。系统在 WatchKit Extension 的主线程上调用此方法。super 方法的实现什么都不做。

developer.apple.com/documentati…

  1. 内容现在在屏幕上 didAppear()。使用此方法配置动画或其他与界面相关的任务。系统在 WatchKit Extension 的主线程上调用此方法。super 方法的实现什么都不做。

developer.apple.com/documentati…

  1. 内容不再处于活动状态 didDeactivate()。使用此方法使计时器无效或保存任何尚未保存的与应用程序相关的状态信息。我们使用此方法执行的任何任务都应该很快完成。不活动的内容可能会在以后重新激活,也可能会被 deinit 分配。

在 didDeactivate() 中对视图属性进行设置是无效的,因为当前的 WKInterfaceController 已经非活跃

developer.apple.com/documentati…

  1. 内容现在不在屏幕上 willDisappear()。使用此方法可在停用之前停止动画或执行其他与界面相关的任务。系统在 WatchKit Extension 的主线程上调用此方法。super 方法的实现什么都不做。

developer.apple.com/documentati…

WKInterfaceObject 及其子类

WKInterfaceObject 提供 watchOSApp 中所有界面对象共有的信息的对象,即负责具体的界面对象的设置。包括 WKInterfaceLabelWKInterfaceDateWKInterfaceTimer 等。

developer.apple.com/documentati…

我们可能一开始会产生错觉,认为 WKInterfaceObject 应该对应 UIView,但实际上并非如此。

WKInterfaceObject 只是 WatchKit 的实际的 View 的一个在 Watch Extension 的代理,而非 View 本身。Watch App 中实际展现和渲染在屏幕上的 View 对于代码来说是非直接可见的,我们只能在 Extension target 中通过对应的代理对象对属性进行设置,然后在每个 run loop 刷新 UI 时由 WatchKit 进行界面刷新。反过来,手表中的实际的 view 想要将用户交互事件传递给 iPhone 也需要通过 WKInterfaceObject 代理进行。

watchOS App 采取的布局方式和 iOS App 完全不同。我们无法自由指定某个视图的具体坐标,也不能使用像 AutoLayout 这样的灵活的界面布局方案。我们只能在以“行”为基本单位的同时通过 group 来在行内进行“列”布局。

所有的 WKInterfaceObject 对象都必须由 Storyboard 进行添加,运行时我们无法再向界面上添加或者移除元素,我们只能通过设置 setHidden(_:) 来进行界面对象隐藏;此外,WKInterfaceObject 与布局相关的某些属性,比如行高行数等,不能够在运行时进行变更和设定。基本来说在运行时我们只能够改变视图的内容,以及通过隐藏某些视图元素来达到有限地改变布局。

幸运表情

我们将使用 Storyboard 实现一个简单的 Watch App Demo “幸运表情”。

添加图像

我们将 luck_icon.png 添加到 watchOS&Storyboard WatchKit App 的 Assets 中。

image

打开 watchOS&Storyboard WatchKit AppInterface.storyboard,点击右上角的“➕”,并将 Image 拖拽到 Interface Controller 上。

image

调整点击右侧“Show the Attributes inspector,并进行如下图所示调整。

image

运行程序,我们会看到我们的背景已经添加。

image

添加幸运表情

回到 Interface.storyboard,添加一个 Label、一个 Button,并进行相应调整。

image

image

image

将我们添加控件对象链接到 InterfaceController.swift 上:

image

image

image

image

接着,我们在 watchOS&Storyboard WatchKit Extension 中,新建一个 Swift 文件并命名为 EmojiModel.swift,将内容进行替换:

import Foundation

class EmojiModel {
    
    var text = ""
    var emoji = ""
    
    private let sentences = [
        (text: "Lucky animal", emoji: "🐷"),
        (text: "Lucky drink", emoji: "☕️"),
        (text: "Lucky sport", emoji: "🏊"),
    ]
    
    private var index = 0

    init() {
        update()
    }
    
    private func update() {
        text = sentences[index].text
        emoji = sentences[index].emoji
    }

    func next() {
        index += 1
        if index == sentences.count {
            index = 0
        }
        update()
    }
}
复制代码

并在 InterfaceController.swift 内添加代码:

private let emojiModel = EmojiModel()
    
override func awake(withContext context: Any?) {
    // Configure interface objects here.
    emoji.setText(emojiModel.emoji)
    text.setTitle(emojiModel.text)
}
复制代码

运行程序,我们会看到我们的 watchOS 已经有了数据。

image

接着,我们为 Button 添加 Action:

image

image

接着,我们调整 textAction() 的代码:

@IBAction func textAction() {
    emojiModel.next()
    emoji.setText(emojiModel.emoji)
    text.setTitle(emojiModel.text)
}
复制代码

运行我们的 watchOS App。现在,它与我们之前通过 SwiftUI 开发的项目基本一致:

image

image

image

参考

分类:
iOS
标签: