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

SwiftUI中如何扩大AVPlayer视频的双击手势检测区域?

问题解决:双击视频仅右上角响应的修复方案

你的问题核心是手势绑定层级错误+VideoPlayer布局未填满父容器,导致双击检测区域被限制在极小范围。以下是具体修复方案:

原因分析

  1. VideoPlayer是UIKit包装视图,默认不会自动填充整个父容器空间,你添加的.contentShape(Rectangle())仅作用于它自身的可视区域,而非整个feed流的视频占位区域
  2. 手势直接绑定在VideoPlayer上时,系统AVPlayer自带的交互控件(如右上角播放暂停按钮)会抢占部分区域的手势响应权

修复步骤

  1. 将手势绑定到外层ZStack上,确保整个视频占位区域都能响应点击
  2. 给VideoPlayer添加.frame(maxWidth: .infinity, maxHeight: .infinity)强制填充父容器
  3. 用exclusiveGesture处理单双击的优先级冲突,避免两者互相干扰

修改后的完整代码

import SwiftUI
import AVKit

struct VideoPlayerView: View {
    
    @StateObject private var viewModel: VideoPlayerViewModel
    
    init(url: URL, isFeedView: Bool = true) {
        _viewModel = StateObject(wrappedValue: .init(url: url, isFeedView: isFeedView))
    }
    
    var body: some View {
        ZStack {
            if let player: AVPlayer = viewModel.player {
                VideoPlayer(player: player)
                    .frame(maxWidth: .infinity, maxHeight: .infinity) // 强制填充父容器
                    .onAppear {
                        if viewModel.isFeedView {
                            if let lastKnownTime = viewModel.lastKnownTime {
                                player.seek(to: CMTime(seconds: lastKnownTime, preferredTimescale: 600))
                            }
                            player.play()
                            player.volume = 0
                        }
                    }
                    .onDisappear {
                        viewModel.lastKnownTime = player.currentTime().seconds
                        player.pause()
                    }
            }
        }
        .maxSize()
        .contentShape(Rectangle()) // 给整个ZStack设置点击响应区域
        // 用exclusiveGesture确保双击优先识别,避免单双击冲突
        .gesture(
            TapGesture(count: 2)
                .onEnded {
                    print("Double tap detected")
                    viewModel.isFullScreen.toggle()
                }
                .exclusiveGesture(
                    TapGesture()
                        .onEnded {
                            print("Single tap detected")
                            viewModel.player?.volume = 1
                        }
                )
        )
        .fullScreenCover(isPresented: $viewModel.isFullScreen) {
            AVPlayerViewControllerRepresented(viewModel: viewModel)
        }
    }
}

class VideoPlayerViewModel: ObservableObject {
    @Published var player: AVPlayer?
    @Published var lastKnownTime: Double?
    @Published var isFullScreen: Bool = false
    @Published var isFeedView: Bool
    
    init(url: URL, isFeedView: Bool = true) {
        player = AVPlayer(url: url)
        lastKnownTime = nil
        self.isFeedView = isFeedView
        if isFeedView {
            registerForPlaybackEndNotification()
        }
    }
    
    private func registerForPlaybackEndNotification() {
        NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: nil) { [weak self] _ in
            self?.videoDidFinish()
        }
    }
    
    private func videoDidFinish() {
        if isFeedView, let player = player {
            player.seek(to: .zero)
            player.play()
        }
    }
}

额外说明

  • 把contentShape移到ZStack后,整个视频的占位区域都会成为点击响应区
  • exclusiveGesture的使用确保双击手势优先被识别,解决了单双击互相干扰的问题
  • 强制VideoPlayer填充父容器,避免出现视觉上的视频区域和实际点击区域不匹配的情况

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:05:14