iOS 15更新
根据pawello2222在下面的答案中所述,这现在受到新的interactiveDismissDisabled(_:)
API的支持。
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Text("Content View")
.sheet(isPresented: $showSheet) {
Text("Sheet View")
.interactiveDismissDisabled(true)
}
}
}
iOS 15之前的解决方案
我也想达到同样的效果,但却无法在任何地方找到解决方案。抢夺拖动手势的答案有些可行,但当通过滚动滚动视图或表单来关闭它时,就会失效。问题中的方法也不是很巧妙,因此我进一步研究了它。
对于我的用例,我有一个表单在一个工作表中,当没有内容时最好可以关闭它,但当有内容时必须通过警报进行确认。
我解决这个问题的方法:
struct ModalSheetTest: View {
@State private var showModally = false
@State private var showSheet = false
var body: some View {
Form {
Toggle(isOn: self.$showModally) {
Text("Modal")
}
Button(action: { self.showSheet = true}) {
Text("Show sheet")
}
}
.sheet(isPresented: $showSheet) {
Form {
Button(action: { self.showSheet = false }) {
Text("Hide me")
}
}
.presentation(isModal: self.showModally) {
print("Attempted to dismiss")
}
}
}
}
状态值showModally
确定它是否必须以模态方式显示。如果是这样,将其向下拖动以解除显示仅会触发闭包,在示例中仅打印“尝试解雇”,但可用于显示警报以确认解雇。
struct ModalView<T: View>: UIViewControllerRepresentable {
let view: T
let isModal: Bool
let onDismissalAttempt: (()->())?
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: view)
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {
context.coordinator.modalView = self
uiViewController.rootView = view
uiViewController.parent?.presentationController?.delegate = context.coordinator
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
let modalView: ModalView
init(_ modalView: ModalView) {
self.modalView = modalView
}
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
!modalView.isModal
}
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
modalView.onDismissalAttempt?()
}
}
}
extension View {
func presentation(isModal: Bool, onDismissalAttempt: (()->())? = nil) -> some View {
ModalView(view: self, isModal: isModal, onDismissalAttempt: onDismissalAttempt)
}
}
这非常适合我的使用场景,希望它也能对你或其他人有所帮助。
isModal
不应该是一个绑定 (Binding),因为它是只读的。然而,删除 @Binding 会破坏代码,因为Coordinator
只会存储isModal
的初始值。为了解决这个问题,您可以使协调器中的modalView
成为一个var
,然后在updateUIViewController
中更新它,如context.coordinator.modalView = self
,这样如果isModal
发生更改,它将正确更新。关于状态变量不更新的评论可以通过在updateUIViewController
中执行uiViewController.rootView = view
来解决,否则视图将无法正确更新。 - Helam.sheet(isPresented: $showSheet)
,所以请加上@Binding var showSheet: Bool
,并且在需要关闭时使用self.showSheet = false
,而不是self.presentationMode.wrappedValue.dismiss()
。 - LetsGoBrandonUIHostingController
的子类来覆盖willMove(to: parent)
方法,以设置父视图控制器的presentationController
属性,因为在我第一次尝试解散视图控制器之前,updateUIViewController
方法没有被调用。 - vedosity