欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[SwiftUI]在SwiftUI中使用UIView和UIViewController

程序员文章站 2022-03-16 16:13:39
...

SwiftUI is an innovative, exceptionally simple way to build user interfaces across all Apple platforms with the power of Swift. Build user interfaces for any Apple device using just one set of tools and APIs.
Xcode-SwiftUI-Apple Developer

SwiftUI是一种创新的,非常简单的方法,可以借助Swift为Apple全平台构建用户界面。 仅使用一组工具和API就能为任何Apple设备构建用户界面。尽管SwiftUI的工具有限,但配合UIViewUIViewControoler的使用能弥补这一缺憾。

本文将通过几个在项目开发中实际用到的例子来介绍

  • 如何在SwiftUI view中集成UIViewUIViewControoler
  • 如何在SwfitUI和UIKit中传递数据

在SwiftUI中使用UIView

将UIView集成到SwiftUI中的过程主要分为两步:

  1. 声明一个符合SwiftUI view的UIViewRepresentable
  2. 在1中声明的struct中实现两种必须的方法makeUIViewupdateUIView

我们这里以一个视频播放器View为例,代码如下:

import UIKit
import SwiftUI

// 声明PlayerView代表PlayerUIView
struct PlayerView: UIViewRepresentable {
    
    // update方法使我们能够将UIView和SwiftUI状态更新保持同步,目前我们暂且将其保留为空
    func updateUIView(_ uiView: PlayerUIView, context: Context) {
    
    }
    // make方法返回初始视图
    func makeUIView(context: Context) -> PlayerUIView {
    
    }
}

下面给出PlayerUIView的代码

class PlayerUIView: UIView {
    private let playerLayer = AVPlayerLayer()
    var player = AVPlayer()
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        let mediaPath = Bundle.main.path(forResource: "Cossack Squat", ofType: "mp4")
        let mediaURL = URL(fileURLWithPath: mediaPath!)
        
        player = AVPlayer(url:mediaURL as URL)
        player.actionAtItemEnd = .none
        
        playerLayer.player = player
        NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd(notification:)), name: .AVPlayerItemDidPlayToEndTime,
                                               object: player.currentItem)
        
        layer.addSublayer(playerLayer)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
    }
    @objc func playerItemDidReachEnd(notification: Notification) {
        if let playerItem = notification.object as? AVPlayerItem {
            playerItem.seek(to: .zero, completionHandler: nil)
        }
    }
    func beginPlayVideo() {
        player.play()
    }
    func stopPlayVideo() {
        player.pause()
    }
}

这里希望通过SwiftUI中的变量来控制PlayerUIView中视频的播放与暂停,因此SwifUI的代码为:

struct ContentView: View {
    @State var isBegin = false
    @State var isFinished = false
    
    var body: some View {
        PlayerView(isBegin: $isBegin, isFinished: $isFinished)
       		.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 220)
    }
}

同时修改PlayerView中的代码为:

// video player
struct PlayerView: UIViewRepresentable {
    @Binding var isBegin: Bool
    @Binding var isFinished: Bool
    
    func updateUIView(_ uiView: PlayerUIView, context: Context) {
        if isBegin {
            uiView.beginPlayVideo()
        }
        if isFinished {
            uiView.stopPlayVideo()
        }
    }
    func makeUIView(context: Context) -> PlayerUIView {
        return PlayerUIView(frame: .zero)
    }
}

通过以上的例子便能将UIView与SwiftUI连接,并通过Binding传递数据达到SwiftUI控制UIView的目的

在SwiftUI中使用UIViewController

UIViewController集成的过程几乎与UIView相同。即SwiftUI视图必须符合UIViewControllerRepresentable协议并实现相同的方法集。

// Need UIViewControllerRepresentable to show any UIViewController in SwiftUI
struct CameraView : UIViewControllerRepresentable {
    // Init your ViewController
    
    func makeUIViewController(context: Context) -> JointViewController {
        let controller = JointViewController()
        return controller
    }
    
    // Tbh no idea what to do here
    func updateUIViewController(_ uiViewController: JointViewController, context: Context) {

    }
}

如果希望将SwiftUI中的数据传入UIViewController则是相同的操作,通过Binding然后传入update方法中. 那么怎样将UIViewController中处理完的数据再次返回SwiftUI呢?这里就需要用到Coordinator. 通过委托,目标动作,回调和KVO与UIKit通信是Coordinator的责任:

