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)
    ])
}
相关推荐
️ 邪神12 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】文本点击事件
flutter·ios·鸿蒙·reactnative·anroid
️ 邪神13 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】文本Text显示
flutter·ios·鸿蒙·reactnative·anroid
今天也想MK代码14 小时前
基于ModelScope打造本地AI模型加速下载方案
ai·语言模型·swift·model·language model
袁代码14 小时前
Swift 开发教程系列 - 第11章:内存管理和 ARC(Automatic Reference Counting)
开发语言·ios·swift·ios开发
海绵不是宝宝81714 小时前
IOS开发之MapKit定位国内不准的问题
ios
那就可爱多一点点16 小时前
如何处理 iOS 客户端内 Webview H5 中后台播放的音视频问题
ios·音视频
crasowas16 小时前
iOS问题记录 - 503 Service Temporarily Unavailable
ios·fastlane
货拉拉技术18 小时前
货拉拉是如何实现symbolic demangle?
ios·性能优化
hairenjing112320 小时前
适用于 Windows 11/10 电脑 的 13 个最佳文件恢复软件
人工智能·windows·macos·ios·电脑·ipad
海绵不是宝宝8171 天前
IOS开发之AR问题汇总
ios·ar