【问题标题】:Custom back button for NavigationView's navigation bar in SwiftUISwiftUI 中 NavigationView 导航栏的自定义后退按钮
【发布时间】:2019-10-27 12:11:28
【问题描述】:

我想添加一个看起来有点像这样的自定义导航按钮:

现在,我为此编写了一个自定义 BackButton 视图。将该视图应用为前导导航栏项目时,执行以下操作:

.navigationBarItems(leading: BackButton())

...导航视图如下所示:

我玩过以下修饰符:

.navigationBarItem(title: Text(""), titleDisplayMode: .automatic, hidesBackButton: true)

运气不好。

问题

我怎么能...

  1. 在导航栏中设置一个用作自定义后退按钮的视图?或者:
  2. 以编程方式将视图弹出回其父级?
    采用这种方法时,我可以使用 .navigationBarHidden(true) 完全隐藏导航栏

【问题讨论】:

标签: swift navigationview swiftui


【解决方案1】:

TL;DR

使用它来转换你的视图:

NavigationLink(destination: SampleDetails()) {}

将此添加到视图本身:

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

然后,在按钮操作或其他内容中,关闭视图:

presentationMode.wrappedValue.dismiss()

完整代码

从父级,使用NavigationLink导航

 NavigationLink(destination: SampleDetails()) {}

在DetailsView中隐藏navigationBarBackButton并将自定义后退按钮设置为前导navigationBarItem

struct SampleDetails: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var btnBack : some View { Button(action: {
        self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack {
            Image("ic_back") // set image here
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.white)
                Text("Go back")
            }
        }
    }
    
    var body: some View {
            List {
                Text("sample code")
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: btnBack)
    }
}

【讨论】:

  • 这很好用,除了presentationMode.value 现在是presentationMode.wrappedValue,但是,这似乎禁用了默认的滑动返回行为。关于如何再次启用它的任何想法?
  • 知道如何添加回扫功能吗?
  • 谢谢,工作正常,但我们必须注意图像大小。永远记住 .resizable() 自定义帧大小的方法。
  • 当你想在 Parent 中执行 .onAppear 时,使用实际的后退按钮导航它会起作用。但是使用您的代码返回按钮,它将不起作用
【解决方案2】:

SwiftUI 1.0

看来您现在可以组合navigationBarBackButtonHidden.navigationBarItems 来获得您想要达到的效果。

代码

struct Navigation_CustomBackButton_Detail: View {
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        ZStack {
            Color("Theme3BackgroundColor")
            VStack(spacing: 25) {
                Image(systemName: "globe").font(.largeTitle)
                Text("NavigationView").font(.largeTitle)
                Text("Custom Back Button").foregroundColor(.gray)
                HStack {
                    Image("NavBarBackButtonHidden")
                    Image(systemName: "plus")
                    Image("NavBarItems")
                }
                Text("Hide the system back button and then use the navigation bar items modifier to add your own.")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color("Theme3ForegroundColor"))
                    .foregroundColor(Color("Theme3BackgroundColor"))
                
                Spacer()
            }
            .font(.title)
            .padding(.top, 50)
        }
        .navigationBarTitle(Text("Detail View"), displayMode: .inline)
        .edgesIgnoringSafeArea(.bottom)
        // Hide the system back button
        .navigationBarBackButtonHidden(true)
        // Add your custom back button here
        .navigationBarItems(leading:
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
                HStack {
                    Image(systemName: "arrow.left.circle")
                    Text("Go Back")
                }
        })
    }
}

示例

这是它的样子(摘自“SwiftUI Views”一书):

