简体   繁体   English

如何从 TabView 的子视图导航回 SwiftUI 中的 TabView?

[英]How to navigate from a subview of a TabView back to the TabView in SwiftUI?

In SwiftUI a TabView must be the root view.在 SwiftUI 中,TabView 必须是根视图。 You, therefore, cannot use a NavigationLink to navigate to a TabView.因此,您不能使用 NavigationLink 导航到 TabView。 So say for instance I have four screens in my app.例如,假设我的应用程序中有四个屏幕。

Screen A is a TabView that holds Screen B and Screen C.屏幕 A 是一个 TabView,包含屏幕 B 和屏幕 C。 Screen B is a List that has a NavigationLink to take you to a list item details (Screen D) Screen C is an information view (it's not important in the question) Screen D is a list item details screen, you must first navigate to screen b to get here.屏幕 B 是具有 NavigationLink 的列表,可将您带到列表项详细信息(屏幕 D)屏幕 C 是信息视图(在问题中并不重要)屏幕 D 是列表项详细信息屏幕,您必须先导航到屏幕b 到这里。 Screen D, however, has a button that should perform a network action in a ViewModel, and then take you to ScreenA upon completion.然而,屏幕 D 有一个按钮,应该在 ViewModel 中执行网络操作,然后在完成后将您带到屏幕 A。

How can Screen D navigation back two levels to the root screen (Screen A)?屏幕 D 如何导航回两个级别到根屏幕(屏幕 A)?

I did a trick like this, worked for me.我做了这样的把戏,为我工作。

In SceneDelegate.swift, I modified auto generated code.在 SceneDelegate.swift 中,我修改了自动生成的代码。


let contentView = ContentView()

if let windowScene = scene as? UIWindowScene {
   let window = UIWindow(windowScene: windowScene)
   // Trick here.
   let nav = UINavigationController(
       rootViewController: UIHostingController(rootView: contentView)
    )
    // I embedded this host controller inside UINavigationController
    //  window.rootViewController = UIHostingController(rootView: contentView)
    window.rootViewController = nav
    self.window = window
    window.makeKeyAndVisible()
}

Note : I expected embedding TabView inside NavigationView would do the same job, but did not work, it was the reason of doing this trick.注意:我希望在NavigationView中嵌入TabView会做同样的工作,但没有奏效,这是做这个技巧的原因。

I assume, contentView is the View that you want to pop to (the one includes TabView)我假设, contentView是您想要弹出的视图(包括 TabView)

Then in any view navigated from that view, you can call然后在从该视图导航的任何视图中,您可以调用

extension View {
    func popToRoot() {
        guard let rootNav = UIApplication.shared.windows.first?.rootViewController as? UINavigationController else { return }
        rootNav.popToRootViewController(animated: true)
    }
}

// Assume you eventually came to this view.
struct DummyDetailView: View {

    var body: some View {

        Text("DetailView")
           .navigationBarItems(trailing:
               Button("Pop to root view") {
                   self.popToRoot()
               }
           )
    }
}

// EDIT: Requested sample with a viewModel
struct DummyDetailViewWithViewModel: View {

    var viewModel: SomeViewModel = SomeViewModel()

    var body: some View {        
        Button("Complete Order!!") {
            viewModel.completeOrder(success: { _ in
                print("Order Completed")
                self.popToRoot()
            })
        }
    }
}

An effective way to "pop" to your root view would be to utilize the isDetailLink modifier on the NavigationLink that is used for navigating. “弹出”到根视图的一种有效方法是在用于导航的NavigationLink上使用isDetailLink修饰符。

By default, isDetailLink is true .默认情况下, isDetailLinktrue This modifier is used for a variety of view containers, like on iPad, where the detail view would appear on the right side.此修改器用于各种视图容器,例如 iPad,其中详细视图将显示在右侧。

Setting isDetailLink to false means that the view will be pushed on top of the NavigationView stack, and can also be pushed off.isDetailLink设置为false意味着视图将被推送到NavigationView堆栈的顶部,也可以被推送出去。

Along with setting isDetailLink to false on NavigationLink , pass the isActive binding to each child destination view.除了在NavigationLink isDetailLink设置为 false 外,还将isActive绑定传递给每个子目标视图。 When you want to pop to the root view, set the value to false and it will pop everything off:当您想弹出到根视图时,将值设置为false它将弹出所有内容:

import SwiftUI

struct ScreenA: View {
    @State var isActive : Bool = false

    var body: some View {
        NavigationView {
            NavigationLink(
                destination: ScreenB(rootIsActive: self.$isActive),
                isActive: self.$isActive
            ) {
                Text("ScreenA")
            }
            .isDetailLink(false)
            .navigationBarTitle("Screen A")
        }
    }
}

struct ScreenB: View {
    @Binding var rootIsActive : Bool

    var body: some View {
        NavigationLink(destination: ScreenD(shouldPopToRootView: self.$rootIsActive)) {
            Text("Next screen")
        }
        .isDetailLink(false)
        .navigationBarTitle("Screen B")
    }
}

struct ScreenD: View {
    @Binding var shouldPopToRootView : Bool

    var body: some View {
        VStack {
            Text("Last Screen")
            Button (action: { self.shouldPopToRootView = false } ){
                Text("Pop to root")
            }
        }.navigationBarTitle("Screen D")
    }
}

I have resolved this with self.presentationMode.wrappedValue.dismiss() .我已经用self.presentationMode.wrappedValue.dismiss()解决了这个问题。 This is the method that is called to bring back to View of Navigation Root.这是被调用以返回导航根视图的方法。 Here, TestView is ScreenA, ItemList is ScreenB, InfoView is ScreenC and ItemDetails is ScreenD.这里,TestView 是 ScreenA,ItemList 是 ScreenB,InfoView 是 ScreenC,ItemDetails 是 ScreenD。

import SwiftUI

struct TestView: View {
    @State private var currentTab: Tab = .list
    var body: some View {
        TabView(selection: $currentTab){
            ItemList()
                .tabItem{
                    Text("List")
            }
            .tag(Tab.list)
            .navigationBarHidden(false)
            InfoView()
                .tabItem{
                    Text("Info")
            }
            .tag(Tab.info)
            .navigationBarHidden(true)
        }
    }
}

struct ItemList: View {
    var body: some View {
        VStack{
            NavigationView{
                List {
                    NavigationLink(destination: ItemDetails()){
                        Text("Item")
                    }
                    NavigationLink(destination: ItemDetails()){
                        Text("Item")
                    }
                    NavigationLink(destination: ItemDetails()){
                        Text("Item")
                    }
                    NavigationLink(destination: ItemDetails()){
                        Text("Item")
                    }
                }.navigationBarTitle("Item List")
            }
        }
    }
}

struct InfoView: View {
    var body: some View {
        Text("This is information view")
    }
}

struct ItemDetails: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @State private var loading = false
    var body: some View {
        ZStack {
            Text("Connecting...")
                .font(.title)
                .offset(y: -150)
                .pulse(loading: self.loading)
            VStack {
                Text("This is Item Details")
                Button("Connect"){
                    self.loading = true
                    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                        self.loading = false
                        self.presentationMode.wrappedValue.dismiss()
                    }
                }.padding()
            }
        }
    }
}

enum Tab {
    case list, info
}

extension View {
    func pulse(loading: Bool) -> some View {
        self
            .opacity(loading ? 1 : 0)
            .animation(
                Animation.easeInOut(duration: 0.5)
                    .repeatForever(autoreverses: true)
        )

    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM