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

滚动含YouTube Shorts的TabView时出现错误视频播放问题

解决SwiftUI TabView中YouTube视频播放异常问题

问题根源

  1. TabView预加载机制:PageTabViewStyle默认会预加载相邻页面,导致未显示的视图提前触发onAppear,错误设置isPlaying = true。
  2. WebView回调时机偏差:WKWebView的didFinish回调可能在视图未真正显示时完成,此时预加载触发的onAppear已将viewIsShowing设为true,导致未显示视频启动播放。
  3. 状态判断逻辑缺失:原有逻辑仅依赖onAppear/onDisappear,无法区分视图是预加载状态还是真正处于显示状态。

解决方案

通过TabView的selection状态精确标记当前显示的视频,结合选中状态控制播放逻辑,避免预加载视图误触发播放:

修改后的代码

1. 调整AllVideoView,传递选中状态

struct AllVideoView: View {
    @State private var selected = ""
    @State private var arr = ["-q6-DxWZnlQ", "Bp3iu47RRJQ", "lXJdgDjw1Ks", "It3ecCpuzgc", "7WNJjr8QM1w"]
    
    var body: some View {
        ZStack {
            Color.black.edgesIgnoringSafeArea([.bottom, .top])
            TabView(selection: $selected){
                ForEach(arr, id: \.self){ id in
                    // 传递当前视图是否被选中的状态
                    SingleVideoView(link: id, isSelected: selected == id).tag(id)
                }
                .rotationEffect(.init(degrees: -90))
                .frame(width: widthOrHeight(width: true), height: widthOrHeight(width: false))
            }
            .offset(x: -10.5)
            .frame(width: widthOrHeight(width: false), height: widthOrHeight(width: true))
            .rotationEffect(.init(degrees: 90))
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
        }
        .onAppear {
            // 初始化选中第一个视频
            if selected.isEmpty, let firstId = arr.first {
                selected = firstId
            }
        }
    }
}

2. 重构SingleVideoView,依赖选中状态控制播放

struct SingleVideoView: View {
    let link: String
    let isSelected: Bool
    @State private var viewIsShowing = false
    @State private var isVideoPlaying = false
    
    var body: some View {
        ZStack {
            Color.black
            
            SmartReelView(link: link, isPlaying: $isVideoPlaying, viewIsShowing: $viewIsShowing, isSelected: isSelected)

            Button("", action: {}).disabled(true)
            
            Color.gray.opacity(0.001)
                .onTapGesture {
                    // 仅当前选中的视频允许点击切换播放状态
                    if isSelected {
                        isVideoPlaying.toggle()
                    }
                }
            
        }
        .ignoresSafeArea()
        .onChange(of: isSelected) { newValue in
            // 监听选中状态变化,控制播放与显示标记
            if newValue {
                viewIsShowing = true
                isVideoPlaying = true
            } else {
                isVideoPlaying = false
                viewIsShowing = false
            }
        }
    }
}

3. 更新SmartReelView,增加选中状态判断

struct SmartReelView: UIViewRepresentable {
    let link: String
    @Binding var isPlaying: Bool
    @Binding var viewIsShowing: Bool
    let isSelected: Bool // 新增选中状态参数
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> WKWebView {
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.allowsInlineMediaPlayback = true
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.navigationDelegate = context.coordinator

        let userContentController = WKUserContentController()
        
        webView.configuration.userContentController = userContentController

        loadInitialContent(in: webView)
        
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        // 非选中视图强制暂停,避免预加载视频播放
        guard isSelected else {
            uiView.evaluateJavaScript("isPlaying = false; watchPlayingState();", completionHandler: nil)
            return
        }
        let jsString = """
                isPlaying = \((isPlaying) ? "true" : "false");
                watchPlayingState();
            """
        uiView.evaluateJavaScript(jsString, completionHandler: nil)
    }
    
    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: SmartReelView

        init(_ parent: SmartReelView) {
            self.parent = parent
        }
        
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            // 仅当前选中且视图显示时,才触发自动播放
            if self.parent.viewIsShowing && self.parent.isSelected {
                webView.evaluateJavaScript("clickReady()", completionHandler: nil)
            }
        }
    }
    
    // 原有loadInitialContent方法保持不变
    private func loadInitialContent(in webView: WKWebView) {
        let embedHTML = """
        <style>
            body {
                margin: 0;
                background-color: black;
            }
            .iframe-container iframe {
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
            }
        </style>
        <div class="iframe-container">
            <div id="player"></div>
        </div>
        <script>
            var tag = document.createElement('script');
            tag.src = "https://www.youtube.com/iframe_api";
            var firstScriptTag = document.getElementsByTagName('script')[0];
            firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

            var player;
            var isPlaying = false;
            function onYouTubeIframeAPIReady() {
                player = new YT.Player('player', {
                    width: '100%',
                    videoId: '\(link)',
                    playerVars: { 'playsinline': 1, 'controls': 0},
                    events: {
                        'onStateChange': function(event) {
                            if (event.data === YT.PlayerState.ENDED) {
                                player.seekTo(0);
                                player.playVideo();
                            }
                        }
                    }
                });
            }
        
            function clickReady() {
                player.playVideo();
            }
            
            function watchPlayingState() {
                if (isPlaying) {
                    player.playVideo();
                } else {
                    player.pauseVideo();
                }
            }
        
        </script>
        """
        
        webView.scrollView.isScrollEnabled = false
        webView.loadHTMLString(embedHTML, baseURL: nil)
    }
}

// 原有widthOrHeight方法保持不变
func widthOrHeight(width: Bool) -> CGFloat {
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    
    if width {
        return window?.screen.bounds.width ?? 0
    } else {
        return window?.screen.bounds.height ?? 0
    }
}

核心修改点

  • 用isSelected状态替代单纯的onAppear/onDisappear,精确识别当前活跃的视频视图
  • 在updateUIView中强制非选中视图暂停,彻底阻断预加载视频的播放可能
  • 调整didFinish回调的触发条件,确保只有当前选中且显示的视频才启动自动播放
  • 限制点击暂停/播放的作用范围,仅对当前选中的视频生效

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 07:30:26