How to give back swipe gesture in SwiftUI the same behaviour as in UIKit (interactivePopGestureRecognizer)

后端 未结 2 1753
独厮守ぢ
独厮守ぢ 2020-12-29 10:46

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

2条回答
  •  挽巷
    挽巷 (楼主)
    2020-12-29 10:54

    You can do this by descending into UIKit and using your own UINavigationController.

    First create a SwipeNavigationController file:

    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) 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
        }
    }
    

    This is the same SwipeNavigationController provided here, with the addition of the pushSwipeBackView() function.

    This function requires a SwipeBackHostingController which we define as

    import SwiftUI
    
    class SwipeBackHostingController: UIHostingController, 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
        }
    }
    

    We then set up the app's SceneDelegate to use the 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()
        }
    

    Finally use it in your 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)
        }
    }
    

提交回复
热议问题