<aside> 💡 SwiftUI 中的文件名不要起的跟常用的View的名字一样,例如一个文件名叫TabView的文件,里面又用了TabView(){ … }这个视图,会莫名其妙报错。

</aside>

TabView

NavigationStack 非常适合创建分层视图堆栈,让用户深入了解数据,但它们不太适合展示不相关的数据。想要展示一些不相关的视图,经常需要使用 TabView ,它会在屏幕底部创建一个按钮条,点击每个按钮会显示不同的视图。TabView 就像是其自身内部的子视图的容器。这些子视图是单独的屏幕,它提供标签按钮 TabItems ,允许用户在这些子视图之间切换;当标签太多,无法满足设备的需求时,就会创建 "更多 "按钮,在这里可以找到其余的标签。

与 NavigationStack 共用时注意:

<aside> 💡 同时使用 NavigationStackTabView 是很常见的,但应该注意: TabView 应该用作父视图,其中某个选项卡,在必要时可以嵌入一个 NavigationStack ,而不能够反过来(把 NavigationStack 作为父视图)。

</aside>


针对 iOS 18 及更新版本

对于 iOS 18 及更高版本, TabView 是从多个单独的 Tab 视图创建的。

Tab 的设置

每个 Tab 视图都有一个标题和一个图标。如果这些选项卡之一负责搜索您的应用,请添加 .search 的 role 参数。

TabView {
		// 每个视图都有一个标题和一个图标
    Tab("Home", systemImage: "house") {
        Text("Put a HomeView here")
    }
    Tab("Users", systemImage: "person.3") {
        Text("Put a UsersView here")
    }
    Tab("Search", systemImage: "magnifyingglass", role: .search) {
        Text("Put a SearchView here")
    }
}

当想要在 iPadOS 侧边栏中将选项卡分组在一起时,事情会变得更有趣。选项卡组是通过在 TabSection 内放置一个或多个 Tab 视图来创建的,我们可以允许用户使用 sidebarAdaptable 风格。

TabView {
    TabSection("Watch") {
        Tab("Movies", systemImage: "film") {
            Text("Put a MoviesView here")
        }
        Tab("TV Shows", systemImage: "tv") {
            Text("Put a TVShowsView here")
        }
    }

    TabSection("Listen") {
        Tab("Music", systemImage: "music.note.list") {
            Text("Put a MusicView here")
        }

        Tab("Podcasts", systemImage: "mic") {
            Text("Put a PodcastsView here")
        }
    }
}
.tabViewStyle(.sidebarAdaptable)

具体的外观取决于用户使用的设备以及他们激活的选项卡视图模式。

以编程方式控制

如果想以编程方式控制选项卡选择,请绑定到 TabView 的 selection ,然后将适当的 value 参数添加到 Tab 对象。

struct ContentView: View {

		// 使用枚举是提供类型安全的好方法,Swift 知道选择必须是某种 Section ,因此它不允许不是 .cats 的值或 .dogs
    enum Section {
        case cats
        case dogs
    }

    @State private var selectedTab = Section.cats

    var body: some View {
        TabView(selection: $selectedTab) {
            Tab("Cats", systemImage: "cat", value: .cats) {
                Button("Go to Dogs") {
                    selectedTab = .dogs
                }
            }

            Tab("Dogs", systemImage: "dog", value: .dogs) {
                Button("Go to Cats") {
                    selectedTab = .cats
                }
            }
        }
    }
    
}

针对 iOS 17 及更早版本

在 iOS 17 及更早版本中,tabView 要为每个项目提供图像和标题,如果想以编程方式控制哪个选项卡处于活动状态,还可以选择添加标签。

// 基本创建方式
TabView {
		//第一个页面
    Text("Tab 1")
				//在底部标签栏创建一个标签按钮,指向第一个页面
        .tabItem {
            Label("One", systemImage: "star")
        }
		//第二个页面    
    Text("Tab 2")
				//在底部标签栏创建第二个标签按钮,指向第二个页面
        .tabItem {
            Label("Two", systemImage: "circle")
        }
}
// 如果把每个Tab视图拆分得比较好,一般这样写:
TabView {
    FirstView()
        .tabItem {
            Label("Everyone", systemImage: "person.3")
        }
    SecondView()
        .tabItem {
            Label("Contacted", systemImage: "checkmark.circle")
        }
    ThirdView()
        .tabItem {
            Label("Uncontacted", systemImage: "questionmark.diamond")
        }
    FourthView()
        .tabItem {
            Label("Me", systemImage: "person.crop.square")
        }
}

TabItem 的设置

tabItem 可以进行以下一些设置:

//tabItem 可以只有文字
.tabItem{
	Text("标签文案")
}

