SwiftUI已经出来有一段时间了,目前有个新项目,在技术选型时考虑过运用纯SwiftUI,主要是想着在适配pad的UI能省劲些,不过由于一些常用API的运用不成熟和不知道的潜在风险,以及对iOS体系的要求,考虑后仍是放弃了,持续运用UIKit。可是SwiftUI中的一些新特性和快速构建UI界面的声明式编程,仍是让我有点不太甘愿,这篇文章记录一些UIKit和SwiftUI交叉运用的一些方法。
SwiftUI中运用UIViewController
首先在SwiftUI的项目中,新建一个UIKit的UIViewController,里边随便放一个label,居中显现,证明它是个UIKit的viewController
//
// 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,从它的声明中也能够感觉到
protocol UIViewControllerRepresentable : View where Self.Body == Never
它大约是一个定义了如何让viewController被SwiftUI理解和运用的protocol。下一步便是在一个SwiftUI的文件中完成这个协议
//
// 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相同
struct ContentView: View {
var body: some View {
VStack {
Text("MyView")
MyView()
.frame(height: 100)
}
}
}
SwiftUI中运用UIView
有了上面SwiftUI中运用UIViewController的三板斧,SwiftUI中运用UIView的套路就摸清了。仍是定义一个UIView,在一个SwiftUI中完成一个协议,协议暴露需求运用的UIView方针和更新它的方法回调,直接上代码。
//
// 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也是相似的,就不再赘述
//
// 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) {
}
}
详细运用方法也是相同
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回去。
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中的运用
@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即可,大约代码逻辑如下:
@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)
])
}