watchOS 与 UIKit 交互

众所周知,Apple 推荐在 watchOS 上使用 SwiftUI 来构建用户界面。watchOS 的 Storyboard UI 构建方式早已在 watchOS 6.0 时弃用,而用 UIKit 构建则是完全不支持。

不过,watchOS 的 UI 底层其实完全是基于 UIKit 的。Apple 只是将其隐藏了起来不让我们用。我们可以通过一些隐藏的 API 来使用 UIKit。

使用 UIKit 相关的类

watchOS 上的 UIKit 与 iOS 是一样的,只是 Apple 不提供头文件。直接手动导入头文件即可使用。

导入相关头文件

Xcode.app 中包含了所有平台 Framework 的官方头文件,直接将 iOS 的相关头文件全部拖到项目中即可。不过,要让这些头文件能够在 watchOS 上使用,还需要进行一些小调整。直接编译根据报错一个个修改就可以,这里也有一套我已修改好可供 watchOS 使用的头文件。

配置项目

将头文件添加到项目后,在 Build Settings 中,将头文件所在路径添加到 User Header Search Paths 中,然后在桥接头文件中导入即可:

1
#include "UIKit.h"

完成后,就可以在项目中直接使用 UIViewUIViewController 等类了。

构建基于 UIKit 的视图

我们可以同 iOS 上的 UIKit 一样创建视图:

1
2
3
let viewController = UIViewController()
let view = UIView()
viewController.view = view

此处创建的视图拥有 iOS 上的样式与行为。例如 UIButton 的样式明显能看出是 iOS 上的设计, UIScrollView 也不支持通过数码表冠滚动。

与 SwiftUI 交互

在 watchOS 上,果然还是 SwiftUI 才能发挥出 watchOS 的完整性能。我们可以像在 iOS 上一样在 SwiftUI 与 UIKit 间互相桥接。

SwiftUI 到 UIKit:_makeUIHostingView(_:)

SwiftUI 中为 watchOS 提供了一个隐藏的函数:_makeUIHostingView(_:)。这个函数实现了从 SwiftUI 视图到 UIView 的转换。

在 Swift 标准库(Standard Library)和 Apple Framework 中以下划线(_)开头的函数、结构等声明会被 SourceKit 隐藏。它们不会出现在自动补全里,但可以通过手动键入来使用。

1
2
3
4
5
6
7
8
9
10
11
struct MyView: View {
var body: some View {
Text("Hello, world!")
}
}

func getMyViewController() -> UIViewController {
let viewController = UIViewController()
viewController.view = _makeUIHostingView(MyView())
return viewController
}

UIKit 到 SwiftUI:_UIViewRepresentable

_UIViewRepresentable 是 SwiftUI 中为 watchOS 提供的一个隐藏的协议,比 iOS 上的 UIViewRepresentable 在前面多了一个下划线,使用起来几乎是一样的:

1
2
3
4
5
6
struct MyBridgingView: _UIViewRepresentable {
func makeUIView(context: Context) -> some NSObject {
return UIView()
}
func updateUIView(_ uiView: UIViewType, context: Context) {}
}