//tabItem 可以只有图标
.tabItem{
	Image(systemName:"phone")
}

//tabItem 可以是文字 + 图标,顺序无所谓
.tabItem{
	Image(systemName:"phone")
	Text("标签文案")
}

//tabItem 也可以用label来设置图标和文字
.tabItem{
	Label("Messages", systemImage: "phone.and.waveform.fill")
}

// 修改 tabItem 的颜色
TabView {
		Text("Second Screen")
		.tabItem { 
				Image(systemName: "moon.fill")
		}
		//在tabItem后面设置颜色,是无效的
		//.foregroundColor(Color.red)
} 
.edgesIgnoringSafeArea(.top)
.accentColor(.yellow)

// 想改变tabItem图标颜色的有效方法,是设置激活图标的颜色,用这个accentColor
// 注意这个修饰器是加到 TabView 后面的

向 tabItem 添加徽章:

// 例如,如果您想在选项卡项上显示红色数字 5,您可以使用以下命令:
TabView {
    Text("Your home screen here")
        .tabItem {
            Label("Home", systemImage: "house")
        }
        .badge(5)
}

以编程方式控制

  1. 创建状态属性储存当前 Tab
@State private var selectedTab = "One"
  1. 将状态属性绑定到 TabView

将状态属性作为绑定传递到 TabView 中,以便自动跟踪它。

TabView(selection: $selectedTab) { ... }
  1. 设置每个 TabItem 的 tag 值

接下来需要告诉 SwiftUI 应为该属性的每个值显示哪个选项卡,如何实现?我们为每个视图附加一个唯一的标识符,并将其用于选定的选项卡。这些标识符称为标签,并使用 tag() 修饰符附加。

Text("Tab 2")
.tabItem {
		Label("Two", systemImage: "circle")
}
.tag("Two")

<aside> 💡 选项卡的标签可以是任何你想要的,只要数据类型符合 Hashable ,整数可能效果很好。但是如果您要进行任何有意义的编程导航,您应该确保将标签放在某个固定位置,例如视图内的静态属性,这样可以让你在许多地方能共享标签值,降低出错的风险。

</aside>

  1. 想跳转时,修改状态属性

每当想要跳转到不同的选项卡时,将该状态属性修改为新值;

Button("Show Tab 2") {
    selectedTab = "Two"
}
.tabItem {
    Label("One", systemImage: "star")
}
// 完整例子:
@State private var selectedTab = "One"

var body: some View {
				//将 TabView 的选择绑定到 $selectedTab;当创建 TabView 时,它会作为参数传递
        TabView(selection: $selectedTab) {
            Button("Show Tab 2") {
                selectedTab = "Two"
            }
            .tabItem {
                Label("One", systemImage: "star")
            }
            .tag("One")

            Text("Tab 2")
            .tabItem {
		            Label("Two", systemImage: "circle")
            }
            .tag("Two")
        }
}

使用整数作为绑定值也是可以的:

当然,仅仅使用“One”和“Two”并不理想,这些值是固定的,它虽然解决了视图跳转的问题,但它们不容易记住。幸运的是,您可以使用您喜欢的任何值:为每个视图提供一个唯一并反映其用途的字符串标记,将其用于您的 @State 属性。从长远来看,建议使用整数。

// 状态参数,设置当前默认选中的标签索引
    @State private var selectedTab = 0
 
    var body: some View {

				// 告诉 TabView 绑定刚刚设置的状态参数
        TabView(selection: $selectedTab) {

						// 第一个Tab
            Text("Tab 1")

								// 底部的标签不需要添加行为,但如果希望点击页面内的其他元素也跳转到其他Tab
								// 那就要在和其他元素交互的时候,把状态参数 selectedTab 改变
                .onTapGesture {
                    self.selectedTab = 1
                }
                .tabItem {
                    Image(systemName: "star")
                    Text("One")
                }
								// 要给每个tagItem后面加上tag(),以便可以用编程的方式去跳转
                .tag(0)
 
						// 第二个Tab
            Text("Tab 2")
                .tabItem {
                    Image(systemName: "star.fill")
                    Text("Two")
                }
								// 要给每个tagItem后面加上tag(),以便可以用编程的方式去跳转
                .tag(1)
        }
    }

自定义 TabBar + 编程导航

https://www.createwithswift.com/programmatic-navigation-with-tab-view-in-swiftui/

通过这种方式,我们既可以在应用中使用自定义的 Tab 设计,也能在交互时保持 TabView 组件的功能,保留 TabView 导航系统的结构。

// 要实现这种类型的导航,我们首先需要定义代表每个选项卡的类型
enum NavigationTabs {
    case make-it-accessible
    case make-it-spatial
    case make-it-intelligent
}