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

SwiftUI TabView中视频播放器异常问题与播放控制需求咨询

解决SwiftUI TabView混合图片视频的播放控制问题

问题描述

实现了支持图片和视频任意顺序排列的TabView,但存在两个问题:

  • 切换到视频Tab时播放器无法自动启动
  • 切换到图片Tab时,之前的视频音频仍在播放

需要实现:切换到视频Tab时自动播放视频,切换离开时自动暂停。


核心问题分析

  1. onAppear/onDisappear不可靠:在PageTabViewStyle下,相邻的Tab视图不会完全触发onDisappear,导致视频继续播放
  2. Player初始化与关联错误:原代码中VideoWrapper在onAppear重新创建AVPlayer,但AVPlayerViewController没有同步更新这个新的player

修改方案

1. 传递Tab选中状态到VideoWrapper

在MainView中把selection传递给每个VideoWrapper,让视频视图自己判断是否处于当前选中状态,以此控制播放/暂停。

2. 优化Player管理

  • 初始化时直接使用目标视频URL创建player,避免无效初始值
  • 在VideoPlayer的updateUIViewController方法中同步更新player,确保视图与player绑定正确

3. 基于选中状态控制播放逻辑

移除原有的onAppear/onDisappear,改用监听选中状态变化触发播放/暂停,同时处理视图首次出现的情况。


完整修改代码

import SwiftUI
import Kingfisher
import AVFoundation

struct MainView: View {
    @State private var selection = 0
    @State var content: [UploadContent] = [
        UploadContent(isImage: false, videoURL: URL(string: "https://firebasestorage.googleapis.com:443/v0/b/hustle-85b6c.appspot.com/o/stories%2F73F8CF05-39AB-4A8F-AAEF-C70C9A57B22D.mp4?alt=media&token=5b5a63e3-e5af-4e2f-84c4-416abd554b84")!),
        UploadContent(isImage: true, imageURL: "https://firebasestorage.googleapis.com:443/v0/b/hustle-85b6c.appspot.com/o/messages%2F19EAAFB0-0AC1-4100-88DC-F9980531D559?alt=media&token=5b206605-94e2-43f3-9dfb-1922cfecc989"),
        UploadContent(isImage: false, videoURL: URL(string: "https://firebasestorage.googleapis.com:443/v0/b/hustle-85b6c.appspot.com/o/stories%2F73F8CF05-39AB-4A8F-AAEF-C70C9A57B22D.mp4?alt=media&token=5b5a63e3-e5af-4e2f-84c4-416abd554b84")!),
        UploadContent(isImage: false, videoURL: URL(string: "https://firebasestorage.googleapis.com:443/v0/b/hustle-85b6c.appspot.com/o/stories%2F73F8CF05-39AB-4A8F-AAEF-C70C9A57B22D.mp4?alt=media&token=5b5a63e3-e5af-4e2f-84c4-416abd554b84")!),
        UploadContent(isImage: true, imageURL: "https://firebasestorage.googleapis.com:443/v0/b/hustle-85b6c.appspot.com/o/messages%2F01659DB0-C97D-4FB3-A0B4-0F0302FA463B?alt=media&token=9e5b0553-c081-443e-a263-7e0e81630281")
    ]

    var body: some View {
        TabView(selection: $selection) {
            ForEach(Array(content.enumerated()), id: \.1.id) { index, content in
                if let url = content.imageURL {
                    HStack {
                        KFImage(URL(string: url))
                            .resizable()
                            .scaledToFill()
                            .frame(height: 300)
                        Spacer()
                    }
                    .padding(5)
                    .tag(index)
                } else if let url = content.videoURL {
                    HStack {
                        VideoWrapper(url: url, currentSelection: $selection, ownIndex: index)
                            .frame(height: 300)
                        Spacer()
                    }
                    .padding(5)
                    .tag(index)
                }
            }
        }
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
        .frame(height: 310)
    }
}

struct VideoWrapper: View {
    let url: URL
    @Binding var currentSelection: Int
    let ownIndex: Int
    @State private var player: AVPlayer
    
    init(url: URL, currentSelection: Binding<Int>, ownIndex: Int) {
        self.url = url
        self._currentSelection = currentSelection
        self.ownIndex = ownIndex
        self._player = State(initialValue: AVPlayer(url: url))
    }

    var body: some View {
        VideoPlayer(player: $player)
            .scaledToFill()
            .onChange(of: currentSelection) { newSelection in
                if newSelection == ownIndex {
                    player.play()
                } else {
                    player.pause()
                }
            }
            .onAppear {
                if currentSelection == ownIndex {
                    player.play()
                }
            }
    }
}

struct VideoPlayer: UIViewControllerRepresentable {
    @Binding var player: AVPlayer
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player
        controller.showsPlaybackControls = false
        controller.allowsVideoFrameAnalysis = false
        return controller
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
        if uiViewController.player !== player {
            uiViewController.player = player
        }
    }
}

struct UploadContent: Identifiable {
    var id: UUID = UUID()
    var isImage: Bool
    var videoURL: URL?
    var imageURL: String?
}

关键修改说明

  • VideoWrapper新增currentSelection绑定和ownIndex参数,精准判断自身是否处于当前Tab
  • 初始化时直接用目标URL创建AVPlayer,避免无效初始值导致的异常
  • 用onChange(of: currentSelection)监听Tab切换,替代不可靠的onAppear/onDisappear
  • 在VideoPlayer的updateUIViewController中同步player实例,确保控制器使用最新的player

内容的提问来源于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.26 13:51:19