SwiftUI中UIViewControllerRepresentable的Delegate为何失效?
SwiftUI视频播放器控制方法失效问题排查与修复
我用SwiftUI实现了一个基于UIKit PlayerViewController的视频播放器,代码如下:
struct Page: View { let delegate = Delegate() @State private var isPlay = true var body: some View { VStack { PlayerView(delegate: delegate) Button("Play") { delegate.play() } } } } class Delegate { var controller: PlayerViewController? = nil func play() { controller?.play() } func pause() { controller?.pause() } } struct PlayerView: UIViewControllerRepresentable { let delegate: Delegate func makeUIViewController(context: Context) -> some UIViewController { let c = PlayerViewController() delegate.controller = c return c } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } class PlayerViewController: UIViewController { func play() { ... } func pause() { ... } }
播放器能正常显示并播放视频,但点击Play按钮调用play()方法时,发现delegate.controller是nil,控制方法完全失效。我通过Delegate类封装控制逻辑,在PlayerView的makeUIViewController方法中给delegate.controller赋值,但就是无法生效。奇怪的是,我在Playground里复现了相同逻辑却能正常运行,代码如下:
class City { let name: String init(name: String) { self.name = name } func say() { print("hello I am \(name)") } } class Delegate { var city: City? = nil func sayFromDelegate() { city?.say() } } struct Person { let name: String = "Person" let delegate: Delegate func foo() { let c = City(name: "QingDao") delegate.city = c } } let d = Delegate() let p = Person(delegate: d) p.foo() // force bind delegate d.sayFromDelegate() // hello I am QingDao
问题原因
问题核心在于SwiftUI的视图生命周期机制:
Page是值类型结构体,当视图触发更新(比如@State变量isPlay发生变化)时,Page会被重新初始化,此时let delegate = Delegate()会创建一个全新的Delegate实例。- 之前
PlayerView绑定的是旧的Delegate实例,新实例的controller从未被赋值,所以点击按钮调用新Delegate的play()时,controller自然是nil。
而Playground中的代码没有涉及SwiftUI的视图更新逻辑,Person实例不会被重新创建,Delegate的赋值状态能一直保留,所以可以正常运行。
修复方案
要让Delegate实例在视图更新时保持唯一,需要用@StateObject来存储这个引用类型实例(@StateObject专门用于管理视图生命周期内的引用类型,确保视图重新渲染时不会重复创建实例)。修改后的代码如下:
struct Page: View { // 用@StateObject替代直接创建实例 @StateObject private var delegate = Delegate() @State private var isPlay = true var body: some View { VStack { PlayerView(delegate: delegate) Button("Play") { delegate.play() } } } } class Delegate { var controller: PlayerViewController? = nil func play() { controller?.play() } func pause() { controller?.pause() } } struct PlayerView: UIViewControllerRepresentable { let delegate: Delegate func makeUIViewController(context: Context) -> some UIViewController { let c = PlayerViewController() delegate.controller = c return c } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } class PlayerViewController: UIViewController { func play() { ... } func pause() { ... } }
说明
@StateObject会在视图第一次初始化时创建Delegate实例,之后无论视图多少次重新渲染,都会复用同一个实例。这样PlayerView绑定的Delegate和按钮点击时调用的Delegate是同一个对象,controller赋值后不会丢失,控制方法就能正常生效了。
内容的提问来源于stack exchange,提问作者FaiChou
相关产品推荐
相关产品推荐

