【问题标题】:How to navigate from a subview of a TabView back to the TabView in SwiftUI?如何从 TabView 的子视图导航回 SwiftUI 中的 TabView?
【发布时间】:2020-08-15 13:00:06
【问题描述】:

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

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

屏幕 D 如何导航回两级到根屏幕(屏幕 A)?

【问题讨论】:

  • 如果 ScreenA 是 TabView{ScreenB|ScreenC} 和 ScreenB > ScreenD 那么我看不到两级导航,我错过了什么吗?场景不清楚。

标签: ios swift mvvm swiftui


【解决方案1】:

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

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

isDetailLink 设置为false 意味着视图将被压入NavigationView 堆栈的顶部,也可以被压出。

在将NavigationLink 上的isDetailLink 设置为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")
    }
}

【讨论】:

  • 这将如何与 MVVM 设置一起使用? swift 中的每个屏幕都应该有一个 ViewModel。所以说一旦你在屏幕 D,你按下一个在视图模型中执行网络调用的按钮。然后,当该网络调用完成并成功时,您应该弹出到根视图
  • 很好,但是当 ScreenB 显示时,我想 TabView 导航栏必须隐藏 - 怎么做?
  • @IgorCova 用于隐藏标签栏,您可以将 TabView 放在 NavigationView 中。 1.NavigationView -> Link -> TabView screen -> 每个Tab都可以包含NavigationLink,而不是View。
【解决方案2】:

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

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

注意:我希望在 NavigationView 中嵌入 TabView 会做同样的工作,但没有奏效,这就是这样做的原因。

我假设,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()
            })
        }
    }
}

【讨论】:

  • 这将如何与 MVVM 设置一起使用? swift 中的每个屏幕都应该有一个 ViewModel。所以说一旦你在屏幕 D,你按下一个在视图模型中执行网络调用的按钮。然后,当该网络调用完成并成功时,您应该弹出到根视图
  • 不管你在哪里称呼它。例如:您点击了一个按钮并拨打了网络电话。您可以将成功回调返回给您执行操作的视图。所以你可以在那里触发那个方法。
  • ;(如果你想隐藏它,你可以隐藏它。但是如果你想推到一个详细视图,你必须在导航视图中,这是它的工作原理
  • 如果你想摆脱导航栏,我推荐this solution
【解决方案3】:

我已经通过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)
        )

    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-06
    • 2021-06-14
    • 1970-01-01
    • 2018-09-11
    相关资源
    最近更新 更多