SwiftUI:如何以模态正确显示AVPlayerViewController?

时间:2019-09-20 19:17:16

标签: swiftui presentmodalviewcontroller avplayerviewcontroller avkit

正确的UIKit方法:

根据Apple的WWDC 2019 talk on the subjectAVPlayerViewController应该以模块化方式显示,以利用API的所有最新全屏功能。这是建议的示例代码,可从当前的UIKit视图控制器调用该代码:

// Create the player
let player = AVPlayer(url: videoURL)

// Create the player view controller and associate the player
let playerViewController = AVPlayerViewController()
playerViewController.player = player

// Present the player view controller modally
present(playerViewController, animated: true)

这可以按预期工作,并以漂亮的全屏模式播放视频。

与SwiftUI达到相同的效果吗?:

为了使用SwiftUI中的AVPlayerViewController,我创建了UIViewControllerRepresentable实现:

struct AVPlayerView: UIViewControllerRepresentable {

    @Binding var videoURL: URL

    private var player: AVPlayer {
        return AVPlayer(url: videoURL)
    }

    func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
        playerController.player = player
        playerController.player?.play()
    }

    func makeUIViewController(context: Context) -> AVPlayerViewController {
        return AVPlayerViewController()
    }
}
  

我似乎无法弄清楚如何直接从SwiftUI呈现此内容   与直接显示AVPlayerViewController的方式相同   从UIKit。我的目标仅仅是获得所有默认的全屏优势。

到目前为止,以下操作无效:

  • 如果我使用.sheet修饰符并在图纸中显示它,则播放器将嵌入到图纸中,而不是全屏显示。
  • 我还尝试过在UIKit中创建一个自定义的空视图控制器,该控制器仅通过AVPlayerViewController方法以模态形式呈现我的viewDidAppear。这使播放器可以全屏显示,但是在显示视频之前,它还显示了一个空的视图控制器,我不希望用户看到它。

任何想法都将不胜感激!

3 个答案:

答案 0 :(得分:5)

Razib-Mollick解释的解决方案对我来说是一个不错的开始,但是却没有使用SwiftUI .sheet()方法。我通过将以下内容添加到ContentView中来添加了此内容:

    @State private var showVideoPlayer = false

    var body: some View {
        Button(action: { self.showVideoPlayer = true }) {
            Text("Start video")
        }
        .sheet(isPresented: $showVideoPlayer) {
            AVPlayerView(videoURL: self.$vURL)
                .edgesIgnoringSafeArea(.all)
        }
    }

但是问题是,当SwiftUI重新渲染UI时,一次又一次实例化AVPlayer。 因此,AVPlayer的状态必须移到存储在环境中的class对象中,因此我们可以从View struct中获取它。因此,我的最新解决方案如下所示。我希望它可以帮助其他人。

class PlayerState: ObservableObject {

    public var currentPlayer: AVPlayer?
    private var videoUrl : URL?

    public func player(for url: URL) -> AVPlayer {
        if let player = currentPlayer, url == videoUrl {
            return player
        }
        currentPlayer = AVPlayer(url: url)
        videoUrl = url
        return currentPlayer!
    }
}


struct ContentView: View {
    @EnvironmentObject var playerState : PlayerState
    @State private var vURL = URL(string: "https://www.radiantmediaplayer.com/media/bbb-360p.mp4")

    @State private var showVideoPlayer = false

    var body: some View {
        Button(action: { self.showVideoPlayer = true }) {
            Text("Start video")
        }
        .sheet(isPresented: $showVideoPlayer, onDismiss: { self.playerState.currentPlayer?.pause() }) {
            AVPlayerView(videoURL: self.$vURL)
                .edgesIgnoringSafeArea(.all)
                .environmentObject(self.playerState)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(PlayerState())
    }
}


struct AVPlayerView: UIViewControllerRepresentable {

    @EnvironmentObject var playerState : PlayerState
    @Binding var videoURL: URL?

    func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
    }

    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let playerController = AVPlayerViewController()
        playerController.modalPresentationStyle = .fullScreen
        playerController.player = playerState.player(for: videoURL!)
        playerController.player?.play()
        return playerController
    }
}

要注意的东西(一个错误?):每当使用.sheet()显示一个模式表时,环境对象不会自动传递给子视图。必须使用environmentObject()添加它们。 这是更多有关此问题的链接:https://oleb.net/2020/sheet-environment/

答案 1 :(得分:3)

如果想像UIKit这样全屏显示,您是否尝试过ContentView中的以下代码。

import SwiftUI
import UIKit
import AVKit

struct ContentView: View {
    let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
    @State private var vURL = URL(string: "https://www.radiantmediaplayer.com/media/bbb-360p.mp4")
    var body: some View {
        AVPlayerView(videoURL: self.$vURL).transition(.move(edge: .bottom)).edgesIgnoringSafeArea(.all)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


struct AVPlayerView: UIViewControllerRepresentable {

    @Binding var videoURL: URL?

    private var player: AVPlayer {
        return AVPlayer(url: videoURL!)
    }

    func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
        playerController.modalPresentationStyle = .fullScreen
        playerController.player = player
        playerController.player?.play()
    }

    func makeUIViewController(context: Context) -> AVPlayerViewController {
        return AVPlayerViewController()
    }
}

答案 2 :(得分:2)

Xcode 12·iOS 14

→使用error: expected unqualified-id before ‘<<’ token 17 | template <<int, typename> ...> struct FuncList {}; | ^~ 代替.fullScreenCover,一切就好。

另请参阅:How to present a full screen modal view using fullScreenCover