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

时间:2020-05-01 01:56:06

标签: ios swift mvvm swiftui

在SwiftUI中,TabView必须是根视图。因此,您不能使用NavigationLink导航到TabView。假设我的应用程序中有四个屏幕。

屏幕A是一个TabView,其中包含屏幕B和屏幕C。 屏幕B是具有NavigationLink的列表,可带您进入列表项的详细信息(屏幕D) 屏幕C是一个信息视图(在问题中并不重要) 屏幕D是列表项详细信息屏幕,您必须首先导航到屏幕b才能到达此处。但是,屏幕D具有一个按钮,该按钮应在ViewModel中执行网络操作,然后在完成时将您带到ScreenA。

屏幕D如何导航到根屏幕(屏幕A)的两个级别?

3 个答案:

答案 0 :(得分:1)

我做了这样的把戏,为我工作。

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

注意:我希望在TabView内嵌入NavigationView可以完成相同的工作,但是没有用,这就是这样做的原因。

我假设contentView是要弹出的视图(其中一个包含TabView)

然后在从该视图导航的任何视图中,您可以调用

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

答案 1 :(得分:1)

“弹出”根视图的一种有效方法是利用用于导航的isDetailLink上的NavigationLink修饰符。

默认情况下,isDetailLinktrue。此修饰符用于各种视图容器,例如在iPad上,详细视图将显示在右侧。

isDetailLink设置为false意味着该视图将被推到NavigationView堆栈的顶部,也可以被推下。

isDetailLink上将NavigationLink设置为false,然后将isActive绑定传递到每个子目标视图。要弹出到根视图时,请将值设置为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")
    }
}

答案 2 :(得分:0)

我已经用self.presentationMode.wrappedValue.dismiss()解决了这个问题。这是调用此方法以返回到导航根视图的方法。在这里,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)
        )

    }
}