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

SwiftUI实现列表居中视频自动播放问题求助

解决SwiftUI视频列表仅中心项自动播放的问题

问题分析

你的代码存在两个核心问题:

  • 所有列表项共享同一个AVPlayer实例,一旦某个项触发播放,所有视频组件都会同步播放同一内容
  • onAppear仅判断项是否进入可视区域,无法识别是否处于列表中心位置

修正方案

我们需要为每个列表项单独管理播放器,同时通过监听滚动位置来判断当前哪个项处于屏幕中心,进而控制播放/暂停:

1. 创建独立的视频项ViewModel

为每个视频项创建独立的ViewModel,管理自身的播放器状态:

import AVKit

class VideoItemViewModel: ObservableObject {
    let id: Int
    let videoURL: URL
    @Published var player: AVPlayer?
    @Published var isPlaying = false
    
    init(id: Int, videoURL: URL) {
        self.id = id
        self.videoURL = videoURL
        self.player = AVPlayer(url: videoURL)
    }
    
    func play() {
        player?.play()
        isPlaying = true
    }
    
    func pause() {
        player?.pause()
        isPlaying = false
    }
}

2. 监听滚动位置并检测中心项

使用ScrollViewReader获取滚动位置,结合GeometryReader判断每个项的位置是否处于屏幕中心:

import SwiftUI

struct ContentView: View {
    // 模拟视频数据,这里用同一个URL示例,实际可替换为不同视频地址
    private let videoItems = (1...100).map {
        VideoItemViewModel(id: $0, videoURL: URL(string: "video.mp4")!)
    }
    @State private var centerItemId: Int?
    
    var body: some View {
        GeometryReader { screenGeo in
            ScrollViewReader { proxy in
                List {
                    ForEach(videoItems) { item in
                        VideoRowView(viewModel: item)
                            .id(item.id)
                            .padding()
                            .background(
                                GeometryReader { geo in
                                    Color.clear.onAppear {
                                        // 计算当前项是否处于屏幕中心区域
                                        let itemCenterY = geo.frame(in: .global).midY
                                        let screenCenterY = screenGeo.frame(in: .global).midY
                                        let threshold: CGFloat = 150 // 可调整的中心区域阈值
                                        
                                        if abs(itemCenterY - screenCenterY) < threshold {
                                            centerItemId = item.id
                                        }
                                    }
                                }
                            )
                    }
                }
                .listStyle(.inset)
                .onChange(of: centerItemId) { newId in
                    // 暂停所有非中心项,播放中心项
                    videoItems.forEach { item in
                        if item.id == newId {
                            item.play()
                        } else {
                            item.pause()
                        }
                    }
                }
            }
        }
    }
}

// 独立的视频行视图
struct VideoRowView: View {
    @ObservedObject var viewModel: VideoItemViewModel
    
    var body: some View {
        VStack {
            Text("Row \(viewModel.id)")
            VideoPlayer(player: viewModel.player)
                .frame(height: 200)
        }
    }
}

关键改进点

  • 每个视频项拥有独立的AVPlayer实例,避免播放状态混乱
  • 通过GeometryReader计算项的全局位置,与屏幕中心对比,判断是否处于中心区域
  • 使用ScrollViewReader和onChange监听中心项变化,统一控制播放/暂停逻辑
  • 可通过调整threshold值来扩大或缩小触发播放的中心区域范围

内容的提问来源于stack exchange,提问作者chkml

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 17:15:09