【问题标题】:Show leading navigationBarItems button only if shown as a modal仅在显示为模式时才显示前导 navigationBarItems 按钮
【发布时间】:2020-03-06 14:16:13
【问题描述】:

我有一个可以显示为模式的视图,也可以简单地推送到导航堆栈上。当它被按下时,它在左上角有后退按钮,当它显示为模态时,我想添加一个关闭按钮(我的许多测试人员不容易弄清楚他们可以滑下模态并且真的需要一个明确的关闭按钮)。

现在,我有很多问题。

  1. 如何判断视图是否以模态方式显示?或者,如果它不是导航堆栈上的第一个视图?在 UIKit 中有多种方法可以轻松做到这一点。添加presentationMode @Environment 变量没有帮助,因为它的isPresented 值对于推送的屏幕也是如此。我当然可以自己传递一个isModal 变量,但似乎很奇怪这是唯一的方法?
  2. 如何有条件地添加前导 navigationBarItem?问题是如果你给nil,即使是默认的后退按钮也是隐藏的。

将代码复制并粘贴到 Xcode 中并使用:

import SwiftUI

struct ContentView: View {
  @State private var showModal = false

  var body: some View {
    NavigationView {
      VStack(spacing: 20) {
        Button("Open modally") {
          self.showModal = true
        }

        NavigationLink("Push", destination: DetailView(isModal: false))
      }
      .navigationBarTitle("Home")
    }
    .sheet(isPresented: $showModal) {
      NavigationView {
        DetailView(isModal: true)
      }
    }
  }
}

struct DetailView: View {
  @Environment(\.presentationMode) private var presentationMode
  let isModal: Bool

  var body: some View {
    Text("Hello World")
      .navigationBarTitle(Text("Detail"), displayMode: .inline)
      .navigationBarItems(leading: closeButton, trailing: deleteButton)
  }

  private var closeButton: some View {
    Button(action: { self.presentationMode.wrappedValue.dismiss() }) {
      Image(systemName: "xmark")
        .frame(height: 36)
    }
  }

