[SwiftUI]在SwiftUI中使用UIView和UIViewController
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的工具有限,但配合UIView
和UIViewControoler
的使用能弥补这一缺憾。
本文将通过几个在项目开发中实际用到的例子来介绍
- 如何在SwiftUI view中集成
UIView
和UIViewControoler
- 如何在SwfitUI和UIKit中传递数据
在SwiftUI中使用UIView
将UIView集成到SwiftUI中的过程主要分为两步:
- 声明一个符合SwiftUI view的
UIViewRepresentable
- 在1中声明的
struct
中实现两种必须的方法makeUIView
和updateUIView
我们这里以一个视频播放器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
}
}
}
我们先不用理会这三个数据具体的意思,只需要知道isFinished
和currentNum
是UIViewController
希望通过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视图都经历以下步骤,以总结其生命周期:
- 创建一个自定义
Coordinator
实例,该实例管理UIViewController
或UIView
与SwiftUI应用程序其他部分之间的更新 - 创建
UIViewController
或UIView
的实例。使用中的信息来context
连接协调器,并设置UIViewController
或UIView
的初始外观。我们可以将此方法简单视为viewDidLoad
-
update
每当您更改封闭的SwiftUI视图的状态时,SwiftUI都会自动调用该方法。使用此方法可以使您的信息UIView
或UIViewController
与更新的状态信息保持同步。 - 像您通常会在
deinit
中做的一样执行任何清理工作。也就是说移除NotificationCenter
observation,无效的计时器或者取消URLSessionTask
之类的工作
参考
Using UIView and UIViewController in SwiftUI(推荐这篇博客,对概念讲得比较详细)