【讨论】:

    【解决方案3】:

    根据此处的其他答案,这是选项 2 在 XCode 11.0 中为我工作的简化答案:

    struct DetailView: View {
        @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
        var body: some View {
    
            Button(action: {
               self.presentationMode.wrappedValue.dismiss()
            }) {
                Image(systemName: "gobackward").padding()
            }
            .navigationBarHidden(true)
    
        }
    }
    

    注意:要隐藏 NavigationBar,我还需要在 ContentView 中设置并隐藏 NavigationBar。

    struct ContentView: View {
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink(destination: DetailView()) {
                        Text("Link").padding()
                    }
                } // Main VStack
                .navigationBarTitle("Home")
                .navigationBarHidden(true)
    
            } //NavigationView
        }
    }
    

    【讨论】:

    • 在 XCode 11 中的工作就像一个魅力!谢谢
    【解决方案4】:

    这是一个更精简的版本,它使用其他 cmets 中显示的原则仅更改按钮的文本。 chevron.left 图标也可以轻松替换为另一个图标。

    创建您自己的按钮,然后使用 .navigationBarItems() 分配它。我发现以下格式最接近默认的后退按钮。

        @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    
        var backButton : some View {
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
                HStack(spacing: 0) {
                    Image(systemName: "chevron.left")
                        .font(.title2)
                    Text("Cancel")
                }
            }
        }
    

    确保您使用.navigationBarBackButtonHidden(true) 隐藏默认按钮并替换为您自己的!

            List(series, id:\.self, selection: $selection) { series in
                Text(series.SeriesLabel)
            }
            .navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: backButton)
    

    【讨论】:

      【解决方案5】:

      我希望您想在所有可导航屏幕中使用自定义后退按钮, 所以我根据@Ashish 的答案编写了自定义包装器。

      struct NavigationItemContainer<Content>: View where Content: View {
          private let content: () -> Content
          @Environment(\.presentationMode) var presentationMode
      
          private var btnBack : some View { Button(action: {
              self.presentationMode.wrappedValue.dismiss()
          }) {
              HStack {
                  Image("back_icon") // set image here
                      .aspectRatio(contentMode: .fit)
                      .foregroundColor(.black)
                  Text("Go back")
              }
              }
          }
      
          var body: some View {
              content()
                  .navigationBarBackButtonHidden(true)
                  .navigationBarItems(leading: btnBack)
          }
      
          init(@ViewBuilder content: @escaping () -> Content) {
              self.content = content
          }
      }
      
      

      在 NavigationItemContainer 中包装屏幕内容:

      用法:

      struct CreateAccountScreenView: View {
          var body: some View {
              NavigationItemContainer {
                  VStack(spacing: 21) {
                      AppLogoView()
                      //...
                  }
              }
          }
      }
      

      【讨论】:

      • 您的 CreateAccountScreenView 有问题。如果CreateAccountScreenView 中有其他导航尾随项,则只有尾随或前导导航项。这是因为您不能定义两次 navigationBarItems 并且只有一个有效
      【解决方案6】:

      这种方式不会禁用滑动。

      为我工作。 XCode 11.3.1

      把它放在你的根视图中

      init() {
          UINavigationBar.appearance().isUserInteractionEnabled = false
          UINavigationBar.appearance().backgroundColor = .clear
          UINavigationBar.appearance().barTintColor = .clear
          UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
          UINavigationBar.appearance().shadowImage = UIImage()
          UINavigationBar.appearance().tintColor = .clear
      }
      

      这在你的孩子视图中

      @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
      
      Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
          Image(systemName: "gobackward")
      }
      

      【讨论】:

      • 谢谢!这是适用于 iOS 13 和 iOS 14 的绝佳解决方案。在 Xcode 12.5.1 中工作
      【解决方案7】:

      我在这里看到的所有解决方案似乎都禁用了滑动返回功能以导航到上一页,因此分享我发现的维护该功能的解决方案。您可以扩展您的根视图并覆盖您的导航样式并在视图初始化程序中调用该函数。

      示例视图

      struct SampleRootView: View {
      
          init() {
              overrideNavigationAppearance()
          }
      
          var body: some View {
              Text("Hello, World!")
          }
      }
      

      扩展

      extension SampleRootView {
         func overrideNavigationAppearance() {
              let navigationBarAppearance = UINavigationBarAppearance()
              let barAppearace = UINavigationBar.appearance()
              barAppearace.tintColor = *desired UIColor for icon*
              barAppearace.barTintColor = *desired UIColor for icon*
      
              navigationBarAppearance.setBackIndicatorImage(*desired UIImage for custom icon*, transitionMaskImage: *desired UIImage for custom icon*)
      
              UINavigationBar.appearance().standardAppearance = navigationBarAppearance
              UINavigationBar.appearance().compactAppearance = navigationBarAppearance
              UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
         }
      }
      

      这种方法的唯一缺点是我没有找到删除/更改与自定义后退按钮关联的文本的方法。

      【讨论】:

        【解决方案8】:

        您可以为此使用UIAppearance

        if let image = UIImage(named: "back-button") {
            UINavigationBar.appearance().backIndicatorImage = image
            UINavigationBar.appearance().backIndicatorTransitionMaskImage = image
        }
        

        这应该像App.init 这样在您的应用程序中尽早添加。这也保留了原生的向后滑动功能。

        【讨论】:

          【解决方案9】:

          此解决方案适用于 iPhone。但是,对于 iPad,它会因为 splitView 而无法工作。

          import SwiftUI
          
          struct NavigationBackButton: View {
            var title: Text?
            @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
          
            var body: some View {
              ZStack {
                VStack {
                  ZStack {
                    HStack {
                      Button(action: {
                        self.presentationMode.wrappedValue.dismiss()
                      }) {
                        Image(systemName: "chevron.left")
                          .font(.title)
                          .frame(width: 44, height: 44)
                        title
                      }
                      Spacer()
                    }
                  }
                  Spacer()
                }
              }
              .zIndex(1)
              .navigationBarTitle("")
              .navigationBarHidden(true)
            }
          }
          
          struct NavigationBackButton_Previews: PreviewProvider {
            static var previews: some View {
              NavigationBackButton()
            }
          }
          

          【讨论】:

            【解决方案10】:

            非常简单的方法。只有两行代码?

            @Environment(\.presentationMode) var presentationMode
            self.presentationMode.wrappedValue.dismiss()
            

            例子:

            import SwiftUI
            
            struct FirstView: View {
                @State var showSecondView = false
                
                var body: some View {
                    NavigationLink(destination: SecondView(),isActive : self.$showSecondView){
                        Text("Push to Second View")
                    }
                }
            }
            
            
            struct SecondView : View{
                @Environment(\.presentationMode) var presentationMode
            
                var body : some View {    
                    Button(action:{ self.presentationMode.wrappedValue.dismiss() }){
                        Text("Go Back")    
                    }    
                }
            }
            

            【讨论】:

              【解决方案11】:

              我发现了这个:https://ryanashcraft.me/swiftui-programmatic-navigation/

              它确实有效,它可以为状态机控制显示的内容奠定基础,但它并不像以前那样简单。

              import Combine
              import SwiftUI
              
              struct DetailView: View {
                  var onDismiss: () -> Void
              
                  var body: some View {
                      Button(
                          "Here are details. Tap to go back.",
                          action: self.onDismiss
                      )
                  }
              }
              
              struct RootView: View {
                  var link: NavigationDestinationLink<DetailView>
                  var publisher: AnyPublisher<Void, Never>
              
                  init() {
                      let publisher = PassthroughSubject<Void, Never>()
                      self.link = NavigationDestinationLink(
                          DetailView(onDismiss: { publisher.send() }),
                          isDetail: false
                      )
                      self.publisher = publisher.eraseToAnyPublisher()
                  }
              
                  var body: some View {
                      VStack {
                          Button("I am root. Tap for more details.", action: {
                              self.link.presented?.value = true
                          })
                      }
                          .onReceive(publisher, perform: { _ in
                              self.link.presented?.value = false
                          })
                  }
              }
              
              struct ContentView: View {
                  var body: some View {
                      NavigationView {
                          RootView()
                      }
                  }
              }
              
              If you want to hide the button then you can replace the DetailView with this:
              
              struct LocalDetailView: View {
                  var onDismiss: () -> Void
              
                  var body: some View {
                      Button(
                          "Here are details. Tap to go back.",
                          action: self.onDismiss
                      )
                          .navigationBarItems(leading: Text(""))
                  }
              }
              

              【讨论】:

              • 你知道你可以编辑你的答案吗?不要发布新的,而是单击答案底部的小edit button。然后,删除这个。
              • 改进版。 (斯威夫特,iOS 13 beta 4)stackoverflow.com/questions/56853828/…
              • 有趣的是,我因为发布链接到严重限制我的帐户而受到抨击,但最后一个答案完美地通过了。
              【解决方案12】:

              就这样写吧:

              import SwiftUI
              
              struct ContentView: View {
                  var body: some View {
                      NavigationView {
              
                      }.onAppear() {
                          UINavigationBar.appearance().tintColor = .clear
                          UINavigationBar.appearance().backIndicatorImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
                          UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
                      }
                  }
              }
              

              【讨论】:

              • 它有点“有效”,但新的后退按钮图像未正确对齐。代码可能需要一些调整。另外,也许将它放在 init() {} 中可能会更好?
              猜你喜欢
              • 1970-01-01
              • 2021-09-08
              • 2020-03-13
              • 1970-01-01
              • 2016-04-01
              • 2012-01-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多