ScrollView内部分OnTapGesture无响应问题求助(附SwiftUI代码)
SwiftUI列表中部分盒子的OnTapGesture无法响应问题
我正在开发一个SwiftUI视图,用来展示包含可点击盒子的对象列表,但遇到了部分盒子的OnTapGesture无法响应的问题,奇怪的是另一部分盒子能正常响应。我试过一些类似问题的解决方案,但都没用。
相关代码
EventStepView
import SwiftUI import Kingfisher struct EventStepView: View { @ObservedObject var viewModel: EventViewModel @EnvironmentObject var appViewModel: AppViewModel var event: Event @State private var isLocked = true // 存储锁定状态 @State private var isSelected = false var body: some View { ZStack { KFImage(URL(string: event.image)) .resizable() .scaledToFill() .frame(height: 70) .opacity(0.8) .clipShape(RoundedRectangle(cornerRadius: 10)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color("FontColor"), lineWidth: 3) ) .padding(.horizontal, 20) HStack { VStack(alignment: .leading) { Text(event.name) .font(.custom("Inter-Regular_bold", size: 18)) Text(event.date, style: .date) // 根据需要格式化日期 .font(.custom("Inter-Regular", size: 17)) } .foregroundStyle(Color("FontColor")) Spacer() ZStack { if !isLocked { Image(systemName: "lock.fill") .foregroundStyle(Color("FontColor")) .font(.system(size: 30)) } else if event.attendees.contains(viewModel.userId) { Image(systemName: "checkmark.diamond.fill") .foregroundStyle(Color("FontColor")) .font(.system(size: 30)) } else { Image(systemName: "diamond") .foregroundStyle(Color("FontColor")) .font(.system(size: 30)) } } .onTapGesture { print("X") if !isLocked && appViewModel.currentUser!.id == viewModel.userId { viewModel.addAttendeeToEvent(eventId: event.id!, userId: viewModel.userId) isLocked = false isSelected = true } } } .padding(.horizontal, 40) } .onAppear { isLocked = Date() > event.date } } } #Preview { EventStepView(viewModel: EventViewModel(userId: "testId"), event: Event.defaultEvent) }
EventStepsView
import SwiftUI struct EventStepsView: View { @ObservedObject var viewModel: EventViewModel var body: some View { ScrollViewReader { scrollView in ScrollView { VStack(spacing: 0) { ForEach(viewModel.events) { event in VStack(spacing:0 ) { EventStepView(viewModel: viewModel, event: event) if event != viewModel.events.last { VStack(spacing:0) { SemiCircle().rotation(.degrees(180)).frame(width:20) Rectangle().frame(width: 6, height:60) SemiCircle() .frame(width:20) } .foregroundStyle(Color("FontColor")) } } } } .padding() .onAppear { scrollView .scrollTo(viewModel.events.last?.id) } } } .background(Color("Background"), ignoresSafeAreaEdges: .all) } } #Preview { EventStepsView(viewModel: EventViewModel(userId: "testUser")) }
父视图EventView
import SwiftUI struct EventView: View { @Environment(\.dismiss) var dismiss @ObservedObject var viewModel: EventViewModel @EnvironmentObject var appViewModel: AppViewModel var userId: String init(userId: String) { self.userId = userId _viewModel = ObservedObject(wrappedValue: EventViewModel(userId: userId)) } var body: some View { NavigationStack { EventStepsView(viewModel: viewModel) .onAppear { viewModel.fetchEvents() } .navigationBarTitleDisplayMode(.inline) .toolbarBackground( Color("Background"), for: .navigationBar) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Text("FRESHERS WEEK") .modifier(ViewHeadingStyle()) } ToolbarItem(placement: .navigationBarTrailing) { HStack { Image(systemName: "chevron.left") .imageScale(.large) .foregroundColor(.white) .onTapGesture { dismiss() } } } } } } } #Preview { EventView(userId:"test") }
修复方案
1. 解决视图遮挡问题
列表中每个EventStepView下方的连接线视图会覆盖下方盒子的顶部,拦截点击事件。给连接线添加.allowsHitTesting(false),让它不响应点击:
VStack(spacing:0) { SemiCircle().rotation(.degrees(180)).frame(width:20) Rectangle().frame(width: 6, height:60) SemiCircle().frame(width:20) } .foregroundStyle(Color("FontColor")) .allowsHitTesting(false) // 新增该行
2. 确保ForEach的唯一标识
检查Event类型的id是否唯一且稳定,SwiftUI依赖Identifiable的id来管理视图状态。如果id重复或动态变化,会导致视图更新异常,影响手势响应。
3. 优化手势绑定范围
当前手势只绑定在右侧图标上,若图标区域过小或被遮挡,会导致点击无响应。可以将手势绑定到整个EventStepView容器:
ZStack { // 原有内容... } .onTapGesture { guard !isLocked, let currentUserId = appViewModel.currentUser?.id, currentUserId == viewModel.userId else { return } viewModel.addAttendeeToEvent(eventId: event.id!, userId: viewModel.userId) isLocked = true isSelected = true }
4. 替换State为计算属性
isLocked用@State在.onAppear初始化,可能因视图复用导致状态异常。改为计算属性实时判断:
private var isLocked: Bool { Date() > event.date }
同时移除.onAppear中的赋值逻辑。
内容的提问来源于stack exchange,提问作者Ricardo Mendez Cavalieri
相关产品推荐
相关产品推荐

