SwiftUI中如何通过子视图的.onTapGesture关闭Sheet?
问题分析与解决方案
核心问题点
- Sheet嵌套在ForEach循环内部:每个列表项都绑定同一个
showingProductSheet1并创建Sheet实例,导致视图环境混乱,@Environment(\.dismiss)无法正确传递到子视图。 - 异步回调未在主队列执行UI操作:
addFavouriteProducts是异步操作,回调可能在后台队列运行,而UI操作必须在主队列完成。 - 自定义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
相关产品推荐
相关产品推荐

