在SwiftUI中隐藏导航栏而不丢失向后轻扫手势

nsc4cvqm  于 2023-03-11  发布在  Swift
关注(0)|答案(5)|浏览(172)

在SwiftUI中,只要导航栏被隐藏,滑动返回手势也会被禁用。
有没有办法在SwiftUI中隐藏导航栏的同时保留向后滑动的手势?我已经有了一个自定义的“后退”按钮,但仍然需要这个手势。
我见过一些UIKit的解决方案,但仍然不知道如何在SwiftUI中实现
下面是代码来试试自己:

import SwiftUI

struct RootView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: SecondView()) {
                Text("Go to second view")
            }
        }
    }
}

struct SecondView: View {
    var body: some View{
        Text("As you can see, swipe to go back will not work")
        .navigationBarTitle("")
        .navigationBarHidden(true)
    }
}

如有任何建议或解决方案,我们将不胜感激

xmq68pz9

xmq68pz91#

只需扩展UINavigationController就可以了。

extension UINavigationController: UIGestureRecognizerDelegate {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return viewControllers.count > 1
    }
}
mqkwyuun

mqkwyuun2#

这甚至比尼克·贝鲁奇的回答更简单。下面是最简单的工作解决方案:

extension UINavigationController {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = nil
    }
}
vmdwslir

vmdwslir3#

使用UINavigationController扩展时,您可能会遇到一个错误,即在您开始滑动屏幕并在不返回的情况下放开屏幕后阻止导航。将.navigationViewStyle(StackNavigationViewStyle())添加到NavigationView确实可以修复此问题。
如果您需要基于设备的不同视图样式,此扩展可帮助您:

extension View {
    public func currentDeviceNavigationViewStyle() -> AnyView {
        if UIDevice.current.userInterfaceIdiom == .pad {
            return AnyView(self.navigationViewStyle(DefaultNavigationViewStyle()))
        } else {
            return AnyView(self.navigationViewStyle(StackNavigationViewStyle()))
        }
    }
}
f2uvfpb9

f2uvfpb94#

我查阅了关于这个问题的文档和其他来源,没有发现任何问题。只有几个解决方案,基于使用UIKitUIViewControllerRepresentable。我尝试合并解决方案from this question,我保存了向后滑动手势,即使用其他视图替换了后退按钮。代码仍然有点脏,但我认为这是进一步研究的起点(例如,完全隐藏导航栏)。下面是ContentView的外观:

import SwiftUI

struct ContentView: View {

    var body: some View {

        SwipeBackNavController {

            SwipeBackNavigationLink(destination: DetailViewWithCustomBackButton()) {
                Text("Main view")
            }
            .navigationBarTitle("Standard SwiftUI nav view")

        }
        .edgesIgnoringSafeArea(.top)

    }

}

// MARK: detail view with custom back button
struct DetailViewWithCustomBackButton: View {

    @Environment(\.presentationMode) var presentationMode

    var body: some View {

        Text("detail")
            .navigationBarItems(leading: Button(action: {
                self.dismissView()
            }) {
                HStack {
                    Image(systemName: "return")
                    Text("Back")
                }
            })
        .navigationBarTitle("Detailed view")

    }

    private func dismissView() {
        presentationMode.wrappedValue.dismiss()
    }

}

下面是SwipeBackNavControllerSwipeBackNavigationLink的实现,它们模拟了NavigationViewNavigationLink,它们只是SwipeNavigationController工作的 Package 器,最后一个是UINavigationController的子类,可以根据需要定制:

import UIKit
import SwiftUI

struct SwipeBackNavController<Content: View>: UIViewControllerRepresentable {

    let content: Content

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

    func makeUIViewController(context: Context) -> SwipeNavigationController {
        let hostingController = UIHostingController(rootView: content)
        let swipeBackNavController = SwipeNavigationController(rootViewController: hostingController)
        return swipeBackNavController
    }

    func updateUIViewController(_ pageViewController: SwipeNavigationController, context: Context) {

    }

}

struct SwipeBackNavigationLink<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
    }

    var body: some View {
        Button(action: {
            guard let window = UIApplication.shared.windows.first else { return }
            guard let swipeBackNavController = window.rootViewController?.children.first as? SwipeNavigationController else { return }
            swipeBackNavController.pushSwipeBackView(DetailViewWithCustomBackButton())
        }, label: label)
    }
}

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
        setNavigationBarHidden(true, animated: false)
        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
    }
}

// MARK: Hosting controller
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
    }
}

这个实现现在提供了保存自定义后退按钮和向后滑动手势。我仍然不喜欢一些时刻,像SwipeBackNavigationLink如何推视图,所以稍后我会尝试继续研究。

aydmsdu9

aydmsdu95#

采用@Nick Bellucci解决方案,但并非适用于所有屏幕,
创建AppState类

class AppState {
    static let shared = AppState()

    var swipeEnabled = false
}

添加nick的扩展名(已修改)

extension UINavigationController: UIGestureRecognizerDelegate {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if AppState.shared.swipeEnabled {
            return viewControllers.count > 1
        }
        return false
    }
    
}

你的观点

struct YourSwiftUIView: View {
    var body: some View {
        VStack {
            // your code
        }
        .onAppear {
            AppState.shared.swipeEnabled = false
        }
        .onDisappear {
            AppState.shared.swipeEnabled = true
        }
    }
    
}

相关问题