iOS16 SwiftUI添加.onTapGesture后NavigationLink失效的解决方法
问题根源
- AVPlayer实例重复创建:你将
player定义为计算属性,每次调用该属性(如传给VideoPlayer或BtnFull2)都会生成全新的AVPlayer实例。这导致你暂停的是新创建的空实例,而非正在播放的那个,播放状态完全不受控制。 - 手势拦截导航事件:在
ButtonStyle中添加onTapGesture会优先拦截点击事件,覆盖NavigationLink的默认导航行为,导致链接无法跳转。
修复步骤与完整代码
1. 封装AVPlayer确保实例唯一
创建ObservableObject包装器,保证整个视图生命周期内只有一个AVPlayer实例:
import SwiftUI import AVKit // 包装AVPlayer,避免重复创建 class PlayerWrapper: ObservableObject { let player: AVPlayer init(url: URL) { player = AVPlayer(url: url) } }
2. 重构视图逻辑
- 用
@StateObject持有Player实例,控制播放时机 - 移除ButtonStyle中的手势逻辑,改用
simultaneousGesture同时触发导航与暂停
struct SpecialVideo6: View { private let videoURL = Bundle.main.url(forResource: "war", withExtension: "mp4")! @Binding var path: NavigationPath // 持有唯一的Player实例 @StateObject private var playerWrapper = PlayerWrapper(url: Bundle.main.url(forResource: "war", withExtension: "mp4")!) var body: some View { ZStack { Color(.black).ignoresSafeArea() VideoPlayer(player: playerWrapper.player) .scaledToFill() // 右侧按钮组 HStack{ Spacer() VStack { NavigationLink("", value: Route.linkSpecials).buttonStyle(BtnVid(vidIcon: "share")) NavigationLink("", value: Route.linkSpecials).buttonStyle(BtnVid(vidIcon: "hearth")) Spacer() } .frame(maxHeight: 330) .padding(.trailing, 16) .padding(.top, 50) } // 底部Bet按钮 VStack { Spacer() NavigationLink("Bet", value: Route.linkBetBlackOps) .buttonStyle(BtnFull2()) // 同时触发导航与暂停,不拦截事件 .simultaneousGesture(TapGesture().onEnded { playerWrapper.player.pause() }) } .frame(maxHeight: 330) } .navigationBarBackButtonHidden() .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { path.removeLast() } label: { HStack(spacing: 0) { Spacer() Image("x") } .padding(.top, 25) } } } // 视图出现时启动播放 .onAppear { playerWrapper.player.play() } // 视图消失时暂停(可选,防止后台持续播放) .onDisappear { playerWrapper.player.pause() } } } // 简化ButtonStyle,仅处理样式逻辑 struct BtnFull2: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .font(.custom("Bebas Neue", size: 24)) .foregroundColor(.white) .frame(width: 98, height: 50) .background(LinearGradient(gradient: Gradient(colors: [Color("lb"), Color("rb")]), startPoint: .top, endPoint: .bottom)) .cornerRadius(10) } } // 保留原BtnVid样式实现(假设你已完成) struct BtnVid: ButtonStyle { let vidIcon: String func makeBody(configuration: Configuration) -> some View { configuration.label .imageScale(.large) .foregroundColor(.white) .padding() } } // 假设Route枚举已定义 enum Route: Hashable { case linkSpecials case linkBetBlackOps }
关键修复点说明
- 实例唯一性:通过
@StateObject和包装器确保AVPlayer实例在视图生命周期内唯一,暂停操作直接作用于正在播放的视频。 - 手势不拦截导航:
simultaneousGesture让点击事件同时触发导航动作与暂停逻辑,不会覆盖NavigationLink的默认行为。 - 播放时机可控:将播放逻辑移至
onAppear,避免计算属性每次渲染都创建新实例并自动播放。
内容的提问来源于stack exchange,提问作者Pro Girl
相关产品推荐
相关产品推荐

