SwiftUI 与 UIKit 混合开发

SwiftUI 中有很多官方的组件已经非常好用,但是在一些特殊的场景中还是需要使用 UIKit 的组件提供支持,这篇文章简单介绍一些如何让 SwiftUI 和 UIKit 的组件混合开发的方法。

在真正使用 SwiftUI 开发时,还是推荐尽量使用原有组件来完成功能。

文章简单记录如果使用,对于具体的业务来说,逻辑肯定是更加复杂的。

UIKit in SwiftUI

UIView

要在 SwiftUI 中使用 UIKit 的话,需要用 UIViewRepresentable 对 UIView 进行一层包装。

且 UIView 需要使用 typealias 指定一个需要包装的 UIView 类型,并实现两个方法:

  • func makeUIView(context: Self.Context) -> Self.UIViewType

创建一个 View 并返回。可在这里设置默认的一些属性及状态。

  • func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)

在 SwiftUI 中可改变的属性在这里进行设置及刷新 View 的状态。

需要暴露的、可设置的属性,就在 View 中声明一个属性,供 SwiftUI 使用时进行调整。

例子1。常规显示的 View。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Mark :- UIkit Define
struct AAView: UIViewRepresentable {
typealias UIViewType = UIProgressView

@Binding var progress: Float // SwiftUI 中设置

func makeUIView(context: Context) -> UIProgressView {
let view = UIProgressView()
return view
}

func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.progress = progress
}
}

// Mark :- SwiftUI
struct ContentView: View {
@State var progress: Float = 0.5

var body: some View {
AAView(progress: $progress)
}
}
例子2。包装的 View 中有 Target/Action 事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Mark :- UIkit Define
struct AASwitchView: UIViewRepresentable {
typealias UIViewType = UISwitch

@Binding var isOn: Bool

func makeUIView(context: Context) -> UISwitch {
let view = UISwitch()
view.addTarget(self, action: #selector(Coordinator.valueChanged(sender:)), for: .valueChanged)
return view
}

func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.isOn = isOn
}

func makeCoordinator() -> Coordinator {
Coordinator(self)
}

class Coordinator: NSObject {
var view: AASwitchView

init(_ view: AASwitchView) {
self.view = view
}

@objc func valueChanged(sender: UISwitch) {
view.isOn = sender.isOn
}
}
}

// Mark :- SwiftUI
struct ContentView: View {
@State var isOn = false

var body: some View {
AASwitchView(isOn: $isOn)
}
}

UIViewController

要在 SwiftUI 中使用 UIViewController 的话,需要用 UIViewControllerRepresentable 对 UIViewController 进行一层包装。

且 UIView 需要使用 typealias 指定一个需要包装的 UIView 类型,并实现两个方法:

  • func makeUIViewController(context: Self.Context) -> Self.UIViewControllerType

创建一个 UIViewController 并返回。可在这里设置默认的一些属性及状态。

  • func updateUIViewController(_ uiViewController: Self.UIViewControllerType, context: Self.Context)

在 SwiftUI 中可改变的属性在这里进行设置及刷新 UIViewController 的状态。

需要暴露的、可设置的属性,就在 View 中声明一个属性,供 SwiftUI 使用时进行调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct AANavViewController: UIViewControllerRepresentable {
typealias UIViewControllerType = UINavigationController

var root: UIViewController
var title: String

func makeUIViewController(context: Context) -> UINavigationController {
let vc = UINavigationController()
return vc
}

func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
uiViewController.topViewController?.title = title
}
}

struct ContentView: View {
var body: some View {
AANavViewController(root: UIViewController(), title: "Title")
}
}

SwiftUI in UIkit

SwiftUI 中的控件想在 UIKit 中使用的话相对来说比较简单。只需要通过 UIHostingController 包装一次即可。

1
let vc = UIHostingController(rootView: Text("Text"))

这边需要注意的是,取到的是一个 UIViewController 对象。