  private var deleteButton: some View {
    Button(action: { print("DELETE") }) {
      Image(systemName: "trash")
        .frame(height: 36)
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

如果我将closeButton 更改为返回可选的AnyView?,然后在isModal 为假时返回nil,我根本没有返回按钮。我也不能调用navigationBarItems 两次,一次是前导按钮,一次是尾随按钮,因为后一个调用会覆盖第一个调用。我有点卡在这里。

【问题讨论】:

  • 关于整个视图层次结构中的跟踪模式,您可以考虑Dismiss a parent modal in SwiftUI from a NavigationView主题中提供的方法
  • 请在继续之前,看看为什么在工作表中使用 NavigationView 是错误的想法stackoverflow.com/questions/60468040/… 如果向下滑动不起作用,请添加一些关闭按钮,不要使用导航堆栈
  • 我在模式中使用 NavigationView,因为我想在导航栏中显示标题和一些按钮。不是因为我要将辅助屏幕推送到模态的导航堆栈上。对我来说似乎非常好,而不是我想要改变的东西。
  • 这种情况,看我的回答

标签: ios swiftui


【解决方案1】:

好的,我做到了。它不漂亮,我非常愿意接受不同的建议,但它有效?

import SwiftUI

extension View {
  func eraseToAnyView() -> AnyView {
    AnyView(self)
  }

  public func conditionalNavigationBarItems(_ condition: Bool, leading: AnyView, trailing: AnyView) -> some View {
    Group {
      if condition {
        self.navigationBarItems(leading: leading, trailing: trailing)
      } else {
        self
      }
    }
  }
}

struct ContentView: View {
  @State private var showModal = false

  var body: some View {
    NavigationView {
      VStack(spacing: 20) {
        Button("Open modally") {
          self.showModal = true
        }

        NavigationLink("Push", destination: DetailView(isModal: false))
      }
      .navigationBarTitle("Home")
    }
    .sheet(isPresented: $showModal) {
      NavigationView {
        DetailView(isModal: true)
      }
    }
  }
}

struct DetailView: View {
  @Environment(\.presentationMode) private var presentationMode
  let isModal: Bool

  var body: some View {
    Text("Hello World")
      .navigationBarTitle(Text("Detail"), displayMode: .inline)
      .navigationBarItems(trailing: deleteButton)
      .conditionalNavigationBarItems(isModal, leading: closeButton, trailing: deleteButton)
  }

  private var closeButton: AnyView {
    Button(action: { self.presentationMode.wrappedValue.dismiss() }) {
      Image(systemName: "xmark")
        .frame(height: 36)
    }.eraseToAnyView()
  }

  private var deleteButton: AnyView {
    Button(action: { print("DELETE") }) {
      Image(systemName: "trash")
        .frame(height: 36)
    }.eraseToAnyView()
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

【讨论】:

  • 如果将conditionalNavigationBarItems 标头更改为使用泛型,则不再需要使用eraseToAnyView 扩展方法:public func conditionalNavigationBarItems<L, T>(_ condition: Bool, leading: L, trailing: T) -> some View where L : View, T : View
【解决方案2】:

我没有发现任何问题,只需将关闭按钮添加到您的导航栏即可。您只需重新排列您的视图层次结构,无需将任何绑定传递给您的 DetailView

import SwiftUI

struct DetailView: View {
    var body: some View {
        Text("Detail View")
    }
}
struct ContentView: View {
    @State var sheet = false
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                Button("Open modally") {
                    self.sheet = true
                }

                NavigationLink("Push", destination: DetailView())
            }.navigationBarTitle("Home")
        }
        .sheet(isPresented: $sheet) {
            NavigationView {
                DetailView().navigationBarTitle("Title").navigationBarItems(leading: Button(action: {
                    self.sheet.toggle()
                }, label: {
                    Text("Dismiss")
                }))
            }
        }
    }
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

你仍然可以通过向下滑动来关闭它,你可以添加一些按钮(作为 DetailView 声明的一部分)......等等。

按下时,你有默认的返回按钮,如果显示模式,你有关闭 确实是按钮。

更新(基于 od 讨论)

.sheet(isPresented: $sheet) {
    NavigationView {
        GeometryReader { proxy in
            DetailView().navigationBarTitle("Title")
                .navigationBarItems(leading:
                    HStack {

                        Button(action: {
                            self.sheet.toggle()
                        }, label: {
                            Text("Dismiss").padding(.horizontal)
                        })
                        Color.clear
                        Button(action: {

                        }, label: {
                            Image(systemName: "trash")
                                .imageScale(.large)
                                .padding(.horizontal)
                        })

                    }.frame(width: proxy.size.width)
            )
        }
    }
}

最后我建议你使用

extension View {
    @available(watchOS, unavailable)
    public func navigationBarItems<L, T>(leading: L?, trailing: T) -> some View where L : View, T : View {
        Group {
            if leading != nil {
                self.navigationBarItems(leading: leading!, trailing: trailing)
            } else {
                self.navigationBarItems(trailing: trailing)
            }
        }
    }
}

【讨论】:

  • 一开始我也在考虑同样的解决方案,但是当你还想显示一个尾随按钮时不能使用它,因为在工作表中添加前导 navigationBarItems 可以摆脱详细视图本身内的尾随项。但是,当您想要的只是一个可选的关闭按钮时,是的,这是一个很好的方法!
  • @KevinRenskers 将所有操作按钮放在 HStack 中,并用分隔符分隔前导组和尾随组。
  • @KevinRenskers 我在“今天”项目中使用的另一个更新(查看扩展)
【解决方案3】:

每当我们提供.navigationBarItems(leading: _anything_),即anything时,标准的后退按钮已经消失,因此您必须有条件地提供自己的后退按钮。

以下方法有效(使用 Xcode 11.2 / iOS 13.2 测试)

    .navigationBarItems(leading: Group {
        if isModal {
            closeButton
        } else {
            // custom back button here calling same dismiss
        }
    }, trailing: deleteButton)

更新:替代方法可能如下(测试相同)

var body: some View {
    VStack {
        if isModal {
            Text("Hello")
                .navigationBarItems(leading: closeButton, trailing: deleteButton)
        } else {
            Text("Hello")
                .navigationBarItems(trailing: deleteButton)
        }
    }
    .navigationBarTitle("Test", displayMode: .inline)
}

【讨论】:

  • 是的,但是你必须创建自己的后退按钮,这与其他页面不匹配,动画不同等等。
  • @KevinRenskers,查看使用替代方法的更新。
  • 是的,这也是我通过conditionalNavigationBarItems 得到的结果,所以我不必将整个视图层次结构写两次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-19
  • 2020-05-28
  • 2017-02-14
  • 2017-04-14
  • 2020-07-07
  • 2012-05-01
  • 2017-09-25
相关资源
最近更新 更多