UIKit和SwiftUI的交叉使用

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)
    ])
}
相关推荐
问道飞鱼23 分钟前
【移动端知识】移动端多 WebView 互访方案:Android、iOS 与鸿蒙实现
android·ios·harmonyos·多webview互访
mascon2 小时前
U3D打包IOS的自我总结
ios
名字不要太长 像我这样就好2 小时前
【iOS】继承链
macos·ios·cocoa
karshey3 小时前
【IOS webview】IOS13不支持svelte 样式嵌套
ios
潜龙95273 小时前
第4.3节 iOS App生成追溯关系
macos·ios·cocoa
游戏开发爱好者812 小时前
iOS App 电池消耗管理与优化 提升用户体验的完整指南
android·ios·小程序·https·uni-app·iphone·webview
神策技术社区18 小时前
iOS 全埋点点击事件采集白皮书
大数据·ios·app
J船长19 小时前
gRPC 与传统 REST API 的区别与实践详解
app
wuyoula19 小时前
iOS V2签名网站系统源码/IPA在线签名/全开源版本/亲测
ios
2501_9159184120 小时前
iOS 性能监控工具全解析 选择合适的调试方案提升 App 性能
android·ios·小程序·https·uni-app·iphone·webview