如何在.interactiveDismissDisabled(true)时检测用户的sheet滑动解散尝试?
解决方案
在SwiftUI里,直接设置.interactiveDismissDisabled(true)会完全拦截滑动解散的手势,导致没法检测到用户的解散尝试。可以通过以下两种方式实现你要的需求:
方法一:用UIKit代理监听解散请求
通过包装UIKit的代理对象,精准捕获sheet的解散请求:
import SwiftUI import UIKit struct SheetDismissInterceptor: UIViewControllerRepresentable { var hasUnsavedChanges: Bool var onAttemptDismiss: () -> Void func makeUIViewController(context: Context) -> UIViewController { let vc = UIViewController() vc.modalPresentationStyle = .overFullScreen return vc } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { guard let presentationController = uiViewController.parent?.presentationController else { return } context.coordinator.hasUnsavedChanges = hasUnsavedChanges context.coordinator.onAttemptDismiss = onAttemptDismiss presentationController.delegate = context.coordinator } func makeCoordinator() -> Coordinator { Coordinator() } class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate { var hasUnsavedChanges = false var onAttemptDismiss: (() -> Void)? func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { if hasUnsavedChanges { onAttemptDismiss?() return false } return true } } } // 实际使用示例 struct ContentView: View { @State private var showSheet = false @State private var hasUnsavedChanges = false @State private var showConfirmAlert = false var body: some View { Button("打开编辑Sheet") { showSheet = true } .sheet(isPresented: $showSheet) { EditSheetView(hasUnsavedChanges: $hasUnsavedChanges) .background(SheetDismissInterceptor( hasUnsavedChanges: hasUnsavedChanges, onAttemptDismiss: { showConfirmAlert = true } )) .alert("确定要放弃修改并关闭吗?", isPresented: $showConfirmAlert) { Button("取消", role: .cancel) {} Button("放弃修改", role: .destructive) { showSheet = false } } } } } struct EditSheetView: View { @Binding var hasUnsavedChanges: Bool @State private var inputText = "" var body: some View { TextField("输入内容", text: $inputText) .onChange(of: inputText) { _ in hasUnsavedChanges = true } .padding() } }
方法二:自定义滑动手势替代系统手势
禁用系统的滑动解散,自己添加拖动手势来模拟,触发时判断是否需要弹出确认:
struct ContentView: View { @State private var showSheet = false @State private var hasUnsavedChanges = false @State private var showConfirmAlert = false var body: some View { Button("打开编辑Sheet") { showSheet = true } .sheet(isPresented: $showSheet) { EditSheetView(hasUnsavedChanges: $hasUnsavedChanges) .interactiveDismissDisabled(true) // 关掉系统自带的滑动手势 .gesture( DragGesture(minimumDistance: 20, coordinateSpace: .global) .onEnded { gesture in // 判断是否是向下滑动的解散手势 if gesture.translation.height > 50 { if hasUnsavedChanges { showConfirmAlert = true } else { showSheet = false } } } ) .alert("确定要放弃修改并关闭吗?", isPresented: $showConfirmAlert) { Button("取消", role: .cancel) {} Button("放弃修改", role: .destructive) { showSheet = false } } } } } struct EditSheetView: View { @Binding var hasUnsavedChanges: Bool @State private var inputText = "" var body: some View { TextField("输入内容", text: $inputText) .onChange(of: inputText) { _ in hasUnsavedChanges = true } .padding() } }
两种方法对比
- 方法一兼容性更强,能捕获所有触发sheet解散的操作(包括滑动、点击外部),体验更贴合系统原生
- 方法二实现更简单,但需要自己调整手势的判断逻辑,可能没法完全复刻系统手势的细节
内容的提问来源于stack exchange,提问作者Ilja
相关产品推荐
相关产品推荐

