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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 12:54:51