仅在显示为模式

时间:2020-03-06 14:16:13

标签: ios swiftui

我有一个视图,可以将其显示为模式视图,也可以将其简单地推到导航堆栈上。按下它时,它在左上角有一个后退按钮,当它显示为模式时,我想添加一个关闭按钮(许多测试人员不容易发现他们可以向下滑动模式,实际上应该有一个明确的关闭按钮)。

现在,我有多个问题。

  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?,然后在nil为假时返回isModal,则根本没有返回按钮。我也不能两次调用navigationBarItems,一次是使用前导按钮,还是是使用尾随按钮,因为后一个调用会覆盖第一个调用。我有点卡在这里。

3 个答案:

答案 0 :(得分: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()
  }
}

答案 1 :(得分:1)

我看不到任何麻烦,只需将“关闭”按钮添加到导航栏中。您只需要重新排列View层次结构,无需将任何绑定传递给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()
    }
}

enter image description here

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

按下后,您将拥有默认的后退按钮;如果显示模态,则将您关闭 按钮。

更新(基于讨论)

.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)
            )
        }
    }
}

enter image description here

最终我建议您使用

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)
            }
        }
    }
}

答案 2 :(得分:0)

只要我们提供.navigationBarItems(leading: _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)
}