我在将基本内容从 UIKit 导入到 SwiftUI 时遇到了严重的问题,这似乎剥夺了 SwiftUI 快速原型设计 UI 元素的所有优势。我将把我的问题分成不同的帖子,以遵守坚持一个问题的指导方针。
下面的代码通过一个名为
AVPlayerViewController
的中间 UIViewController
来呈现 VideoViewControllerWrapper
,它在 makeUIViewController
中返回。我使用中间控制器,因为否则 AVPlayerViewController
在启动时不会显示播放控件。
我的第一个问题是这个视图控制器应该在没有动画的情况下被关闭。无论如何我看不到实现它。
这就是我目前驳回它的方式。
VideoPlayerView(player: player, showsPlaybackControls: true, onCompletion: { complete in
shouldDismiss.toggle()
})
.ignoresSafeArea(.all)
.frame(maxWidth: .infinity)
.aspectRatio(16/9.0, contentMode: .fit)
.onChange(of: shouldDismiss) { newValue in
dismiss()
}
import Foundation
import SwiftUI
import AVKit
public typealias VideoPlayerCompletion = (
Bool
) -> Void
public struct VideoPlayerView: UIViewControllerRepresentable {
let player: AVPlayer?
var showsPlaybackControls: Bool
var onCompletion:VideoPlayerCompletion?
public init(player: AVPlayer?,
showsPlaybackControls: Bool = true,
onCompletion:VideoPlayerCompletion? = nil) {
self.player = player
self.showsPlaybackControls = showsPlaybackControls
self.onCompletion = onCompletion
}
public func makeUIViewController(context: Context) -> VideoViewControllerWrapper {
return VideoViewControllerWrapper(presentingController: false, player: player, onCompletion: onCompletion)
}
public func updateUIViewController(_ controller: VideoViewControllerWrapper, context: Context) {
controller.player = player
controller.onCompletion = onCompletion
// controller.updateState()
}
func dismantleUIViewController(_ uiViewController: UIViewController, coordinator: ()) {
print("Calling dismantle")
}
}
public final class VideoViewControllerWrapper: UIViewController, UIViewControllerTransitioningDelegate {
var presentingController = false
var player:AVPlayer?
var onCompletion:VideoPlayerCompletion?
init(presentingController: Bool = false, player: AVPlayer? = nil, onCompletion: VideoPlayerCompletion? = nil) {
self.presentingController = presentingController
self.player = player
self.onCompletion = onCompletion
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateState()
}
fileprivate func updateState() {
if player != nil {
if !presentingController {
presentingController = true
let controller = AVPlayerViewController()
controller.player = player
controller.modalPresentationStyle = .fullScreen
controller.showsPlaybackControls = true
controller.transitioningDelegate = self
self.present(controller, animated: false, completion: {
})
}
}
}
public func animationController(forDismissed dismissed: UIViewController)
-> UIViewControllerAnimatedTransitioning?
{
// The dismissal was before the movie ended
// print("AVPlayerController dismissed")
onCompletion?(true)
return nil
}
}
这里是一些示例代码,请参阅注释以获取一些解释。
import SwiftUI
struct AVPlaverInterfaceView: View {
@State private var isPresenting: Bool = false
var body: some View {
Button("present") {
isPresenting.toggle()
}
.disabled(isPresenting) // You can't dismiss the player from here.
.background {
//It doesn't matter where this goes as long as it is somewhere in the `body`, this prevents the error you mentioned before about not being in the view hierarchy.
AVPlayer_UI(url: URL(string: "https://v.ftcdn.net/06/51/32/56/700_F_651325695_ybJOFmDp9TSn9vfIvbP1bbcqIeHJ7vaO.mp4")!, showsPlaybackControls: true, isPresenting: $isPresenting)
//The parent is empty, nothing to show
.frame(width: 0, height: 0)
}
}
}
#Preview {
AVPlaverInterfaceView()
}
import AVKit
struct AVPlayer_UI: UIViewControllerRepresentable{
let url: URL
let showsPlaybackControls: Bool
@Binding var isPresenting: Bool
func makeUIViewController(context: Context) -> some UIViewController {
.init() //Parent UIViewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
guard isPresenting, uiViewController.presentedViewController == nil else {return} // Only `present` if `isPresenting`
let player = AVPlayer(url: url)
let controller = AVPlayerViewController()
// Do whatever setup you need
controller.player = player
controller.showsPlaybackControls = showsPlaybackControls
controller.delegate = context.coordinator
// Setup to know when the AVPlayerViewController is dismissed
controller.transitioningDelegate = context.coordinator
context.coordinator.onDismiss = {
isPresenting = false
}
//Now present the controller from UIKit, no need for sheet or fullScreenCover.
uiViewController.present(controller, animated: true) {
controller.player!.play()
}
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator: NSObject, AVPlayerViewControllerDelegate, UIViewControllerTransitioningDelegate {
var onDismiss: (() -> Void)?
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print(#function)
onDismiss?()
return .none
}
}
}