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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 19:19:49