You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 05:07:33