前言
一般老项目都是基于 UIKit 的,跟着 SwiftUI 越来越老练,未来的趋势会趋向于运用 SwiftUI 来进行开,所以,项目的逐渐迁移至 SwiftUI 也变得有必要起来。这篇文章会展现怎么在 UIKit 项目中,接入 SwiftUI。而 SwiftUI 在有些地方支持的还不是特别好,所以或许会有需求在 SwiftUI 中再去引如 UIKit 去解决现在 SwiftUI 欠好解决的问题,比方富文本,所以也会介绍在 SwiftUI 中怎么去加载 UIKit 的视图。
UIKit 接入 SwiftUI
1.Push 到一个 SwiftUI 视图
假设有一个 UIKit 的 ViewControllerA
,然后咱们新建一个 SwiftUI 的 SwiftUIViewB
,然后从 A push
到 B。
首要在这儿新建一个 SwiftUIViewB
。
然后,给 A 增加一个按钮,创建的代码就不写了,响应的办法为:
@IBAction func pushAction(_ sender: UIButton) {
let swiftUIViewController = UIHostingController(rootView: SwiftUIViewB())
navigationController?.pushViewController(swiftUIViewController, animated: true)
}
UIHostingController
是继承自 UIViewController
的,其实便是运用 UIHostingController
将 SwiftUIViewB
给包裹起来,类似于 UIKit 的 UIViewController
-> UIVIew
的方式,将 SwiftUI 的视图,当成 UIViewController
的 UIView
一样来进行运用。
作用如下:
2. 在 UIKit 视图中嵌套一个 SwiftUI 视图
改一下 SwiftUIViewB
的代码:
struct SwiftUIViewB: View {
var body: some View {
ZStack {
Color.cyan
Text("Hello, World!")
.foregroundColor(Color.white)
}
}
}
然后在 A 中:
override func viewDidLoad() {
super.viewDidLoad()
let sView = SwiftUIViewB()
let hostingController = UIHostingController(rootView: sView)
self.addChild(hostingController)
self.view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
hostingController.view.snp.makeConstraints({ make in
make.centerX.equalTo(view)
make.centerY.equalTo(view).offset(100)
make.width.equalTo(200)
make.height.equalTo(100)
})
}
一样的,借助于 UIHostingController
,将 SwiftUI 的视图当做一个 UIKit 的 UIView
来运用。
作用如下:
3.值传递
来测验一下改动 SwiftUI 视图的背景色,还是 A 和 B,咱们需求在 A 中去改动 B 的背景色。
首要,在 SwiftUIViewB
中新增一个 Color
特点:
struct SwiftUIViewB: View {
var bColor: Color
var body: some View {
ZStack {
bColor
Text("SwiftUI Screen")
}
}
}
然后咱们构建一个属于 SwiftUIViewB
的控制器来办理它:
class SwiftUIViewBController: UIViewController {
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
hostingController = UIHostingController(rootView: SwiftUIViewB(bColor: bColor))
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad(
self.addChild(hostingController)
self.view.addSubview(hostingController.view)
hostingController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
}
var bColor: Color = .white {
didSet {
update()
}
}
let hostingController: UIHostingController<SwiftUIViewB>
func update() {
hostingController.rootView = SwiftUIViewB(bColor: bColor)
}
}
然后在 A 中:
let hostingController = SwiftUIViewBController()
hostingController.bColor = .red
navigationController?.pushViewController(hostingController, animated: true)
4.监听值更改并主动改写 UI
在第三点中,咱们是在 bColor
的 didSet
办法中,手动去改写 UI。SwiftUI 提供了当值发生改动时,UI 主动改写的方式,通过 ObservableObject
,当然不止一种,咱们这儿先只说这种。
加一个自定义的数据:
class SwiftUIViewBConfig: ObservableObject {
@Published var textColor: Color
init (textColor: Color) {
self.textColor = textColor
}
}
然后改动一下 SwiftUIViewB
的代码,让它来运用这个数据:
struct SwiftUIViewB: View {
var config: SwiftUIViewBConfig
var body: some View {
ZStack {
Text("SwiftUI Screen")
.foregroundColor(config.textColor)
}
}
}
然后在 A 控制器中进行运用,简单点直接运用 UIHostingController
。
class ViewController: UIViewController {
let swiftUIBViewConfig = SwiftUIViewBConfig(textColor: .black)
@IBAction func pushAction(_ sender: UIButton) {
let hostingController = UIHostingController(rootView: SwiftUIViewB(config: swiftUIBViewConfig))
navigationController?.pushViewController(hostingController, animated: true)
}
@IBAction func changeColorAction(_ sender: UIButton) {
swiftUIBViewConfig.textColor = .blue
}
}
B 中的文字颜色本来是黑色,在 A 中滴点击按钮,将 config.textColor
改成蓝色,看一下作用:
SwiftUI 接入 UIKit
其实 SwiftUI 的支持还没有 UIKit 那么完善,而且最低到 13 系统,很多 SwiftUI 的特性其实支持的欠好,所以遇到实在是难搞定的,主张桥接 UIKit,或许直接运用 UIKit 来完成。
要桥接也很简单,使 UIViewRepresentable
协议将 UIView
包装一下,然后就能够在 SwiftUI 中运用了,怎么需求桥接 UIViewController
,也有对应的 UIViewControllerRepresentable
协议。
UIViewRepresentable
这个协议有两个必须完成的办法,便是 makeUIView
和 updateUIView
。
public protocol UIViewRepresentable : View where Self.Body == Never {
func makeUIView(context: Self.Context) -> Self.UIViewType
func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)
}
举个简单的比如,桥接一个 UIView
并改动它的背景颜色。
首要,先处理好需求桥接的 UIView
:
struct UIKitView: UIViewRepresentable {
@Binding var bColor: UIColor
func makeUIView(context: UIViewRepresentableContext<UIKitView>) -> UIView {
let view = UIView()
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<UIKitView>) {
uiView.backgroundColor = bColor
}
}
然后在 SwiftUI 中运用它:
struct SwiftUIView: View {
@State var kitViewColor: UIColor = .blue
var body: some View {
VStack {
UIKitView(bColor: $kitViewColor)
.frame(width: 100, height: 100)
Button {
kitViewColor = .red
} label: {
Text("change color")
}
}
}
}
看一下作用:
-
makeUIView
办法在其生命周期只会调用一次,在这个办法中回来一个你要在 SwiftUI 中体现的UIView
。 -
updateUIView
办法会在UIView
的状况发生变化时被调用,在生命周期内会被调用次。
在这个比如中,运用 @Binding
包装特点将 SwiftUI
中的 State
和 UIKit 的 UIKitView
绑定在一起,当 state
发生变化,就会触发 updateUIView
办法,去改动 UIKitView
的背景颜色。
现在 bColor
是从 SwiftUI 中传递到 UIKit 的,可是假如需求从 UIKit 传值到 SwiftUI 中又该怎么完成呢,答案是运用 Coordinator
。
SwiftUI 从 UIKit 中获取值
假如你要桥接到 SwiftUI 中的不是一个 UIView
,而是一个 UITextField
,而你又刚好需求完成 UITextFiled
的代理,那么你该把这个 delegate
设置成谁呢?答案是 Coordinator
。
在 UIViewRepresentable
协议中,还有一个东西:
public protocol UIViewRepresentable : View where Self.Body == Never {
func makeCoordinator() -> Self.Coordinator
}
直接看怎么运用:
struct CustomTextFiled: UIViewRepresentable {
@Binding var text: String
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.backgroundColor = .cyan
textField.frame = CGRect(x: 50, y: 28, width: 200, height: 44)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
}
}
然后在 SwiftUI 中运用:
struct SwiftUIView: View {
@State var kitViewText: String = "123"
var body: some View {
VStack {
CustomTextFiled(text: $kitViewText)
.frame(width: 300, height: 44)
Text(kitViewText)
}
}
}
上述代码中,当 UITextFiledDelegate
的 textFielDidChangeSelection
办法触发,coordinator
或更新 text
,触发 updateUIView
,最终更新 UITextField
。
小结
UIKit 和 SwiftUI 的相互桥接,了解之后其实并不麻烦,当然 SwiftUI 有自己的学习曲线,面向未来编程的话,主张假如仅支持 iOS13 后的 App,能够考虑运用 SwiftUI 来完成一些简单页面了。