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)
    ])
}