[SwiftUI 100 天] 用 TabView 和 tabItem() 来创建 tab

3,903 阅读4分钟

译自 www.hackingwithswift.com/books/ios-s… www.hackingwithswift.com/books/ios-s…

更多内容,欢迎关注公众号 「Swift花园」

喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀

用 TabView 和 tabItem() 来创建 tab

导航视图非常适用于创建层次化的视图栈,让用户可以层层深入数据,但它们对于不相关的数据则不那么合适。 对于那种数据,我们需要用到 SwiftUI 的 TabView,它会在屏幕底部创建一排不带边框的按钮,每次点击不同的按钮显示不同的视图。

往一个 TabView 里添加 tab 很简单,只要逐一列出来就行了,像这样:

TabView {
    Text("Tab 1")
    Text("Tab 2")
}

不过,实践中你基本上一定需要对 tab 显示的方式做定制 —— 上面的代码显示的 tab 栏将会是一条空白的灰色栏。尽管你可以点击灰色区域的左边和右边激活两个 tab,但这种用户体验实在是太糟糕了。

因此,更好的方案是给 TabView 里的每个子视图附加 tabItem() modifier。这个 modifier 能让你定制每个 tab 的呈现方式,比如像这样提供一个图像和文本:

TabView {
    Text("Tab 1")
        .tabItem {
            Image(systemName: "star")
            Text("One")
        }

    Text("Tab 2")
        .tabItem {
            Image(systemName: "star.fill")
            Text("Two")
        }
}

你可以试着往 tabItem() modifier 里添加更多东西,甚至重新排列,让文字先于图像出现,怎么着都行,但不会管用:SwiftUI 只会显示一个图像,一个文本,并且是按先图像后文本的固定顺序。

除了能让我们点击 tab 项切换视图,SwiftUI 还允许我们通过使用状态来程序化控制当前视图。这种方式需要四步:

  1. 创建一个记录当前正在显示的视图的 @State 属性。
  2. 每当我们跳到一个的 tab 时修改这个属性。
  3. 把这个属性以 binding 的形式传给 TabView,以便它能够被自动跟踪。
  4. 告诉 SwiftUI 对应属性的每种值应该显示哪个 tab。

前三步很简单,让我们快速解决掉它们。首先是一个记录当前 tab 的状态,把下面这个属性添加到 ContentView

@State private var selectedTab = 0

其次,我们需要在某个地方修改这个属性,让 SwiftUI 切换 tab。在我们的小 demo 中,我们给第一个 tab 添加一个 onTapGesture() modifier,像这样:

Text("Tab 1")
    .onTapGesture {
        self.selectedTab = 1
    }
    .tabItem {
        Image(systemName: "star")
        Text("One")
    }

再次,我们需要把 TabViewselection 绑定到 $selectedTab,这一步只需要在创建 TabView 时传递参数就可以了:

TabView(selection: $selectedTab) {

接下来是有趣的部分,当我们说 self.selectedTab = 1 的时候,SwiftUI 是怎么知道哪个 tab 是 tab 1 的?你可能会想,所有的 tab 应该被视作数组,这样的话第二个 tab 就应该是在索引 1。但我们采用这种方案的话会遇到各种问题:假如我们后来在 TabView 里把各个 tab 子视图挪顺序了怎么办?

深想一步,这样做会破坏 SwiftUI 的一个核心理念:视图应该是可以被自由组合的。

所以这不是一个好主意。SwiftUI 提供了一个更好的解决方案:我们可以给每个视图附加一个唯一的标识符,用这个标识符作为被选中的 tab。这些标识符被称为 tag,是用 tag() modifier 来提供的:

Text("Tab 2")
    .tabItem {
        Image(systemName: "star.fill")
        Text("Two")
    }
    .tag(1)

因此,我们的整个视图可以修改成下面这样:

struct ContentView: View {
    @State private var selectedTab = 0

    var body: some View {
        TabView(selection: $selectedTab) {
            Text("Tab 1")
                .onTapGesture {
                    self.selectedTab = 1
                }
                .tabItem {
                    Image(systemName: "star")
                    Text("One")
                }
                .tag(0)

            Text("Tab 2")
                .tabItem {
                    Image(systemName: "star.fill")
                    Text("Two")
                }
                .tag(1)
        }
    }
}

现在代码已经可以工作了:你可以通过点击 tab 项切换不同的 tab,或者通过第一个 tab 的 tap 手势程序化地跳到第二个 tab。

当然,仅仅使用 0 和 1 还不理想 —— 这些值只是解决了视图可能被调整顺序的问题,但是不好记忆。所幸我们可以用字符串来代替:给每个视图一个能反映它的用途的唯一字符串 tag,然后把它用在你的 @State 属性上。长期来看,这样做比使用整数要更易用。

提示: 同时需要 NavigationViewTabView 的情况很常见,你需要注意的是:TabView 应该作为父视图,然后让里面的 tab 在必要时使用 NavigationView


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~

Swift花园微信公众号