You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SwiftUI:ZStack中NavigationView列表点击失效的解决方法

解决NavigationLink无法点击的问题

你猜的完全正确!问题根源就是ZStack的堆叠顺序——你放在后面的HStack(筛选按钮)和Picker视图,都位于NavigationView的上层,它们会拦截所有落在自身区域内的点击事件,导致下面List里的NavigationLink根本接收不到点击信号。

给你两个靠谱的解决方案,按需选择:

方案1:改用SwiftUI原生布局(推荐)

既然要在列表顶部放筛选栏,最符合SwiftUI设计逻辑的方式是把筛选栏放到List的header里,这样既保证了它在顶部的位置,又不会和NavigationView的点击事件冲突:

import SwiftUI
struct BonusList: View {
    var bonuses = sampleBonusData
    @State var showSettings = false
    @State var showBonuses = false
    @State var bonusEarned = true
    @State var showStatePicker = false
    @State var showCategoryPicker = false
    
    var body: some View {
        NavigationView {
            List(bonuses) { item in
                NavigationLink(destination: BonusDetail(bonusName: item.bonusName, bonusCode: item.bonusCode, city: item.city, sampleImage: item.sampleImage)) {
                    HStack(spacing: 12.0) {
                        Image(item.sampleImage)
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width: 60, height: 60)
                            .background(Color.white)
                            .cornerRadius(15)
                        VStack(alignment: .leading) {
                            HStack {
                                Text(item.bonusName)
                                    .font(.headline)
                                Spacer()
                                Image(systemName: "checkmark.shield")
                                    .opacity(self.bonusEarned ? 1 : 0)
                            }
                            Text("\(item.city), \(item.state)")
                                .font(.subheadline)
                                .frame(height: 25.0)
                            HStack {
                                Text(item.bonusCategory)
                                    .font(.caption)
                                    .fontWeight(.bold)
                                    .foregroundColor(.gray)
                                    .padding(.top, 4)
                                Spacer()
                                Text(item.bonusCode)
                                    .font(.caption)
                                    .fontWeight(.bold)
                                    .foregroundColor(.gray)
                                    .padding(.top, 4)
                            }
                        }
                    }
                }
            }
            .navigationBarTitle(Text("Bonuses"))
            // 把筛选栏放到List的header中
            .listHeader {
                HStack {
                    FilterByCategory(showCategoryPicker: $showCategoryPicker)
                    Spacer()
                    FilterByState(showStatePicker: $showStatePicker)
                }
                .padding(.horizontal)
                .padding(.vertical, 8)
            }
        }
        // 把Picker作为弹窗层单独处理,点击背景可以关闭
        ZStack {
            if showStatePicker {
                Color.black.opacity(0.3)
                    .ignoresSafeArea()
                    .onTapGesture {
                        showStatePicker = false
                    }
                StatePicker(showStatePicker: $showStatePicker)
                    .padding()
                    .background(Color.white)
                    .cornerRadius(12)
                    .position(x: UIScreen.main.bounds.width/2, y: UIScreen.main.bounds.height/2)
            }
            if showCategoryPicker {
                Color.black.opacity(0.3)
                    .ignoresSafeArea()
                    .onTapGesture {
                        showCategoryPicker = false
                    }
                CategoryPicker(showCategoryPicker: $showCategoryPicker)
                    .padding()
                    .background(Color.white)
                    .cornerRadius(12)
                    .position(x: UIScreen.main.bounds.width/2, y: UIScreen.main.bounds.height/2)
            }
        }
    }
}

方案2:保留ZStack但修复点击穿透

如果你一定要维持原来的ZStack结构,可以通过allowsHitTesting控制事件穿透:给上层的筛选栏容器设置不拦截点击,只让内部的筛选按钮响应点击,这样列表区域的点击就能穿透到下面的NavigationLink:

var body: some View {
    ZStack {
        NavigationView {
            List(bonuses) { item in
                // 原有的NavigationLink和行内容不变
            }
        }
        .navigationBarTitle(Text("Bonuses"))
        .saturation(self.bonusEarned ? 0 : 1)
        
        // 给整个HStack设置不拦截点击,仅内部控件允许响应
        HStack {
            FilterByCategory(showCategoryPicker: $showCategoryPicker)
                .allowsHitTesting(true)
            Spacer()
            FilterByState(showStatePicker: $showStatePicker)
                .allowsHitTesting(true)
        }
        .allowsHitTesting(false)
        
        // Picker部分保持不变,它们是弹窗,只有打开时才会拦截点击
        StatePicker(showStatePicker: $showStatePicker)
        CategoryPicker(showCategoryPicker: $showCategoryPicker)
    }
}

两种方案都能解决你的问题,更推荐方案1,因为它更贴合SwiftUI的布局规范,后续维护也更省心。

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

火山引擎 最新活动