ios - 如何在 SwiftUI 中恢复与 UIKit (interactivePopGestureRecognizer) 中相同的行为

标签 ios swift swiftui

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

SwiftUI: /image/shQl8.jpg

UIKit: /image/2EeIS.jpg

<小时/>

问题:

使用 SwiftUI View 时是否可以获取 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) {}
}

最佳答案

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

导航 View

我将 UINavigationController 包装在一个 super 简单的 UIViewControllerRepresentable 中,它将 UINavigationController 作为环境对象提供给 SwiftUI 内容 View 。这意味着 NavigationLink 稍后可以获取它,只要它位于同一个导航 Controller 中(呈现的 View Controller 不接收环境对象),这正是我们想要的。

注意: 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 {}

导航链接

我创建了一个自定义的 NavigationLink,它访问环境 UINavigationController 以推送托管下一个 View 的 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)
    }
}

这解决了向后滑动在 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()
        }
    }
}
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")
        }
    }
}
<小时/>

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

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

编辑2:

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

关于ios - 如何在 SwiftUI 中恢复与 UIKit (interactivePopGestureRecognizer) 中相同的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58234142/

相关文章:

ios - 如何快速调用 initWith...

ios - 核心位置 : CPAS data response was invaild

ios - 从 UIPageViewContoller 中包含的 View 设置 UINavigationBar 标题

objective-c - Parse.com includeKey 与 PFFile

swiftui - 在不使用 SwiftUI 生命周期的情况下,无法对 URL、NSUserActivity 和其他外部事件使用场景方法

ios - 无法使用 SwiftUI 实现暗模式

ios - 当具有不同值的同一类对象作为数据源传递时,为什么不重新呈现 View

ios - GLSL可以进行递归公式计算吗?或者我怎样才能加快这个公式

ios - UISearchController 在结构中搜索结构数组

swift - 如何让函数在某些情况下返回 1 个值并返回 2 或 3 个值,反之亦然?