SwiftUI已经出来有一段时间了,目前有个新项目,在技术选型时考虑过使用纯SwiftUI,主要是想着在适配pad的UI能省事些,不过因为一些常用API的使用不成熟和未知的潜在风险,以及对iOS系统的要求,考虑后还是放弃了,继续使用UIKit。但是SwiftUI中的一些新特性和快速构建UI界面的声明式编程,还是让我有点不太甘心,这篇文章记录一些UIKit和SwiftUI交叉使用的一些方式。
SwiftUI中使用UIViewController
首先在SwiftUI的项目中,新建一个UIKit的UIViewController,里面随便放一个label,居中显示,证明它是个UIKit的viewController
swift
//
// MyViewController.swift
// UIKitInSwiftUI
//
// Created by luseike on 2023/12/19.
//
import UIKit
class MyViewController: UIViewController {
private var label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title1)
label.text = "Hello, UIKit"
label.textAlignment = .center
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
view.addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20)
])
}
}
在SwiftUI的环境中使用UIKit,需要一个方式来让SwiftUI理解UIKit的viewController,那就是 UIViewControllerRepresentable这哥们干的活了。UIViewControllerRepresentable是一个protocol,从它的声明中也可以感觉到
ini
protocol UIViewControllerRepresentable : View where Self.Body == Never
它大概是一个定义了如何让viewController被SwiftUI理解和使用的protocol。下一步就是在一个SwiftUI的文件中实现这个协议
swift
//
// MyView.swift
// UIKitInSwiftUI
//
// Created by luseike on 2023/12/19.
//
import SwiftUI
struct MyView: UIViewControllerRepresentable {
//typealias UIViewControllerType = MyViewController
func makeUIViewController(context: Context) -> MyViewController {
let vc = MyViewController()
return vc
}
func updateUIViewController(_ uiViewController: MyViewController, context: Context) {
//
}
}
首先UIViewControllerRepresentable需要知道用的是那个viewController,在makeUIViewController实例化并返回,对viewController的一些初始化配置也在这里。也有使用 typealias UIViewControllerType = MyViewController
的写法来直接告诉UIViewControllerRepresentable的方式,但因为实现了makeUIViewController,它会推断出使用的具体是哪个对象,所以来不来这一句无所谓了。其次是updateUIViewController方法,用来在SwiftUI发生重绘,需要update对应viewController的时候调用,demo中没啥可update的......
再下一步就可以直接使用MyView了,就像使用一个普通的SwiftUI view一样
swift
struct ContentView: View {
var body: some View {
VStack {
Text("MyView")
MyView()
.frame(height: 100)
}
}
}
SwiftUI中使用UIView
有了上面SwiftUI中使用UIViewController的三板斧,SwiftUI中使用UIView的套路就摸清了。还是定义一个UIView,在一个SwiftUI中实现一个协议,协议暴露需要使用的UIView对象和更新它的方法回调,直接上代码。
swift
//
// UIKitView.swift
// UIKitInSwiftUI
//
// Created by luseike on 2023/12/19.
//
import UIKit
class UIKitView: UIView {
lazy var label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title1)
label.text = "Hello UIKit View"
label.textAlignment = .center
return label
}()
init() {
super.init(frame: .zero)
backgroundColor = .systemMint
addSubview(label)
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
label.topAnchor.constraint(equalTo: topAnchor, constant: 20),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) undefined")
}
}
使用UIViewController时用到的协议是UIViewControllerRepresentable,使用UIView时用到的协议就是UIViewRepresentable,两个要实现的方法及其含义跟UIViewControllerRepresentable也是类似的,就不再赘述
swift
//
// RepresentedUIKitView.swift
// UIKitInSwiftUI
//
// Created by luseike on 2023/12/19.
//
import SwiftUI
struct RepresentedUIKitView: UIViewRepresentable {
func makeUIView(context: Context) -> UIKitView {
let view = UIKitView()
return view
}
func updateUIView(_ uiView: UIKitView, context: Context) {
}
}
具体使用方式也是一样
swift
var body: some View{
VStack{
Text("MyView")
RepresentedUIKitView().frame(height: 100)
}
}
UIKit中使用SwiftUI,当做一个UIViewController
想要把SwiftUI当做一个UIViewController来使用,需要 UIHostingController 这哥们的帮助。它是一个可以把SwiftUI的view包装成UIKit中的UIViewController的角色。首先还是新建一个SwiftUI文件,里面展示一个label是一个按钮,label证明自己确实是SwiftUI对象,按钮等会用来pop回去。
swift
SwiftUI
struct SwiftUIView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack {
Text("Hello, SwiftUI!")
.font(.title)
.buttonStyle(.borderedProminent)
Button("Dismiss"){
dismiss()
}
}.navigationTitle("SwiftUI")
}
}
#Preview {
SwiftUIView()
}
在UIKit中的使用
swift
@IBAction func didTapButton(_ sender: Any) {
let vc = UIHostingController(rootView: SwiftUIView())
navigationController?.pushViewController(vc, animated: true)
}
UIHostingController是一个转换SwiftUI和UIKit的利器,让我们可以使用任何SwiftUI里的新特性,很好的将这些SwiftUI的编程体感和用到的新特性适配到UIKit的环境中去,就问你心动不心动。
UIKit中使用SwiftUI,当做一个UIView
不像是在SwiftUI中使用UIViewController和UIView那样,有类似的protocol干这件事。UIKit中使用SwiftUI当做UIViewController有UIHostingController的帮忙,但是当UIView使用却没有类似的人帮忙了,需求我们自己多做件事儿。
其实还是用到了UIHostingController,需要先将SwiftUI当做一个UIViewController,然后再将controller当做一个child view添加到目标viewController中。在UIKit中可以将一个controller的view嵌入到另一个controller中,整个流程接上一步,把SwiftUI通过UIHostingController当做UIViewController后,获取controller的view即可,大概代码逻辑如下:
swift
@IBAction func didTapButton(_ sender: Any) {
let vc = UIHostingController(rootView: SwiftUIView())
// navigationController?.pushViewController(vc, animated: true)
// 这里拿到vc就不跳转了,直接获取它的view使用
let swiftUIView = vc.view!
swiftUIView.translatesAutoresizingMaskIntoConstraints = false
// 这里不要忘了先添加viewController
addChild(vc)
view.addSubview(swiftUIView)
NSLayoutConstraint.activate([
swiftUIView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
swiftUIView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}