TabView 在切换选项卡时重置导航堆栈

TabView resets navigation stack when switching tabs

我有一个简单的 TabView:

TabView {
    NavigationView {
        VStack {
            NavigationLink(destination: Text("Detail")) {
                Text("Go to detail")
            }
        }
    }
        .tabItem { Text("First") }
        .tag(0)
    Text("Second View")
        .tabItem { Text("Second") }
        .tag(1)
}

当我转到选项卡 1 的详细视图时,切换到选项卡 2,然后切换回选项卡 1,我假设返回到详细视图(在 iOS 中随处可见的基本用户体验)。相反,它会重置为选项卡 1 的根视图。

由于 SwiftUI 看起来不支持开箱即用,我该如何解决这个问题?

所以,这确实 "preserve" 切换标签时的详细视图,但只有在切换回标签 1 时才能明显地推动详细视图。我一直未能成功禁用它,例如,.animation().

此外,您几乎必须覆盖 DetailView 中的导航栏项目,因为默认后退按钮的行为很奇怪(注释掉 .navigationBarItems() 行以了解我的意思)。

考虑到这些注意事项,这确实可以作为一种解决方法。

struct ContentView: View {
    @State var showingDetail = false

    var body: some View {
        TabView {
            NavView(showingDetail: $showingDetail)
                .tabItem { Text("First") }
                .tag(0)
            Text("Second View")
                .tabItem { Text("Second") }
                .tag(1)
        }
    }
}

struct NavView: View {
    @Binding var showingDetail: Bool

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView(showing: $showingDetail), isActive: $showingDetail) {
                    Text("Go to detail")
                }
            }
        }
    }
}

struct DetailView: View {
    @Binding var showing: Bool

    var body: some View {
            Text("Detail")
                .navigationBarItems(leading: Button("Back", action: { self.showing = false }))
    }
}

这里不太明显的解决方案是实际上不使用 SwiftUI。为了获得 UIKit 行为,我将 UIKit UITabBarController 包装在 SwiftUI UIViewControllerRepresentable 中,如本例所示:https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit.

我在这里展示了一个基本的实现。完整的最新实施在 github:https://gist.github.com/Amzd/2eb5b941865e8c5cccf149e6e07c8810

在 SwiftUI 视图中包装 UIKit UITabBarController:

struct UIKitTabView: View {
    var viewControllers: [UIHostingController<AnyView>]

    init(_ tabs: [Tab]) {
        self.viewControllers = tabs.map {
            let host = UIHostingController(rootView: [=10=].view)
            host.tabBarItem = [=10=].barItem
            return host
        }
    }

    var body: some View {
        TabBarController(controllers: viewControllers)
            .edgesIgnoringSafeArea(.all)
    }

    struct Tab {
        var view: AnyView
        var barItem: UITabBarItem

        init<V: View>(view: V, barItem: UITabBarItem) {
            self.view = AnyView(view)
            self.barItem = barItem
        }
    }
}
struct TabBarController: UIViewControllerRepresentable {
    var controllers: [UIViewController]

    func makeUIViewController(context: Context) -> UITabBarController {
        let tabBarController = UITabBarController()
        tabBarController.viewControllers = controllers
        return tabBarController
    }

    func updateUIViewController(_ uiViewController: UITabBarController, context: Context) {

    }
}

用法示例:

struct ExampleView: View {
    @State var text: String = ""

    var body: some View {
        UIKitTabView([
            UIKitTabView.Tab(
                view: NavView(), 
                barItem: UITabBarItem(title: "First", image: nil, selectedImage: nil)
            ),
            UIKitTabView.Tab(
                view: Text("Second View"), 
                barItem: UITabBarItem(title: "Second", image: nil, selectedImage: nil)
            )
        ])
    }
}

struct NavView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("This page stays when you switch back and forth between tabs (as expected on iOS)")) {
                    Text("Go to detail")
                }
            }
        }
    }
}

下面是一个简单示例,说明如何在根目录中保留导航堆栈的状态:

struct ContentView: View {

    var body: some View {

        TabView {

            Text("First tab")
                .tabItem { Image(systemName: "1.square.fill"); Text("First") }
                .tag(0)

            SecondTabView()
                .tabItem { Image(systemName: "2.square.fill"); Text("Second") }
                .tag(1)
        }
    }
}

struct SecondTabView: View {

    private struct ListItem: Identifiable {
        var id = UUID()
        let title: String
    }

    private let items = (1...10).map { ListItem(title: "Item #\([=10=])") }

    @State var selectedItemIndex: Int? = nil

    var body: some View {

        NavigationView {
            List(self.items.indices) { index in
                NavigationLink(destination:  Text(self.items[index].title),
                               tag: index,
                               selection: self.$selectedItemIndex) {
                    Text(self.items[index].title)
                }
            }
            .navigationBarTitle("Second tab", displayMode: .inline)
        }
    }
}