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

jos*_*hap 5 presentmodalviewcontroller avkit avplayerviewcontroller swiftui

正确的UIKit方法:

根据Apple在WWDC 2019上有关此主题的演讲AVPlayerViewController应以模态呈现,以利用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)
Run Code Online (Sandbox Code Playgroud)

这样可以正常工作,并以漂亮的全屏模式启动视频。

使用SwiftUI获得相同的效果?:

为了使用AVPlayerViewController来自SwiftUI的代码,我创建了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()
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

任何想法将不胜感激!

Raz*_*ick 7

只是一个想法,如果您喜欢像 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 当以“.sheet”形式呈现时,这对我有用。该工作表将显示为 SwiftUI 模式,顶部有小间隙。如果旋转设备或点击左上角的展开至全屏按钮,播放器将进入全屏模式。 (2认同)

pd9*_*d95 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)
        }
    }
Run Code Online (Sandbox Code Playgroud)

但问题是,当 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
    }
}
Run Code Online (Sandbox Code Playgroud)

需要注意的事情(错误?):每当使用.sheet()环境对象显示模态表时,都不会自动传递给子视图。必须使用environmentObject(). 这是阅读有关此问题的更多信息的链接:https : //oleb.net/2020/sheet-environment/


ixa*_*any 5

Xcode 12 \xc2\xb7 iOS 14

\n

\xe2\x86\x92 使用.fullScreenCover而不是.sheet\xe2\x80\x99 就可以了。

\n

另请参阅:如何使用 fullScreenCover 呈现全屏模式视图

\n