And*_*can 3 data-binding wrapper avplayer swiftui
长话短说
\n\n似乎无法使用绑定来告诉wrappedAVPlayer
停止\xe2\x80\x94 为什么不呢?弗拉德的“一个奇怪的技巧” 对我来说很有效,没有状态和绑定,但为什么呢?
也可以看看
\n\n我的问题与此类似,但该海报想要包装一个AVPlayerViewController
,我想以编程方式控制播放。
这家伙也想知道什么时候updateUIView()
被叫到的。
发生了什么(控制台日志如下所示。)
\n\n使用如下所示的代码,
\n\n用户点击“去看电影”
\n\nMovieView
出现并播放视频updateUIView(_:context:)
被称为用户点击“返回主页”
\n\nHomeView
再次出现updateUIView
被呼叫。但是...删除该###
行,然后
updateUIView
抵达时会接到电话,但离开时不会接到电话如果您取消注释%%%
代码(并注释掉其前面的内容)
代码
\n\n我确实使用了 an @EnvironmentObject
,所以正在进行一些状态共享。
主要内容视图(这里没有争议):
\n\nstruct HomeView: View {\n @EnvironmentObject var router: ViewRouter\n\n var body: some View {\n ZStack() { // +++ Weird trick ### fails if this is Group(). Wtf?\n if router.page == .home {\n Button(action: { self.router.page = .movie }) {\n Text("Go to Movie")\n }\n } else if router.page == .movie {\n MovieView()\n }\n }\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n它使用其中之一(仍然是常规声明式 SwiftUI):
\n\nstruct MovieView: View {\n @EnvironmentObject var router: ViewRouter\n // @State private var isPlaying: Bool = false // %%%\n\n var body: some View {\n VStack() {\n PlayerView()\n // PlayerView(isPlaying: $isPlaying) // %%%\n Button(action: { self.router.page = .home }) {\n Text("Go back Home")\n }\n }.onAppear {\n print("> onAppear()")\n self.router.isPlayingAV = true\n // self.isPlaying = true // %%%\n print("< onAppear()")\n }.onDisappear {\n print("> onDisappear()")\n self.router.isPlayingAV = false\n // self.isPlaying = false // %%%\n print("< onDisappear()")\n }\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n现在我们进入AVKit
具体的内容。我使用Chris Mash描述的方法。
前述的PlayerView
wrappER:
struct PlayerView: UIViewRepresentable {\n @EnvironmentObject var router: ViewRouter\n // @Binding var isPlaying: Bool // %%%\n\n private var myUrl : URL? { Bundle.main.url(forResource: "myVid", withExtension: "mp4") }\n\n func makeUIView(context: Context) -> PlayerView {\n PlayerUIView(frame: .zero , url : myUrl)\n }\n\n // ### This one weird trick makes OS call updateUIView when view is disappearing.\n class DummyClass { } ; let x = DummyClass()\n\n func updateUIView(_ v: PlayerView, context: UIViewRepresentableContext<PlayerView>) {\n print("> updateUIView()")\n print(" router.isPlayingAV = \\(router.isPlayingAV)")\n // print(" isPlaying = \\(isPlaying)") // %%%\n\n // This does work. But *only* with the Dummy code ### included.\n // See also +++ comment in HomeView\n if router.isPlayingAV { v.player?.pause() }\n else { v.player?.play() }\n\n // This logic looks reversed, but is correct.\n // If it\'s the other way around, vid never plays. Try it!\n // if isPlaying { v?.player?.play() } // %%%\n // else { v?.player?.pause() } // %%%\n\n print("< updateUIView()")\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n和包装UIView
:
class PlayerUIView: UIView {\n private let playerLayer = AVPlayerLayer()\n var player: AVPlayer?\n\n init(frame: CGRect, url: URL?) {\n super.init(frame: frame)\n guard let u = url else { return }\n\n self.player = AVPlayer(url: u)\n self.playerLayer.player = player\n self.layer.addSublayer(playerLayer)\n }\n\n override func layoutSubviews() {\n super.layoutSubviews()\n playerLayer.frame = bounds\n }\n\n required init?(coder: NSCoder) { fatalError("not implemented") }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n当然还有基于BLckbirds示例的视图路由器
\n\nclass ViewRouter : ObservableObject {\n let objectWillChange = PassthroughSubject<ViewRouter, Never>()\n\n enum Page { case home, movie }\n\n var page = Page.home { didSet { objectWillChange.send(self) } }\n\n // Claim: App will never play more than one vid at a time.\n var isPlayingAV = false // No didSet necessary.\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n控制台日志
\n\n控制台日志 1(根据需要停止播放)
\n\n> updateUIView() // First call\n router.isPlayingAV = false // Vid is not playing => play it.\n< updateUIView()\n> onAppear()\n< onAppear()\n> updateUIView() // Second call\n router.isPlayingAV = true // Vid is playing => pause it.\n< updateUIView()\n> onDisappear() // After the fact, we clear\n< onDisappear() // the isPlayingAV flag.\n
Run Code Online (Sandbox Code Playgroud)\n\n控制台日志 2(奇怪的技巧已禁用;播放继续)
\n\n> updateUIView() // First call\n router.isPlayingAV = false\n< updateUIView()\n> onAppear()\n< onAppear()\n // No second call.\n> onDisappear()\n< onDisappear()\n
Run Code Online (Sandbox Code Playgroud)\n\n控制台日志 3(尝试使用状态和绑定;继续播放)
\n\n> updateUIView()\n isPlaying = false\n< updateUIView()\n> onAppear()\n< onAppear()\n> updateUIView()\n isPlaying = true\n< updateUIView()\n> updateUIView()\n isPlaying = true\n< updateUIView()\n> onDisappear()\n< onDisappear()\n
Run Code Online (Sandbox Code Playgroud)\n
嗯……在
Run Code Online (Sandbox Code Playgroud)}.onDisappear { print("> onDisappear()") self.router.isPlayingAV = false print("< onDisappear()") }
这是在视图被删除后调用的(它就像didRemoveFromSuperview
,不是will...
),所以我没有看到子视图(甚至它本身)没有更新(在这种情况下)有任何不好/错误/意外的情况updateUIView
...我宁愿如果是这样的话,请感到惊讶(为什么要更新视图,它不在视图层次结构中?!)。
所以这
Run Code Online (Sandbox Code Playgroud)class DummyClass { } ; let x = DummyClass()
而是一些野生错误,或者……错误。忘记它吧,永远不要在发布产品时使用这样的东西。
好吧,现在有人会问,该怎么办?我在这里看到的主要问题是设计引起的,特别是模型和视图的紧密耦合PlayerUIView
,因此无法管理工作流程。AVPlayer
这不是视图的一部分 - 它是模型,根据其状态AVPlayerLayer
绘制内容。因此,解决方案是将这些实体分开并单独管理:逐个视图、逐个模型进行管理。
这是修改和简化方法的演示,其行为符合预期(没有奇怪的东西,也没有 Group/ZStack 限制),并且可以轻松扩展或改进(在模型/视图模型层)
使用 Xcode 11.2 / iOS 13.2 进行测试
ContentView.swift
完整的模块代码(可以从模板复制粘贴到项目中)
import SwiftUI
import Combine
import AVKit
struct MovieView: View {
@EnvironmentObject var router: ViewRouter
// just for demo, but can be interchangable/modifiable
let playerModel = PlayerViewModel(url: Bundle.main.url(forResource: "myVid", withExtension: "mp4")!)
var body: some View {
VStack() {
PlayerView(viewModel: playerModel)
Button(action: { self.router.page = .home }) {
Text("Go back Home")
}
}.onAppear {
self.playerModel.player?.play() // << changes state of player, ie model
}.onDisappear {
self.playerModel.player?.pause() // << changes state of player, ie model
}
}
}
class PlayerViewModel: ObservableObject {
@Published var player: AVPlayer? // can be changable depending on modified URL, etc.
init(url: URL) {
self.player = AVPlayer(url: url)
}
}
struct PlayerView: UIViewRepresentable { // just thing wrapper, as intended
var viewModel: PlayerViewModel
func makeUIView(context: Context) -> PlayerUIView {
PlayerUIView(frame: .zero , player: viewModel.player) // if needed viewModel can be passed completely
}
func updateUIView(_ v: PlayerUIView, context: UIViewRepresentableContext<PlayerView>) {
}
}
class ViewRouter : ObservableObject {
enum Page { case home, movie }
@Published var page = Page.home // used native publisher
}
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
var player: AVPlayer?
init(frame: CGRect, player: AVPlayer?) { // player is a model so inject it here
super.init(frame: frame)
self.player = player
self.playerLayer.player = player
self.layer.addSublayer(playerLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
required init?(coder: NSCoder) { fatalError("not implemented") }
}
struct ContentView: View {
@EnvironmentObject var router: ViewRouter
var body: some View {
Group {
if router.page == .home {
Button(action: { self.router.page = .movie }) {
Text("Go to Movie")
}
} else if router.page == .movie {
MovieView()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
2177 次 |
最近记录: |