TL; DR
似乎无法使用绑定来告诉包装好的AVPlayer
停止-为什么不呢? Vlad中的“一个奇怪的把戏”对我有用,没有状态和约束,但是为什么呢?
另请参见
我的问题类似于this one,但是那个发帖人想包装AVPlayerViewController
,并且我想以编程方式控制播放。
This guy也想知道何时调用updateUIView()
。
会发生什么情况(如下所示的控制台日志。)
使用如下所示的代码,
用户点击“转到电影”
MovieView
出现并且vid播放updateUIView(_:context:)
被称为用户点击“返回首页”
HomeView
重新出现updateUIView
。 但是 ...删除###
行,然后
updateUIView
在到达但未离开时被调用如果您取消注释%%%
代码(并注释掉它前面的内容)
代码
我要做使用一个@EnvironmentObject
,所以 正在进行一些状态共享。
主要内容视图(此处无争议):
struct HomeView: View {
@EnvironmentObject var router: ViewRouter
var body: some View {
ZStack() { // +++ Weird trick ### fails if this is Group(). Wtf?
if router.page == .home {
Button(action: { self.router.page = .movie }) {
Text("Go to Movie")
}
} else if router.page == .movie {
MovieView()
}
}
}
}
使用其中一种(仍然是常规的声明式SwiftUI):
struct MovieView: View {
@EnvironmentObject var router: ViewRouter
// @State private var isPlaying: Bool = false // %%%
var body: some View {
VStack() {
PlayerView()
// PlayerView(isPlaying: $isPlaying) // %%%
Button(action: { self.router.page = .home }) {
Text("Go back Home")
}
}.onAppear {
print("> onAppear()")
self.router.isPlayingAV = true
// self.isPlaying = true // %%%
print("< onAppear()")
}.onDisappear {
print("> onDisappear()")
self.router.isPlayingAV = false
// self.isPlaying = false // %%%
print("< onDisappear()")
}
}
}
现在,我们进入AVKit
特定内容。我使用Chris Mash描述的方法。
上述PlayerView
,即wrappER:
struct PlayerView: UIViewRepresentable {
@EnvironmentObject var router: ViewRouter
// @Binding var isPlaying: Bool // %%%
private var myUrl : URL? { Bundle.main.url(forResource: "myVid", withExtension: "mp4") }
func makeUIView(context: Context) -> PlayerView {
PlayerUIView(frame: .zero , url : myUrl)
}
// ### This one weird trick makes OS call updateUIView when view is disappearing.
class DummyClass { } ; let x = DummyClass()
func updateUIView(_ v: PlayerView, context: UIViewRepresentableContext<PlayerView>) {
print("> updateUIView()")
print(" router.isPlayingAV = \(router.isPlayingAV)")
// print(" isPlaying = \(isPlaying)") // %%%
// This does work. But *only* with the Dummy code ### included.
// See also +++ comment in HomeView
if router.isPlayingAV { v.player?.pause() }
else { v.player?.play() }
// This logic looks reversed, but is correct.
// If it's the other way around, vid never plays. Try it!
// if isPlaying { v?.player?.play() } // %%%
// else { v?.player?.pause() } // %%%
print("< updateUIView()")
}
}
然后是包装好的UIView
:
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
var player: AVPlayer?
init(frame: CGRect, url: URL?) {
super.init(frame: frame)
guard let u = url else { return }
self.player = AVPlayer(url: u)
self.playerLayer.player = player
self.layer.addSublayer(playerLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
required init?(coder: NSCoder) { fatalError("not implemented") }
}
当然还有基于Blckbirds示例的视图路由器
class ViewRouter : ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter, Never>()
enum Page { case home, movie }
var page = Page.home { didSet { objectWillChange.send(self) } }
// Claim: App will never play more than one vid at a time.
var isPlayingAV = false // No didSet necessary.
}
控制台日志
控制台日志1(根据需要停止播放)
> updateUIView() // First call
router.isPlayingAV = false // Vid is not playing => play it.
< updateUIView()
> onAppear()
< onAppear()
> updateUIView() // Second call
router.isPlayingAV = true // Vid is playing => pause it.
< updateUIView()
> onDisappear() // After the fact, we clear
< onDisappear() // the isPlayingAV flag.
控制台日志2(奇怪的技巧已禁用;继续播放)
> updateUIView() // First call
router.isPlayingAV = false
< updateUIView()
> onAppear()
< onAppear()
// No second call.
> onDisappear()
< onDisappear()
控制台日志3(尝试使用状态和绑定;继续播放)
> updateUIView()
isPlaying = false
< updateUIView()
> onAppear()
< onAppear()
> updateUIView()
isPlaying = true
< updateUIView()
> updateUIView()
isPlaying = true
< updateUIView()
> onDisappear()
< onDisappear()
答案 0 :(得分:2)
好吧……开
Drawer( child: Column( // Changed this to a Column from a ListView children: <Widget>[ _createHeader(), ListTile(title: Text('First item')), Expanded(child: Container()), // Add this to force the bottom items to the lowest point Column( children: <Widget>[ _createFooterItem( icon: Icons.event, text: 'Settings', onTap: () => Navigator.pushReplacementNamed(context, '/')), _createFooterItem( icon: Icons.event, text: 'Logout', onTap: () => Navigator.pushReplacementNamed(context, '/')) ], ), ], ), );
删除视图后称为 (就像}.onDisappear {
print("> onDisappear()")
self.router.isPlayingAV = false
print("< onDisappear()")
}
,而不是didRemoveFromSuperview
),所以我看不到任何错误或错误/意外的错误中的那个子视图(甚至它本身)没有被更新(在这种情况下为will...
)……如果这样的话,我会很惊讶(为什么不更新视图层次结构中的视图)? !)。
所以
updateUIView
是一些 wild 错误,或...错误。算了吧,永远不要在发布产品时使用这些东西。
好的,现在有人问,怎么做?我在这里看到的主要问题是源于设计的,特别是class DummyClass { } ; let x = DummyClass()
中的模型和视图的紧密耦合,因此无法管理工作流。 PlayerUIView
并不是视图的一部分-它是模型,并且根据其状态AVPlayer
会绘制内容。因此,解决方案是将这些实体拆开并分别管理:按视图的视图,按模型的模型。
这是一个经过修改和简化的方法的演示,其行为符合预期(没有怪异的东西,没有Group / ZStack的限制),并且可以轻松地扩展或改进(在模型/视图模型层中)>
通过Xcode 11.2 / iOS 13.2测试
完整的模块代码(可以从模板复制粘贴到项目中的AVPlayerLayer
中)
ContentView.swift