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

SwiftUI中如何通过子视图的.onTapGesture关闭Sheet?

问题分析与解决方案

核心问题点

  1. Sheet嵌套在ForEach循环内部:每个列表项都绑定同一个showingProductSheet1并创建Sheet实例,导致视图环境混乱,@Environment(\.dismiss)无法正确传递到子视图。
  2. 异步回调未在主队列执行UI操作:addFavouriteProducts是异步操作,回调可能在后台队列运行,而UI操作必须在主队列完成。
  3. 自定义Binding冗余:Sheet的isPresented直接使用$showingProductSheet1即可,手动包装Binding容易引发状态冲突。

具体修复步骤

1. 调整FavouritesView的Sheet位置,移出ForEach循环

将Sheet绑定到视图根层级,避免循环内重复创建Sheet实例:

struct FavouritesView: View {
    
    @StateObject var favouritesModel = FavouriteProductsViewModel()
    @State var selectedProduct: String?
    @State var product_id: String = ""
    @State private var showingProductSheet1 = false
    
    var body: some View {
        VStack {
            ForEach(favouritesModel.favourite_products, id: \.id){ items in
                VStack {
                    KFImage(URL(string: "https://domain.co.uk/product_logo/\(items.logo_file ?? "")")!)
                    Text(String(items.product_name ?? ""))
                }
                .onTapGesture {
                    selectedProduct = items.id
                    product_id = items.id ?? ""
                    showingProductSheet1 = true
                }
            }
        }
        // 将Sheet移至ForEach外部,确保全局唯一实例
        .sheet(isPresented: $showingProductSheet1, onDismiss: {
            print("ON DISMISS OF FAVOURITES VIEW")
            let defaults = UserDefaults.standard
            let keyString = defaults.string(forKey: "key") ?? ""
            favouritesModel.fetchFavouriteProducts(key: keyString)
        }) {
            BudgetProductSheet(product_id: $product_id)
        }
    }
}

2. 在异步回调中切换到主队列调用dismiss

修改BudgetProductRow的回调逻辑,确保UI操作在主队列执行:

struct BudgetProductRow: View {
    
    @Environment(\.dismiss) var dismiss
    @State var someData = ""
    var productData: ProductTile
    @StateObject var favouritesModel = FavouriteProductsViewModel()
    @Binding var product_id: String

    var body: some View {
        VStack {
            HStack {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(spacing: 20) {
                        ProductBudgetView(productData: productData)
                            .onTapGesture {
                                print("Tapped")
                                someData = productData.id ?? ""
                                let defaults = UserDefaults.standard
                                let keyString = defaults.string(forKey: "key") ?? ""
                                
                                favouritesModel.addFavouriteProducts(
                                    key: keyString,
                                    current_id: product_id,
                                    new_id: someData,
                                    required_enviromental: "1"
                                ) { success in
                                    if success {
                                        product_id = productData.id ?? ""
                                        // 切换到主队列执行dismiss操作
                                        DispatchQueue.main.async {
                                            dismiss()
                                            print("dismiss executed")
                                        }
                                    } else {
                                        print("addFavouriteProducts Unsuccessful...")
                                    }
                                }
                            }
                    }
                }
                .padding()
            }
            Divider()
        }
    }
}

修复逻辑说明

  • 把Sheet移到循环外,保证整个视图只有一个Sheet实例,@Environment(\.dismiss)能正确传递到所有子视图。
  • 异步回调中必须在主队列执行UI操作,否则dismiss无法触发视图状态更新。
  • 移除冗余的自定义Binding,避免不必要的状态绑定冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:25:41