SwiftUI - SwiftUI 中是不是有等效的 popViewController?

Posted

技术标签:

【中文标题】SwiftUI - SwiftUI 中是不是有等效的 popViewController?【英文标题】:SwiftUI - Is there a popViewController equivalent in SwiftUI?SwiftUI - SwiftUI 中是否有等效的 popViewController? 【发布时间】:2019-06-07 10:55:05 【问题描述】:

我在玩 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 尚未最终确定,并且缺少某些内容。 【参考方案1】:

如下修改你的 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 中弹出视图?【参考方案2】:

使用@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()【参考方案3】:

带有状态变量。试试看。

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 中。【参考方案4】:

如果您愿意,现在可以通过编程方式弹出 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。请参阅下面的答案以获得更清洁的整体解决方案。 我发现这对我有用。我可以使用后退按钮或弹出按钮并获得预期的结果。【参考方案5】:

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

@Environment(\.presentationMode) var presentationMode

然后当你需要弹出时

self.presentationMode.wrappedValue.dismiss()

【讨论】:

这样做会在 1/2 秒后自动弹出视图【参考方案6】:

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

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

【讨论】:

感谢您的回答。我似乎没有你遇到的那么多麻烦。然而,正如你所说,这仍然是测试版,还没有最终确定,但PresentationButton 现在就帮我解决问题。【参考方案7】:

更新:自 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 上书写并按下按钮时,应用程序被挂起并且什么也没发生,我们的操作不起作用。我的场景是登录视图..【参考方案8】:

你也可以用.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:)

【讨论】:

【参考方案9】:

编辑:这里的答案比我的要好,但两者都有效: 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)
    

【讨论】:

【参考方案10】:

以下在 XCode11 GM 中适用于我

self.myPresentationMode.wrappedValue.dismiss()

【讨论】:

PresentationMode 已被弃用。【参考方案11】:

使用 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")
    


【讨论】:

【参考方案12】:

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

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

【讨论】:

以上是关于SwiftUI - SwiftUI 中是不是有等效的 popViewController?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 中的 viewWillAppear() 或其他等效项

UIKit 中等效的 SwiftUI 工作表视图是啥?

SwiftUI 的 minimumFontScale 等效项是多少?

如何以编程方式创建等效的 SwiftUI 堆栈视图分隔符

SwiftUI:WKWebView 在 macOS 上截断页面内容(NSViewRepresentable)

将 SwiftUI 视图转换为 NSImage