在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)
}
}
任何建议或解决方案都将受到赞赏
答案 0 :(得分:27)
这应该通过扩展UINavigationController
而起作用。
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
答案 1 :(得分:3)
我环顾了有关此问题的文档和其他来源,却一无所获。基于使用UIKit
和UIViewControllerRepresentable
的解决方案很少。我尝试结合解决方案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()
}
}
这里是SwipeBackNavController
和SwipeBackNavigationLink
的模仿NavigationView
和NavigationLink
的实现。它们只是SwipeNavigationController
工作的包装器。最后一个是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
如何推动视野,因此以后我将尝试继续进行研究。
答案 2 :(得分:3)
在使用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()))
}
}
}
答案 3 :(得分:2)
这比 Nick Bellucci 的回答还要简单。 这是最简单的工作解决方案:
extension UINavigationController {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = nil
}
}