从 SwiftUI 的导航栏中删除后退按钮文本

Remove back button text from navigationbar in SwiftUI

我最近开始使用 SwiftUI,得出的结论是使用导航还不是很好。我想要实现的是以下内容。我终于设法在没有使应用程序崩溃的情况下摆脱了 t运行slucent 背景,但现在我 运行 进入下一期。我怎样才能摆脱 navbaritem 中的 "back" 文本?

我通过像这样在 SceneDelegate.swift 文件中设置默认外观 运行ce 来实现上面的视图。

let newNavAppearance = UINavigationBarAppearance()
newNavAppearance.configureWithTransparentBackground()
newNavAppearance.setBackIndicatorImage(UIImage(named: "backButton"), transitionMaskImage: UIImage(named: "backButton"))
newNavAppearance.titleTextAttributes = [
    .font: UIFont(name: GTWalsheim.bold.name, size: 18)!,
    .backgroundColor: UIColor.white

]

UINavigationBar.appearance().standardAppearance = newNavAppearance

实现此目的的一种可能方法是覆盖导航栏项目,但是这有一个缺点 (SwiftUI Custom Back Button Text for NavigationView) 正如本期的创建者已经说过的那样,在您覆盖后返回手势停止工作导航栏项目。有了这个,我也想知道如何设置后退按钮的 foregroundColor 。它现在具有默认的蓝色,但是我想用另一种颜色覆盖它。

标准后退按钮标题取自上一屏幕的导航栏标题。

有可能通过以下方法获得所需的效果:

struct TestBackButtonTitle: View {
    @State private var hasTitle = true
    var body: some View {
        NavigationView {
            NavigationLink("Go", destination:
                Text("Details")
                    .onAppear {
                        self.hasTitle = false
                    }
                    .onDisappear {
                        self.hasTitle = true
                    }
            )
            .navigationBarTitle(self.hasTitle ? "Master" : "")
        }
    }
}

所以我实际上得到了以下实际有效的解决方案。我正在像这样覆盖导航栏项目

.navigationBarItems(leading:
    Image("backButton")
        .foregroundColor(.blue)
        .onTapGesture {
            self.presentationMode.wrappedValue.dismiss()
    }
)

唯一的问题是后退手势不起作用,因此通过实际扩展 UINavigationController

解决了这个问题
extension UINavigationController: UIGestureRecognizerDelegate {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

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

现在它看起来完全符合我想要的方式,解决方案有点老套...但它现在有效,希望 SwiftUI 会成熟一点,这样可以更容易地完成。

我找到了一种仅使用 SwiftUI 删除后退按钮文本并保留原始人字形的简单方法。

添加了拖动手势来模仿经典的导航后退按钮 当用户想通过向右滑动返回时。

import SwiftUI

struct ContentView: View {

  @Environment(\.presentationMode) var presentation

  var body: some View {
    ZStack {
      // Your main view code here with a ZStack to have the
      // gesture on all the view.
    }
    .navigationBarBackButtonHidden(true)
    .navigationBarItems(
      leading: Button(action: { presentation.wrappedValue.dismiss() }) {
        Image(systemName: "chevron.left")
          .foregroundColor(.blue)
          .imageScale(.large) })
    .contentShape(Rectangle()) // Start of the gesture to dismiss the navigation
    .gesture(
      DragGesture(coordinateSpace: .local)
        .onEnded { value in
          if value.translation.width > .zero
              && value.translation.height > -30
              && value.translation.height < 30 {
            presentation.wrappedValue.dismiss()
          }
        }
    )
  }
}

其实很简单。下面的解决方案是我做的最快最干净的。

例如,将其放在 SceneDelegate 的底部。

extension UINavigationController {
    // Remove back button text 
    open override func viewWillLayoutSubviews() {
        navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
    }
}

这将从您应用中的每个 NavigationView (UINavigationController) 中删除后退按钮文本。

借助@Pitchbloas 提供的解决方案,此方法仅涉及将 backButtonDisplayMode 属性 设置为 .minimal:

extension UINavigationController {

  open override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    navigationBar.topItem?.backButtonDisplayMode = .minimal
  }

}

自定义 navigationBarItems 和 self.presentationMode.wrappedValue.dismiss() 有效,但您不能执行向后滑动

您可以添加以下代码使滑动再次返回

//perform gesture go back
extension UINavigationController: UIGestureRecognizerDelegate {
    override open func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

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

但问题是,有时滑动半屏然后取消会导致应用崩溃。

我建议用另一种方法删除“返回”文本。 添加 isActive 状态以监控当前屏幕是否处于活动状态。 :)

struct ContentView: View {
    @State var isActive = false

    var body: some View {
        NavigationView() {
            NavigationLink(
                "Next",
                destination: Text("Second Page").navigationBarTitle("Second"),
                isActive: $isActive
            )
                .navigationBarTitle(!isActive ? "Title" : "", displayMode: .inline)
        }
    }
}

使用 Introspect 框架,您可以轻松访问底层导航项并将 backButtonDisplayMode 设置为最小。

以下是您可以如何在推送的视图中使用它

var body: some View {
    Text("Your body here")
        .introspectNavigationController { navController in
            navController.navigationBar.topItem?.backButtonDisplayMode = .minimal
        }
}

我通过在推送详细信息屏幕之前更改主屏幕的标题然后在它 re-appears 时将其设置回原位来实现此目的。唯一需要注意的是,当您返回主屏幕时,标题的 re-appearance 有点明显。

总结:

  • 在主视图上添加状态变量(例如 isDetailShowing)以存储详细信息屏幕是否显示

  • 在主视图上使用 navigationTitle 修饰符根据 isDetailShowing 的当前值设置标题

  • 在主视图上使用 onAppear 修饰符将 isDetailShowing 的值设置为 false

  • 在主屏幕的 NavigationLink 上使用 simultaneousGesture 修饰符将 isDetailShowing 设置为 true

    struct MasterView: View {
      @State var isDetailShowing = false
    
      var body: some View {
    
          VStack {
              Spacer()
                  .frame(height: 20)
              Text("Master Screen")
                  .frame(maxWidth: .infinity, alignment: .leading)
              Spacer()
                  .frame(height: 20)
    
              NavigationLink(destination: DetailView()) {
                  Text("Go to detail screen")
              }
              .simultaneousGesture(TapGesture().onEnded() {
                  isDetailShowing = true
              })
          }
          .navigationBarTitleDisplayMode(.inline)
          .navigationTitle(isDetailShowing ? "" : "Master Screen Title")
          .onAppear() {
              isDetailShowing = false
          }
      }
    }
    
    struct DetailView: View {
      var body: some View {
          Text("This is the detail screen")
          .navigationBarTitleDisplayMode(.inline)
          .navigationTitle("Detail Screen Title")
      }
    }