概述
本文主要分享SwiftUI 实现TabBar的过程,将使用案例的方式进行说明。为什么先分享这个哪?是因为 tabbar 是整个项目结构的基础,项目后续的开发基本依赖tabbar。
分析
从页面布局看,整个页面分成两部分,一个是tab区,一个是内容区,所以我们在设计tab的时候就需要考虑这两部分,
主体结构
代码如下:
scss
struct ContentView: View {
var body: some View {
VStack {
//这里为内容区
HStack {
Text("home")
}
.background(.green)
Spacer()
//这里为tab区
HStack {
Text("Text")
}
.frame(height: 100)
.background(.gray)
}
.ignoresSafeArea(edges: .bottom)
.background(.yellow)
}
}
看下效果:
发现是扁的没有充满全屏,我们可以使用GeometryReader,这个控件官方定义为一个容器视图,将其内容定义为自己的大小和函数,这个视图返回一个灵活的父布局的首选大小,读起来有点别扭。 代码如下:
scss
GeometryReader { geometry **in**
VStack {
HStack {
Text("home")
}
.background(.green)
Spacer()
HStack {
Text("Text")
}
//这里可以根据自己需要更改高度
.frame(width: geometry.size.width, height: geometry.size.height/9)
.background(.gray)
}
.ignoresSafeArea(edges: .bottom)
.background(.yellow)
}
看下效果:
这样看起来大体的结构有了,我们的主体结构就这样
TabBarIcon
TabBarIcon一般由两部分组成,一个是图片,一个是文案 代码如下:
css
struct TabBarIcon: View {
var body: some View {
VStack {
Image(systemName: "chart.pie.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
Text("首页")
.font(.footnote)
.font(.system(size: 16))
}
}
}
看下效果:
放到主页面中看下:
发现布局不对,并且图片与文案也是写死的,这样我们让它活一下,添加 TabBarIcon 的宽高,图片,文案。
代码如下:
scss
let width, height: CGFloat
let systemIconName, tabName: String
var body: some View {
VStack {
Image(systemName: systemIconName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: width, height: height)
.padding(.top, 6)
Text(tabName)
.font(.footnote)
.font(.system(size: 16))
Spacer()
}
.padding(.horizontal, -2)
}
主页面也改一下
arduino
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
VStack {
HStack {
Text("home")
}
.background(.green)
Spacer()
ZStack {
HStack {
TabBarIcon(width: geometry.size.width/5, height: geometry.size.height/32,systemIconName: "chart.pie.fill", tabName: "首页").frame(maxWidth: .infinity)
TabBarIcon(width: geometry.size.width/5, height: geometry.size.height/32,systemIconName: "pencil.circle", tabName: "详情").frame(maxWidth: .infinity)
TabBarIcon(width: geometry.size.width/5, height: geometry.size.height/32,systemIconName: "person.crop.circle.fill", tabName: "我的").frame(maxWidth: .infinity)
}
// 将宽度设置为父视图的宽度大小,高度需要微调,可以设置为具体是数值,比如 100
.frame(width: geometry.size.width, height: geometry.size.height/9)
.background(.gray)
}
.ignoresSafeArea(edges: .bottom)
.background(.yellow)
}
}
}
}
看下效果:
效果基本达到了,但是现在却还不能点击,也不能切换,也没有点击亮
切换与高亮
实现点击,这时候我们就需要在 TabBarIcon中添加点击事件,同时点击后图片及文案都变成高亮显示 为了方便管理及拓展性,我们创建一个ViewRouter进行管理 代码如下:
arduino
enum Page {
case home
case detail
case mine
}
class ViewRouter: ObservableObject {
@Published var currentPage: Page = .home
}
修改TabBarIcon中代码 代码如下:
scss
struct TabBarIcon: View {
@StateObject var viewRouter: ViewRouter
let assignedPage: Page
let width, height: CGFloat
let systemIconName, tabName: String
var body: some View {
VStack {
Image(systemName: systemIconName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: width, height: height)
.padding(.top, 6)
Text(tabName)
.font(.footnote)
.font(.system(size: 16))
Spacer()
}
.padding(.horizontal, -2)
.onTapGesture {
//点击把当前页付值给viewRouter
viewRouter.currentPage = assignedPage
}
//这俩修改高亮
.foregroundColor(viewRouter.currentPage == assignedPage ? .green : .white)
}
}
修改主页代码 代码如下:
less
struct ContentView: View {
@StateObject var viewRouter: ViewRouter = ViewRouter()
var body: some View {
GeometryReader { geometry in
VStack {
HStack {
Text("home")
}
.background(.green)
Spacer()
ZStack {
HStack {
TabBarIcon(viewRouter: viewRouter, assignedPage: .home, width: geometry.size.width/5, height: geometry.size.height/32,systemIconName: "chart.pie.fill", tabName: "首页").frame(maxWidth: .infinity)
TabBarIcon(viewRouter: viewRouter, assignedPage: .detail, width: geometry.size.width/5, height: geometry.size.height/32,systemIconName: "pencil.circle", tabName: "详情").frame(maxWidth: .infinity)
TabBarIcon(viewRouter: viewRouter, assignedPage: .mine,width: geometry.size.width/5, height: geometry.size.height/32,systemIconName: "person.crop.circle.fill", tabName: "我的").frame(maxWidth: .infinity)
}
// 将宽度设置为父视图的宽度大小,高度需要微调,可以设置为具体是数值,比如 100
.frame(width: geometry.size.width, height: geometry.size.height/9)
.background(.gray)
}
.ignoresSafeArea(edges: .bottom)
.background(.yellow)
}
}
}
}
看下效果:
这时点击,我们发现tab已经可以点击了,同时点击也有了高亮显示。那怎么去切换页面呢?
切换页面
首先我们创建三个页面 Home, Mine, Detail,然后 在内容区进行切换 代码如下:
arduino
switch viewRouter.currentPage {
case .home:
Home()
case .detail:
Detail()
case .mine:
Mine()
}
查看效果:
页面已经可以正常切换,但是问题又来了,我们怎么去push一个页面呢?
实现Push
在SwiftUI中有一个控件NavigationView, 这个控件是专门用来实现push效果的,我们把它包裹住整个内容区域
代码如下:
less
**struct** ContentView: View {
@StateObject **var** viewRouter: ViewRouter = ViewRouter()
**var** body: **some** View {
GeometryReader { geometry **in**
NavigationView {
//内容区代码
VStack {...}
.ignoresSafeArea(edges: .bottom)
}
}
}
}
以详情页为例,怎么具体的去push到一个新页面,创建一个新页面PushPage,然后在detai里写push逻辑。
代码如下:
css
struct Detail: View {
var body: some View {
VStack {
NavigationLink {
PushPage()
} label: {
Text("push")
}
}
.navigationBarTitle("详情页", displayMode: .automatic)
}
}
查看下效果:
点击push发现可以正常push,同时底部tab也会自动隐藏。
结语
到这里,我们的TarBar效果就基本实现了,其中有些UI方面的小细节,需要我们自己去处理下。