防止在 SwiftUI 中关闭模态视图控制器 [英] Prevent dismissal of modal view controller in SwiftUI

查看:19
本文介绍了防止在 SwiftUI 中关闭模态视图控制器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 WWDC 2019 上,Apple 宣布了一种新的卡片式"外观,用于模态演示,它带来了内置手势,用于通过向下滑动卡片来关闭模态视图控制器.他们还在 UIViewController 上引入了新的 isModalInPresentation 属性,以便您可以选择禁止这种关闭行为.

At WWDC 2019, Apple announced a new "card-style" look for modal presentations, which brought along with it built-in gestures for dismissing modal view controllers by swiping down on the card. They also introduced the new isModalInPresentation property on UIViewController so that you can disallow this dismissal behavior if you so choose.

不过,到目前为止,我还没有发现可以在 SwiftUI 中模拟这种行为的方法.据我所知,使用 .presentation(_ modal: Modal?) 并不能让您以相同的方式禁用关闭手势.我还尝试将模态视图控制器放在 UIViewControllerRepresentable View 中,但这似乎也没有帮助:

So far, though, I have found no way to emulate this behavior in SwiftUI. Using the .presentation(_ modal: Modal?), does not, as far as I can tell, allow you to disable the dismissal gestures in the same way. I also attempted putting the modal view controller inside a UIViewControllerRepresentable View, but that didn't seem to help either:

struct MyViewControllerView: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<MyViewControllerView>) -> UIHostingController<MyView> {
        return UIHostingController(rootView: MyView())
    }

    func updateUIViewController(_ uiViewController: UIHostingController<MyView>, context: UIViewControllerRepresentableContext<MyViewControllerView>) {
        uiViewController.isModalInPresentation = true
    }
}

即使在使用 .presentation(Modal(MyViewControllerView())) 进行展示之后,我也能够向下滑动以关闭视图.目前有没有办法用现有的 SwiftUI 结构来做到这一点?

Even after presenting with .presentation(Modal(MyViewControllerView())) I was able to swipe down to dismiss the view. Is there currently any way to do this with existing SwiftUI constructs?

推荐答案

iOS 15 更新

根据 pawello2222answer 下面,新的 interactiveDismissDisabled(_:) API 现在支持此功能.

Update for iOS 15

As per pawello2222's answer below, this is now supported by the new 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 之前的答案

我也想这样做,但在任何地方都找不到解决方案.劫持拖动手势的答案有点有效,但在通过滚动滚动视图或表单将其解除时则无效.问题中的方法也不太老套,所以我进一步研究了它.

Pre-iOS-15 answer

I wanted to do this as well, but couldn't find the solution anywhere. The answer that hijacks the drag gesture kinda works, but not when it's dismissed by scrolling a scroll view or form. The approach in the question is less hacky also, so I investigated it further.

对于我的用例,我在工作表中有一个表单,理想情况下可以在没有内容时关闭该表单,但在有内容时必须通过警报进行确认.

For my use case I have a form in a sheet which ideally could be dismissed when there's no content, but has to be confirmed through a alert when there is content.

我对这个问题的解决方案:

My solution for this problem:

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 决定它是否必须以模态显示.如果是这样,将其向下拖动以关闭只会触发仅打印尝试关闭"的关闭.在示例中,但可用于显示警报以确认解雇.

The state value showModally determines if it has to be showed modally. If so, dragging it down to dismiss will only trigger the closure which just prints "Attempted to dismiss" in the example, but can be used to show the alert to confirm dismissal.

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)
    }
}

这非常适合我的用例,希望它也能帮助您或其他人.

This is perfect for my use case, hope it helps you or someone else out as well.

这篇关于防止在 SwiftUI 中关闭模态视图控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