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)
    ])
}
相关推荐
Cedric_Anik12 小时前
iOS App的启动与优化
ios
席子哥哥的代码库17 小时前
自制简单的图片查看器(python)
开发语言·python·swift
leluckys1 天前
iOS之动态库和静态库的区别
ios
Black_Rock_br1 天前
iPhone 智能进化:Siri 调用 DeepSeek 大模型
ios·语言模型·iphone
猪萌萌1 天前
使用iOS个人声音与SoVITS训练个人AI语音(10分钟快速上手)
人工智能·ios·tts·文字转语音·sovits
二流小码农1 天前
鸿蒙开发:V2版本装饰器@Once
android·ios·harmonyos
打工人你好1 天前
Swift 的 KeyPath 是什么?
swift
Johnny Tong2 天前
iOS 获取设备占用内存
ios·内存·host_vm
木兰不吃草2 天前
如何在 Mac 上下载安装仙剑游戏仙剑世界?可以通过IPA砸壳包安装非常简单
游戏·macos·ios·游戏程序·mac
帅次2 天前
Flutter 异步编程利器:Future 与 Stream 深度解析
android·flutter·ios·小程序·kotlin·webview·android-studio