SwiftUI ScrollView中视频渲染卡顿问题及优化咨询
问题修复方案
一、解决滚动卡顿问题
1. 分离静态与动态视图,减少重渲染触发
红色色块属于静态布局元素,与视频播放器放在同一VStack时,会导致视图整体布局稳定性下降,触发频繁重计算。调整结构,让视频播放器部分独立封装:
struct MessageVideoPlayerX: View { @State var player: AVPlayer? = nil let url: URL let viewID: String // 移除@State,避免不必要的状态更新 init(url: URL, viewID: String? = nil) { self.url = url self.viewID = viewID ?? UUID().uuidString } var body: some View { VStack(spacing: 0) { // 视频播放器用ZStack包裹,保证布局边界稳定 ZStack { if let vid = player { VidPlayer(player: vid) .frame(width: 200, height: 350) .clipped() } else { Color.gray.frame(width: 200, height: 350) // 添加占位避免视图跳动 } } Color.red.frame(width: 200, height: 300) } .onAppear { if player == nil { player = AVPlayer(url: url) player?.isMuted = true } player?.play() } .onDisappear { player?.pause() } .id(viewID) // 绑定唯一ID,确保LazyVStack正确复用视图 } }
2. 优化AVPlayerViewController性能
关闭UIKit播放器控制器的冗余功能,降低嵌入SwiftUI的开销:
struct VidPlayer: UIViewControllerRepresentable { var player: AVPlayer func makeUIViewController(context: Context) -> AVPlayerViewController { let controller = AVPlayerViewController() controller.player = player controller.showsPlaybackControls = false controller.allowsVideoFrameAnalysis = false controller.videoGravity = .resizeAspectFill controller.automaticallyWaitsToMinimizeStalling = false // 关闭自动等待,由SwiftUI控制播放 return controller } func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {} }
3. 强化LazyVStack的视图复用逻辑
给每个列表项绑定稳定ID,避免LazyVStack误销毁视图:
struct MessageView: View { let urlV: URL let video: allVids var body: some View { VStack { MessageVideoPlayerX(url: urlV, viewID: video.id) } .id(video.id) } }
二、实现视频持久化渲染(类似Ins/Twitter)
核心思路是缓存AVPlayer实例,避免滚动离开后销毁实例,返回时无需重新初始化。
1. 构建全局Player缓存管理器
class PlayerCacheManager: ObservableObject { static let shared = PlayerCacheManager() private var players: [String: AVPlayer] = [:] private let queue = DispatchQueue(label: "com.yourapp.playercache") // 获取或复用AVPlayer func player(for url: URL) -> AVPlayer { let key = url.absoluteString return queue.sync { if let existingPlayer = players[key] { return existingPlayer } else { let newPlayer = AVPlayer(url: url) newPlayer.isMuted = true players[key] = newPlayer return newPlayer } } } // 释放指定Player(可选,根据内存情况调用) func releasePlayer(for url: URL) { let key = url.absoluteString queue.async { self.players[key]?.pause() self.players[key] = nil } } }
2. 在视图中使用缓存的Player
不再用@State存储Player,改为从缓存管理器获取:
struct MessageVideoPlayerX: View { let url: URL let viewID: String @StateObject private var cacheManager = PlayerCacheManager.shared var body: some View { VStack(spacing: 0) { ZStack { VidPlayer(player: cacheManager.player(for: url)) .frame(width: 200, height: 350) .clipped() } Color.red.frame(width: 200, height: 300) } .onAppear { cacheManager.player(for: url).play() } .onDisappear { cacheManager.player(for: url).pause() // 内存紧张时可调用releasePlayer,但会丢失播放状态 // cacheManager.releasePlayer(for: url) } .id(viewID) } }
3. 内存优化:监听内存警告清理缓存
extension PlayerCacheManager { func setupMemoryWarningObserver() { NotificationCenter.default.addObserver( self, selector: #selector(handleMemoryWarning), name: UIApplication.didReceiveMemoryWarningNotification, object: nil ) } @objc private func handleMemoryWarning() { queue.async { self.players.removeAll() } } }
在App启动时调用PlayerCacheManager.shared.setupMemoryWarningObserver()即可。
额外优化建议
- 用
AVPlayerLayer直接封装视图替代AVPlayerViewController,进一步降低UIKit嵌入开销:
struct VidPlayerLayerView: UIViewRepresentable { var player: AVPlayer func makeUIView(context: Context) -> UIView { let view = UIView() let playerLayer = AVPlayerLayer() playerLayer.player = player playerLayer.videoGravity = .resizeAspectFill view.layer.addSublayer(playerLayer) return view } func updateUIView(_ uiView: UIView, context: Context) { guard let playerLayer = uiView.layer.sublayers?.first as? AVPlayerLayer else { return } playerLayer.frame = uiView.bounds playerLayer.player = player } }
- 限制同时播放的视频数量:通过滚动监听,只让当前可见的1-2个视频处于播放状态,减少性能消耗。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