extension CameraView {
    class Coordinator: NSObject, CameraViewDelegate {

        @Binding var isFinished: Bool
        @Binding var currentNum: Int
        @Binding var desiredGoal: Int
        
        init(isFinished: Binding<Bool>, currentNum: Binding<Int>, desiredGoal: Binding<Int>) {
            _isFinished = isFinished
            _currentNum = currentNum
            _desiredGoal = desiredGoal
        }
        
        func CameraViewDidFinished(_ viewController: JointViewController) {
            isFinished = viewController.finishFlag
        }
        
        func OneGroupDidFinished(_ viewController: JointViewController) {
            currentNum = viewController.successCount
        }
        
        func ConfirmDesiredGoal(_ viewController: JointViewController) {
            viewController.desiredGoal = desiredGoal
        }
    }
}

我们先不用理会这三个数据具体的意思,只需要知道isFinishedcurrentNumUIViewController希望通过Coordinator传递给我SwiftUI的,而desiredGoal是SwiftUI希望在改变其值后传入到UIViewController

下面给出JointViewController相关代码

class JointViewController: UIViewController {
	var delegate : CameraViewDelegate?
 	// number of desired amount
 	public var desiredGoal: Int = 0
 	public var finishFlag = false
 	public var successCount = 0
 	// MARK: - View Controller Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()

        // setup delegate for performance measurement
        ????‍????.delegate = self
    }
    func compareWithStandardPoints() {
        ...
        } else {
            delegate?.ConfirmDesiredGoal(self)
            successCount += 1
            print("Success count: ", successCount)
            delegate?.OneGroupDidFinished(self)
        }
    }
}
protocol CameraViewDelegate: NSObjectProtocol {
    func CameraViewDidFinished(_ viewController: JointViewController)
    func OneGroupDidFinished(_ viewController: JointViewController)
    func ConfirmDesiredGoal(_ viewController: JointViewController)
}

同时我们应该将CameraView更新为:

// Need UIViewControllerRepresentable to show any UIViewController in SwiftUI
struct CameraView : UIViewControllerRepresentable {
    @Binding var title: String
    @Binding var isBegin: Bool
    @Binding var isFinished: Bool
    @Binding var desiredGoal: Int
    @Binding var currentNum: Int
    // Init your ViewController
    
    func makeUIViewController(context: Context) -> JointViewController {
        let controller = JointViewController()
        controller.name = title
        controller.desiredGoal = desiredGoal
        controller.delegate = context.coordinator
        return controller
    }
    
    
    // Tbh no idea what to do here
    func updateUIViewController(_ uiViewController: JointViewController, context: Context) {
        if isBegin {
            uiViewController.begin()
        } else {
            uiViewController.pause()
        }
    }
    
    func makeCoordinator() -> CameraView.Coordinator {
        return Coordinator(isFinished: $isFinished, currentNum: $currentNum, desiredGoal: $desiredGoal)
    }
}

接着在便可在SwiftUI view中直接调用CameraView:

struct ContentView: View {
    @Binding var title: String
    @State var isBegin = false
    @State var isFinished = false
    @State var desiredGoal = 10
    @State var currentNum = 0
    
    var body: some View {
        CameraView(title: $title, isBegin: $isBegin, isFinished: $isFinished, desiredGoal: $desiredGoal, currentNum: $currentNum)
       		.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
    }
}

这里代码没有给完全,不过整体框架便是如此

生命周期

每个表示UIKit视图或视图控制器的SwiftUI视图都经历以下步骤,以总结其生命周期:
[SwiftUI]在SwiftUI中使用UIView和UIViewController

  1. 创建一个自定义Coordinator实例,该实例管理UIViewControllerUIView与SwiftUI应用程序其他部分之间的更新
  2. 创建UIViewControllerUIView的实例。使用中的信息来context连接协调器,并设置UIViewControllerUIView的初始外观。我们可以将此方法简单视为viewDidLoad
  3. update每当您更改封闭的SwiftUI视图的状态时,SwiftUI都会自动调用该方法。使用此方法可以使您的信息UIViewUIViewController与更新的状态信息保持同步。
  4. 像您通常会在deinit中做的一样执行任何清理工作。也就是说移除NotificationCenter observation,无效的计时器或者取消URLSessionTask之类的工作

参考
Using UIView and UIViewController in SwiftUI(推荐这篇博客,对概念讲得比较详细)

相关标签: iOS swift