如何在SwiftUI中返回与UIKit中相同的行为(interactivePopGestureRecognizer)

Cas*_*gen 8 ios swift swiftui

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

SwiftUI: https ://imgur.com/xxVnhY7

UIKit: https //imgur.com/f6WBUne


题:

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


尝试次数

我试图将UIHostingController嵌入UINavigationController内,但其行为与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) {}
}
Run Code Online (Sandbox Code Playgroud)

Cas*_*gen 4

我最终覆盖了默认值NavigationViewNavigationLink获得了所需的行为。这看起来很简单,我一定忽略了默认 SwiftUI 视图所做的事情?

导航视图

我将 a 包装UINavigationController在一个超级简单的文件中UIViewControllerRepresentable,将UINavigationControllerSwiftUI 内容视图作为环境对象提供。这意味着NavigationLink只要它位于同一个导航控制器中(呈现的视图控制器不接收环境对象),稍后就可以获取它,这正是我们想要的。

注意: NavigationView 需要.edgesIgnoringSafeArea(.top),但我还不知道如何在结构本身中设置它。如果您的 NVC 在顶部切断,请参阅示例。

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 {}
Run Code Online (Sandbox Code Playgroud)

导航链接

我创建了一个自定义的 NavigationLink,它访问环境 UINavigationController 以推送托管下一个视图的 UIHostingController。

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

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

例子

在示例中,我也展示了模式表示。

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:我一开始就说“这看起来很简单,我一定是忽略了一些东西”,我想我找到了它。这似乎没有将环境对象转移到下一个视图。我不知道默认的 NavigationLink 是如何做到这一点的,所以现在我手动将对象发送到我需要它们的下一个视图。

NavigationLink(destination: Text("Detail").environmentObject(objectToSendOnToTheNextView)) {
    Text("Show detail")
}
Run Code Online (Sandbox Code Playgroud)

编辑2:

NavigationView这通过执行以下操作将导航控制器公开给内部的所有视图@EnvironmentObject var nvc: UINavigationController。解决这个问题的方法是使我们用来管理导航的environmentObject成为一个文件私有类。我在要点中修复了这个问题:https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb