【问题标题】:SwiftUI - Is there a popViewController equivalent in SwiftUI?SwiftUI - SwiftUI 中是否有等效的 popViewController?
【发布时间】:2019-10-22 21:08:49
【问题描述】:

我在玩 SwiftUI,希望在点击按钮时能够返回到上一个视图,就像我们在 UINavigationController 中使用 popViewController 一样。 到目前为止有没有提供的方法?

我也尝试过使用NavigationDestinationLink 这样做,但没有成功。

struct AView: View {
    var body: some View {
        NavigationView {
            NavigationButton(destination: BView()) {
                Text("Go to B")
            }
        }
    }
}

struct BView: View {
    var body: some View {
        Button(action: {
            // Trying to go back to the previous view
            // previously: navigationController.popViewController(animated: true)
        }) {
            Text("Come back to A")
        }
    }
}

【问题讨论】:

  • Apple 可能希望阻止这一点,因此返回按钮是返回的唯一方法。然而,这似乎是一个疏忽。
  • 有条件地显示一个视图或另一个视图会起作用吗?还是必须使用NavigationView
  • @zoecarver 这个想法是让第二个视图添加一个项目(例如一篇博客文章),并带有一个保存按钮以使用我的新项目返回主视图或返回按钮以取消它.这只是我习惯的一种方式,但我可能可以将该视图嵌入到弹出窗口或其他内容中。
  • 您可以尝试类似.presentation(boolCheck ? BView() : nil) 的方法(但不确定这是否可行)。其中boolCheck 是一个有状态变量。
  • 他们不鼓励这样做对我来说毫无意义。当您选择列表中的项目时,它们的原生 Picker 视图会显示弹出行为。似乎 API 尚未最终确定,并且缺少某些内容。

标签: swift swiftui


【解决方案1】:

您也可以使用.sheet 进行操作

.navigationBarItems(trailing: Button(action: {
            self.presentingEditView.toggle()
        }) {
            Image(systemName: "square.and.pencil")
        }.sheet(isPresented: $presentingEditView) {
            EditItemView()
        })

在我的情况下,我从右侧导航栏项目中使用它,然后您必须创建要在该模式视图中显示的视图(在我的情况下为 EditItemView())。

https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:)

【讨论】:

    【解决方案2】:

    带有状态变量。试试看。

    struct ContentViewRoot: View {
        @State var pushed: Bool = false
        var body: some View {
            NavigationView{
                VStack{
                    NavigationLink(destination:ContentViewFirst(pushed: self.$pushed), isActive: self.$pushed) { EmptyView() }
                        .navigationBarTitle("Root")
                    Button("push"){
                        self.pushed = true
                    }
                }
            }
            .navigationViewStyle(StackNavigationViewStyle())
        }
    }
    
    
    struct ContentViewFirst: View {
        @Binding var pushed: Bool
        @State var secondPushed: Bool = false
        var body: some View {
            VStack{
                NavigationLink(destination: ContentViewSecond(pushed: self.$pushed, secondPushed: self.$secondPushed), isActive: self.$secondPushed) { EmptyView() }
                    .navigationBarTitle("1st")
                Button("push"){
                    self.secondPushed = true;
                }
            }
        }
    }
    
    
    
    struct ContentViewSecond: View {
        @Binding var pushed: Bool
        @Binding var secondPushed: Bool
    
        var body: some View {
            VStack{
                Spacer()
                Button("PopToRoot"){
                    self.pushed = false
                } .navigationBarTitle("2st")
    
                Spacer()
                Button("Pop"){
                             self.secondPushed = false
                         } .navigationBarTitle("1st")
                Spacer()
            }
        }
    }
    

    【讨论】:

    • 这是一个很好的答案,我只想补充一点,如果你在表单或列表中有这个,带有 EmptyView 的 NavigationLink 将占据一行。因此,为避免出现空行,应将 NavigationLink 和 Button 放在 ZStack 中。
    【解决方案3】:

    使用@Environment(\.presentationMode) var presentationMode 返回上一个视图。检查下面的代码以获得更多理解。

    import SwiftUI
    
    struct ContentView: View {
    
    
        var body: some View {
    
            NavigationView {
                ZStack {
                    Color.gray.opacity(0.2)
    
                    NavigationLink(destination: NextView(), label: {Text("Go to Next View").font(.largeTitle)})
                }.navigationBarTitle(Text("This is Navigation"), displayMode: .large)
                    .edgesIgnoringSafeArea(.bottom)
            }
        }
    }
    
    struct NextView: View {
        @Environment(\.presentationMode) var presentationMode
        var body: some View {
            ZStack {
                Color.gray.opacity(0.2)
            }.navigationBarBackButtonHidden(true)
                .navigationBarItems(leading: Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }, label: { Image(systemName: "arrow.left") }))
                .navigationBarTitle("", displayMode: .inline)
        }
    }
    
    
    struct NameRow: View {
        var name: String
        var body: some View {
            HStack {
                Image(systemName: "circle.fill").foregroundColor(Color.green)
                Text(name)
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    【讨论】:

    • 用按钮视图操作尝试了这一点,效果也很好。
    • update.. 使用:@Environment(\.dismiss) var dismiss 并在行动中使用:dismiss()
    【解决方案4】:

    以下在 XCode11 GM 中适用于我

    self.myPresentationMode.wrappedValue.dismiss()
    

    【讨论】:

    • PresentationMode 已被弃用。
    【解决方案5】:

    在目标中传递您要重定向的视图,并在块中传递数据以传递到另一个视图。

    NavigationLink(destination: "Pass the particuter View") {
        Text("Push")
    }
    

    【讨论】:

      【解决方案6】:

      如下修改你的 BView 结构。该按钮将像 UIKit 中的 popViewController 一样执行。

      struct BView: View {
          @Environment(\.presentationMode) var mode: Binding<PresentationMode>
          var body: some View {
              Button(action: { self.mode.wrappedValue.dismiss() })
              { Text("Come back to A") }
          }
      }
      

      【讨论】:

      • 这个解决方案对我有用。我也尝试了其他模型,但是当您在列表中使用其他解决方案之一时,详细视图的启动次数与我在列表中的记录一样多。这可能是因为@state 变量不是特定于元素的,而是主视图的“全局”变量。
      • value 现在已更改为 wrappedValue
      • 由于类型推断,@Environment(\.presentationMode) var mode 就足够了。
      • 谢谢。你是对的。尽管没有必要,但为了清楚起见,我将其包括在内,以帮助阅读答案的人了解实际发生的情况。
      • 是否可以从视图模型@ObservedObject 中弹出视图?
      【解决方案7】:

      这似乎在 watchOS 上对我有用(尚未在 iOS 上尝试过):

      @Environment(\.presentationMode) var presentationMode
      

      然后当你需要弹出时

      self.presentationMode.wrappedValue.dismiss()
      

      【讨论】:

      • 这样做会在 1/2 秒后自动弹出视图
      【解决方案8】:

      更新:自 iOS 13 Beta 5 起,此解决方案中的 NavigationDestinationLink API 已被弃用。现在建议将 NavigationLink 与 isActive 绑定一起使用。

      我想出了一个使用 NavigationDestinationLink 在 NavigationView 中以编程方式推送/弹出视图的解决方案。

      这是一个简单的例子:

      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 MainView: 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 RootView: View {
          var body: some View {
              NavigationView {
                  MainView()
              }
          }
      }
      

      我在一篇博文here 中写过这个。

      【讨论】:

      • beta 4 错误:“AnyPublisher”类型的值没有成员“发送”
      • @Ryan Ashcraft .!当我们有 TextField 和按钮时,这个场景不起作用。如果 textfield 已经填满,导航可以工作,但是当我们在 TextField 上书写并按下按钮时,应用程序被挂起并且什么也没发生,我们的操作不起作用。我的场景是登录视图..
      【解决方案9】:

      如果您愿意,现在可以通过编程方式弹出 NavigationView。这是 beta 5。

      请注意,您不需要返回按钮。您可以以任何您喜欢的方式以编程方式触发 DetailView 中的 showSelf 属性。而且您不必在母版中显示“推送”文本。这可能是一个 EmptyView(),从而创建了一个不可见的 segue。

      (新的 NavigationLink 功能接管了已弃用的 NavigationDestinationLink)

      import SwiftUI
      
      struct ContentView: View {
          var body: some View {
              NavigationView {
                  MasterView()
              }
          }
      }
      
      struct MasterView: View {
          @State var showDetail = false
      
          var body: some View {
              VStack {
                  NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
                      Text("Push")
                  }
              }
          }
      }
      
      struct DetailView: View {
          @Binding var showSelf: Bool
      
          var body: some View {
              Button(action: {
                  self.showSelf = false
              }) {
                  Text("Pop")
              }
          }
      }
      
      #if DEBUG
      struct ContentView_Previews: PreviewProvider {
          static var previews: some View {
              ContentView()
          }
      }
      #endif
      

      【讨论】:

      • 这种方法只有在使用 Pop 按钮时才有效。如果点击返回按钮,showSelf 变量不会切换回 false,MasterView 将再次自动推送 DetailView。请参阅下面的答案以获得更清洁的整体解决方案。
      • 我发现这对我有用。我可以使用后退按钮或弹出按钮并获得预期的结果。
      【解决方案10】:

      使用 NavigationDestinationLink 代替 NavigationButton

      但你应该导入 Combine

      struct AView: View {
       var link: NavigationDestinationLink<BView>
      var publisher: AnyPublisher<Void, Never>
      
      init() {
          let publisher = PassthroughSubject<Void, Never>()
          self.link = NavigationDestinationLink(
              BView(onDismiss: { publisher.send() }),
              isDetail: false
          )
          self.publisher = publisher.eraseToAnyPublisher()
      }
      
      var body: some View {
          NavigationView {
              Button(action:{
              self.link.presented?.value = true
      
      
       }) {
                  Text("Go to B")
              }.onReceive(publisher, perform: { _ in
                  self.link.presented?.value = false
              })
          }
      }
      }
      
      struct BView: View {
      var onDismiss: () -> Void
      var body: some View {
          Button(action: self.onDismiss) {
              Text("Come back to A")
          }
      }
      }
      

      【讨论】:

        【解决方案11】:

        编辑:这里的答案比我的要好,但两者都有效: SwiftUI dismiss modal

        您真正想要(或应该想要)的是模态演示,这里有几个人提到过。如果你走这条路,你肯定需要能够以编程方式关闭模式,Erica Sadun 在这里有一个很好的例子来说明如何做到这一点:https://ericasadun.com/2019/06/16/swiftui-modal-presentation/

        鉴于声明式编码和命令式编码之间的区别,解决方案可能并不明显(例如,将 bool 切换为 false 以关闭模态),但如果您的模型状态是事实的来源,则它是有意义的,而不是 UI 本身的状态。

        这是我对 Erica 示例的快速理解,使用传递给 TestModal 的绑定,以便它可以自行关闭,而不必成为 ContentView 本身的成员(为简单起见,Erica 就是这样)。

        struct TestModal: View {
            @State var isPresented: Binding<Bool>
        
            var body: some View {
                Button(action: { self.isPresented.value = false }, label: { Text("Done") })
            }
        }
        
        struct ContentView : View {
            @State var modalPresented = false
        
            var body: some View {
                NavigationView {
                    Text("Hello World")
                    .navigationBarTitle(Text("View"))
                    .navigationBarItems(trailing:
                        Button(action: { self.modalPresented = true }) { Text("Show Modal") })
                }
                .presentation(self.modalPresented ? Modal(TestModal(isPresented: $modalPresented)) {
                    self.modalPresented.toggle()
                } : nil)
            }
        }
        

        【讨论】:

          【解决方案12】:

          似乎大量的基本导航功能是超级错误的,这令人失望,可能值得暂时放弃以节省数小时的挫败感。对我来说,PresentationButton 是唯一有效的。 TabbedView 选项卡不能正常工作,而且 NavigationButton 根本不适合我。如果 NavigationButton 适合您,听起来像 YMMV。

          我希望他们在修复自动完成的同时修复它,这将使我们更好地了解我们可用的内容。与此同时,我很不情愿地围绕它进行编码并记录修复何时出现。不得不弄清楚我们是否做错了什么或者它是否不起作用,这很糟糕,但那是你的测试版!

          【讨论】:

          • 感谢您的回答。我似乎没有你遇到的那么多麻烦。然而,正如你所说,这仍然是测试版,还没有最终确定,但PresentationButton 现在就帮我解决问题。
          猜你喜欢
          • 2020-09-22
          • 1970-01-01
          • 2022-08-14
          • 2019-12-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多