swiftui 跳转_SwiftUI之如何监听Dismiss手势

df4aa1378c589caf06423c4de97dac64.png

本文中介绍的方法,有可能在未来的SwiftUI升级中,失去效果,但我们仍然可以使用本文中解决问题的思想,这一点很重要。

可以在这里下载完整代码https://gist.github.com/agelessman/335914b2db480c5a343111a4a5bd4e36

大家先思考一个问题,假如我们想在SwiftUI中监听一个Modal试图的dismiss手势,应该怎么做?在UIKit中,很简单,但是在SwiftUI中,暂时还没有直接的方法。

UIAdaptivePresentationControllerDelegate里边有一些方法,在这种场景下很有用,比如:

func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        dismissGuardianDelegate?.attemptedUpdate(flag: true)
    }

    func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
        return !self.preventDismissal
    }

上边是协议中的两个方法,分别可以监听dismiss和是否支持dismiss。

那么重点来了,我们现在要使用UIHostingController在SwiftUI和UIAdaptivePresentationControllerDelegate中间架起一座桥梁,换句话说,以后再遇到SwiftUI中不好解决的问题,都可以采用这种思想,这就是本文要教给你最重要的东西。

我们先看看最终的效果:

7e2bca6af943157bffdaa0f9a5dc9b95.png
protocol DismissGuardianDelegate {
    func attemptedUpdate(flag: Bool)
}

class DismissGuardianUIHostingController<Content>: UIHostingController<Content>, UIAdaptivePresentationControllerDelegate where Content: View {
    var preventDismissal: Bool
    var dismissGuardianDelegate: DismissGuardianDelegate?

    init(rootView: Content, preventDismissal: Bool) {
        self.preventDismissal = preventDismissal
        super.init(rootView: rootView)
    }

    @objc required dynamic init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        viewControllerToPresent.presentationController?.delegate = self
        dismissGuardianDelegate?.attemptedUpdate(flag: false)
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }

    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        dismissGuardianDelegate?.attemptedUpdate(flag: true)
    }

    func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
        return !self.preventDismissal
    }
}

这里有一个比较重要的内容,当我们在SwiftUI中通过sheet,present出一个新的界面的时候,SwiftUI会使用距离该sheet最近的一个controller做presentationController,这里有什么区别呢?举两个例子:

NavigationView {
    DismissGuardian(preventDismissal: $preventDismissal, attempted: $attempted) {
        Text("hi").sheet(isPresented: self.$modal1, content: { MyModal().environmentObject(self.model) })
    }
}

这种情况下,由于sheet写在了Text中,所以最近的presentationController是DismissGuardian。

DismissGuardian(preventDismissal: $preventDismissal, attempted: $attempted) {
    NavigationView {
        Text("hi").sheet(isPresented: self.$modal1, content: { MyModal().environmentObject(self.model) })
    }
}

如果代码是这样,sheet最近的presentationController就是NavigationView了,也就是导航控制器。

这里边的区别就是下边的这种情况我们无法监听到Dismiss手势,原因是:

override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        viewControllerToPresent.presentationController?.delegate = self
        dismissGuardianDelegate?.attemptedUpdate(flag: false)
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }

我们把viewControllerToPresent的presentationController赋值给了DismissGuardianUIHostingController。

大家可以这样理解这一块的内容,DismissGuardianUIHostingController做为一个容器,它里边放着SwiftUI中的View。

struct ContentView: View {
    @State private var show = false
    @ObservedObject var dataModel = MyDataModel()

    var body: some View {
        DissmissGuardian(preventDismiss: $dataModel.preventDissmissal, attempted: $dataModel.attempted) {
            VStack {
                Spacer()

                Text("演示如何监听Dissmiss手势").font(.title)

                Spacer()

                Button("跳转到新的View") {
                    self.show = true
                }
                .sheet(isPresented: self.$show) {
                    MyCustomerView().environmentObject(self.dataModel)
                }

                Spacer()
            }
        }
    }
}

如果大家理解起来有困难,可以留言。DismissGuardianUIHostingController是不能直接显示在SwiftUI中的body中的,需要通过UIViewControllerRepresentable转换一层。

如何转换,基本上也是固定的写法,包含3个步骤:

  • 初始化
  • makeUIViewController,updateUIViewController
  • makeCoordinator,Coordinator

这里就不多说了,大家看代码就行了:

struct DissmissGuardian<Content: View>: UIViewControllerRepresentable {
    @Binding var preventDismiss: Bool
    @Binding var attempted: Bool
    var content: Content

    init(preventDismiss: Binding<Bool>, attempted: Binding<Bool>, @ViewBuilder content: @escaping () -> Content) {
        self._preventDismiss = preventDismiss
        self._attempted = attempted
        self.content = content()
    }

    func makeUIViewController(context: Context) -> UIViewController {
        return DismissGuardianUIHostingController(rootView: self.content, preventDismissal: self.preventDismiss)
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        let dismissHosting = uiViewController as! DismissGuardianUIHostingController<Content>
        dismissHosting.preventDismissal = self.preventDismiss
        dismissHosting.rootView = self.content
        dismissHosting.dismissGuardianDelegate = context.coordinator
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(attempted: $attempted)
    }

    class Coordinator: NSObject, DismissGuardianDelegate {
        @Binding var attempted: Bool

        init(attempted: Binding<Bool>) {
            self._attempted = attempted
        }

        func attemptedUpdate(flag: Bool) {
            self.attempted = flag
        }
    }
}

最后总结一下,凡是遇到在SwiftUI中很难实现的功能,在UIKit中很容易实现,就考虑这种方法。

注:上边的内容参考了网站https://swiftui-lab.com/modal-dismiss-gesture/,如有侵权,立即删除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值