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

问题描述 投票:3回答:1

交互式弹出手势识别器应允许用户在滑过屏幕的一半以上(或这些行周围的内容)时返回导航堆栈中的上一个视图。在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) {}
}
ios swift swiftui
1个回答
0
投票
首先创建一个SwipeNavigationController文件:

import UIKit import SwiftUI final class SwipeNavigationController: UINavigationController { // MARK: - Lifecycle override init(rootViewController: UIViewController) { super.init(rootViewController: rootViewController) } override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) delegate = self } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) delegate = self } override func viewDidLoad() { super.viewDidLoad() // This needs to be in here, not in init interactivePopGestureRecognizer?.delegate = self } deinit { delegate = nil interactivePopGestureRecognizer?.delegate = nil } // MARK: - Overrides override func pushViewController(_ viewController: UIViewController, animated: Bool) { duringPushAnimation = true super.pushViewController(viewController, animated: animated) } var duringPushAnimation = false // MARK: - Custom Functions func pushSwipeBackView<Content>(_ content: Content) where Content: View { let hostingController = SwipeBackHostingController(rootView: content) self.delegate = hostingController self.pushViewController(hostingController, animated: true) } } // MARK: - UINavigationControllerDelegate extension SwipeNavigationController: UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { guard let swipeNavigationController = navigationController as? SwipeNavigationController else { return } swipeNavigationController.duringPushAnimation = false } } // MARK: - UIGestureRecognizerDelegate extension SwipeNavigationController: UIGestureRecognizerDelegate { func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { guard gestureRecognizer == interactivePopGestureRecognizer else { return true // default value } // Disable pop gesture in two situations: // 1) when the pop animation is in progress // 2) when user swipes quickly a couple of times and animations don't have time to be performed let result = viewControllers.count > 1 && duringPushAnimation == false return result } }

SwipeNavigationController提供的here相同,并附加了pushSwipeBackView()功能。

此功能需要一个SwipeBackHostingController,我们将其定义为

import SwiftUI class SwipeBackHostingController<Content: View>: UIHostingController<Content>, UINavigationControllerDelegate { func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { guard let swipeNavigationController = navigationController as? SwipeNavigationController else { return } swipeNavigationController.duringPushAnimation = false } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) guard let swipeNavigationController = navigationController as? SwipeNavigationController else { return } swipeNavigationController.delegate = nil } }

然后我们将应用程序的SceneDelegate设置为使用SwipeNavigationController

    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        let hostingController = UIHostingController(rootView: ContentView())
        window.rootViewController = SwipeNavigationController(rootViewController: hostingController)
        self.window = window
        window.makeKeyAndVisible()
    }

最后在ContentView中使用它:

struct ContentView: View {
    func navController() -> SwipeNavigationController {
        return UIApplication.shared.windows[0].rootViewController! as! SwipeNavigationController
    }

    var body: some View {
        VStack {
            Text("SwiftUI")
                .onTapGesture {
                    self.navController().pushSwipeBackView(Text("Detail"))
            }
        }.onAppear {
            self.navController().navigationBar.topItem?.title = "Swift UI"
        }.edgesIgnoringSafeArea(.top)
    }
}