如何在SwiftUI中返回与UIKit(interactivePopGestureRecognizer)相同的行为 [英] How to give back swipe gesture in SwiftUI the same behaviour as in UIKit (interactivePopGestureRecognizer)

查看:177
本文介绍了如何在SwiftUI中返回与UIKit(interactivePopGestureRecognizer)相同的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

交互式弹出手势识别器应允许用户在滑过屏幕的一半以上(或这些行周围的某物)时返回导航堆栈中的上一个视图.在SwiftUI中,如果滑动距离不够远,手势不会被取消.

The interactive pop gesture recognizer should allow the user to go back the the previous view in navigation stack when they swipe further than half the screen (or something around those lines). In SwiftUI the gesture doesn't get canceled when the swipe wasn't far enough.

SwiftUI:: https://imgur.com/xxVnhY7

UIKit : https://imgur.com/f6WBUne

问题:

使用SwiftUI视图时能否获得UIKit行为?

Is it possible to get the UIKit behaviour while using SwiftUI views?

尝试

我试图将UIHostingController嵌入UINavigationController内,但其行为与NavigationView完全相同.

I tried to embed a UIHostingController inside a UINavigationController but that gives the exact same behaviour as NavigationView.

struct ContentView: View {
    var body: some View {
        UIKitNavigationView {
            VStack {
                NavigationLink(destination: Text("Detail")) {
                    Text("SwiftUI")
                }
            }.navigationBarTitle("SwiftUI", displayMode: .inline)
        }.edgesIgnoringSafeArea(.top)
    }
}

struct UIKitNavigationView<Content: View>: UIViewControllerRepresentable {

    var content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    func makeUIViewController(context: Context) -> UINavigationController {
        let host = UIHostingController(rootView: content())
        let nvc = UINavigationController(rootViewController: host)
        return nvc
    }

    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}

推荐答案

我最终重写了默认的NavigationViewNavigationLink以获得所需的行为.这看起来是如此简单,以至于我必须忽略默认SwiftUI视图所做的事情?

I ended up overriding the default NavigationView and NavigationLink to get the desired behaviour. This seems so simple that I must be overlooking something that the default SwiftUI views do?

我将UINavigationController包装在超简单的UIViewControllerRepresentable中,该UINavigationControllerUINavigationController作为环境对象提供给SwiftUI内容视图.这意味着NavigationLink以后可以抢到它,只要它在我们想要的同一个导航控制器中(显示的视图控制器不接收environmentObjects)即可.

I wrap a UINavigationController in a super simple UIViewControllerRepresentable that gives the UINavigationController to the SwiftUI content view as an environmentObject. This means the NavigationLink can later grab that as long as it's in the same navigation controller (presented view controllers don't receive the environmentObjects) which is exactly what we want.

注意::NavigationView需要.edgesIgnoringSafeArea(.top),我还不知道如何在结构本身中进行设置.如果您的nvc在顶部中断,请参见示例.

Note: The NavigationView needs .edgesIgnoringSafeArea(.top) and I don't know how to set that in the struct itself yet. See example if your nvc cuts off at the top.

struct NavigationView<Content: View>: UIViewControllerRepresentable {

    var content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    func makeUIViewController(context: Context) -> UINavigationController {
        let nvc = UINavigationController()
        let host = UIHostingController(rootView: content().environmentObject(nvc))
        nvc.viewControllers = [host]
        return nvc
    }

    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}

extension UINavigationController: ObservableObject {}

NavigationLink

我创建了一个自定义NavigationLink,用于访问环境UINavigationController来推送托管下一个视图的UIHostingController.

NavigationLink

I create a custom NavigationLink that accesses the environments UINavigationController to push a UIHostingController hosting the next view.

注意:我没有实现SwiftUI.NavigationLink的selectionisActive,因为我还不完全了解它们的作用.如果您想提供帮助,请评论/编辑.

Note: I didn't implement the selection and isActive that the SwiftUI.NavigationLink has because I don't fully understand what they do yet. If you want to help with that please comment/edit.

struct NavigationLink<Destination: View, Label:View>: View {
    var destination: Destination
    var label: () -> Label

    public init(destination: Destination, @ViewBuilder label: @escaping () -> Label) {
        self.destination = destination
        self.label = label
    }

    /// If this crashes, make sure you wrapped the NavigationLink in a NavigationView
    @EnvironmentObject var nvc: UINavigationController

    var body: some View {
        Button(action: {
            let rootView = self.destination.environmentObject(self.nvc)
            let hosted = UIHostingController(rootView: rootView)
            self.nvc.pushViewController(hosted, animated: true)
        }, label: label)
    }
}

这解决了向后滑动无法在SwiftUI上正常工作的问题,并且因为我使用了NavigationView和NavigationLink这两个名称,所以我的整个项目都立即切换为这些.

This solves the back swipe not working correctly on SwiftUI and because I use the names NavigationView and NavigationLink my entire project switched to these immediately.

在示例中,我也显示了模态表示.

In the example I show modal presentation too.

struct ContentView: View {
    @State var isPresented = false

    var body: some View {
        NavigationView {
            VStack(alignment: .center, spacing: 30) {
                NavigationLink(destination: Text("Detail"), label: {
                    Text("Show detail")
                })
                Button(action: {
                    self.isPresented.toggle()
                }, label: {
                    Text("Show modal")
                })
            }
            .navigationBarTitle("SwiftUI")
        }
        .edgesIgnoringSafeArea(.top)
        .sheet(isPresented: $isPresented) {
            Modal()
        }
    }
}

struct Modal: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        NavigationView {
            VStack(alignment: .center, spacing: 30) {
                NavigationLink(destination: Text("Detail"), label: {
                    Text("Show detail")
                })
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }, label: {
                    Text("Dismiss modal")
                })
            }
            .navigationBarTitle("Modal")
        }
    }
}


我从这看起来太简单了,以至于我必须忽略某些东西"开始,我想我找到了它.这似乎没有将EnvironmentObjects转移到下一个视图.我不知道默认的NavigationLink是如何做到的,所以现在我手动将对象发送到需要它们的下一个视图.


I started off with "This seems so simple that I must be overlooking something" and I think I found it. This doesn't seem to transfer EnvironmentObjects to the next view. I don't know how the default NavigationLink does that so for now I manually send objects on to the next view where I need them.

NavigationLink(destination: Text("Detail").environmentObject(objectToSendOnToTheNextView)) {
    Text("Show detail")
}

这通过执行@EnvironmentObject var nvc: UINavigationController将导航控制器公开给NavigationView内部的所有视图.解决此问题的方法是使用于管理导航的environmentObject成为fileprivate类.我在要点中对此进行了修复: https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb

This exposes the navigation controller to all views inside NavigationView by doing @EnvironmentObject var nvc: UINavigationController. The way to fix this is making the environmentObject we use to manage navigation a fileprivate class. I fixed this in the gist: https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb

这篇关于如何在SwiftUI中返回与UIKit(interactivePopGestureRecognizer)相同的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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