iOS开发基础30-UITabBarController
UITabBarController 深度解析:多标签导航、UITabBarItem 定制与外观管理
UITabBarController 是 iOS 最常用的容器控制器之一,用于通过底部标签栏在多个功能模块间切换。本文系统梳理其基本用法、UITabBarItem 定制、UITabBarControllerDelegate、外观管理(含 iOS 13+ UITabBarAppearance)、超过 5 个标签的 More 页面,以及子控制器包装导航控制器的常见模式。
一、UITabBarController 概述
UITabBarController 是容器视图控制器,管理多个子视图控制器,底部显示 UITabBar 标签栏。用户点击标签切换子控制器,选中的子控制器的 view 会被添加到 tabBarController 的 view 层级中。
特点
- 简洁直观:底部标签栏组织功能模块,用户一目了然。
- 快速切换:点击标签即时切换,无需返回层级。
- 系统原生:系统自带实现,支持超过 5 个标签时自动生成"更多"页面和标签排序编辑。
- 常驻底部:tabBar 始终显示在底部(push 时可通过
hidesBottomBarWhenPushed隐藏)。
二、基本用法
1. 代码创建
将 UITabBarController 作为应用的根视图控制器:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
// 子控制器 1
UIViewController *firstVC = [[UIViewController alloc] init];
firstVC.view.backgroundColor = [UIColor whiteColor];
firstVC.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"首页"
image:[UIImage imageNamed:@"home"]
selectedImage:[UIImage imageNamed:@"home_selected"]];
// 子控制器 2
UIViewController *secondVC = [[UIViewController alloc] init];
secondVC.view.backgroundColor = [UIColor whiteColor];
secondVC.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"设置"
image:[UIImage imageNamed:@"settings"]
selectedImage:[UIImage imageNamed:@"settings_selected"]];
tabBarController.viewControllers = @[firstVC, secondVC];
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
2. 常见模式:子控制器包装 UINavigationController
实际开发中,每个标签页通常需要独立的导航栈,因此子控制器一般包装在 UINavigationController 中:
// 首页标签页:UINavigationController 包装首页控制器
HomeViewController *homeVC = [[HomeViewController alloc] init];
UINavigationController *homeNav = [[UINavigationController alloc] initWithRootViewController:homeVC];
homeNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"首页" image:[UIImage imageNamed:@"home"] selectedImage:[UIImage imageNamed:@"home_selected"]];
// 设置标签页
SettingsViewController *settingsVC = [[SettingsViewController alloc] init];
UINavigationController *settingsNav = [[UINavigationController alloc] initWithRootViewController:settingsVC];
settingsNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"设置" image:[UIImage imageNamed:@"settings"] selectedImage:[UIImage imageNamed:@"settings_selected"]];
tabBarController.viewControllers = @[homeNav, settingsNav];
tabBarItem可以设置在子控制器上,也可以设置在包装子控制器的 UINavigationController 上(推荐设置在导航控制器上)。
3. Storyboard 创建
在 Interface Builder 中拖入 UITabBarController,通过 Control-drag 从 TabBarController 到子控制器,选择 view controllers 关系建立连接。每个子控制器的 Tab Bar Item 可在 Attributes Inspector 中设置标题和图片。
三、UITabBarItem 定制
每个子控制器通过 tabBarItem 属性控制其在标签栏中的显示。
1. 创建方式
// 方式一:标题 + 图片 + 选中图片
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"首页"
image:[UIImage imageNamed:@"home"]
selectedImage:[UIImage imageNamed:@"home_selected"]];
// 方式二:标题 + 图片 + tag
UITabBarItem *item = [[UITabBarItem alloc] initWithTitle:@"首页" image:[UIImage imageNamed:@"home"] tag:0];
// 方式三:系统样式
UITabBarItem *item = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFavorites tag:0];
2. 常用属性
| 属性 | 说明 |
|---|---|
title |
标签标题文字 |
image |
未选中状态的图片(默认被 tintColor 渲染为模板图) |
selectedImage |
选中状态的图片 |
badgeValue |
角标文字(如未读消息数 @"5",设为 nil 隐藏) |
tag |
标签标识,用于区分 |
titlePositionAdjustment |
标题位置偏移(UIOffset) |
imageInsets |
图片内边距(UIEdgeInsets) |
landscapeImagePhone |
横屏 iPhone 时的图片 |
// 设置角标(如未读消息)
firstVC.tabBarItem.badgeValue = @"5";
// 清除角标
firstVC.tabBarItem.badgeValue = nil;
// 调整标题位置
firstVC.tabBarItem.titlePositionAdjustment = UIOffsetMake(0, 2);
// 系统标签项
typedef NS_ENUM(NSInteger, UITabBarSystemItem) {
UITabBarSystemItemMore,
UITabBarSystemItemFavorites,
UITabBarSystemItemFeatured,
UITabBarSystemItemTopRated,
UITabBarSystemItemRecents,
UITabBarSystemItemContacts,
UITabBarSystemItemHistory,
UITabBarSystemItemBookmarks,
UITabBarSystemItemSearch,
UITabBarSystemItemDownloads,
UITabBarSystemItemMostRecent,
UITabBarSystemItemMostViewed,
};
image默认被tintColor渲染为模板图(只保留透明度,颜色由 tintColor 决定)。如需显示原图颜色,使用[image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]。
四、UITabBarController 属性与方法
1. 核心属性
| 属性 | 类型 | 说明 |
|---|---|---|
viewControllers |
NSArray<UIViewController *> |
管理的子控制器数组 |
selectedViewController |
UIViewController |
当前选中的子控制器 |
selectedIndex |
NSUInteger |
当前选中的索引 |
tabBar |
UITabBar(readonly) |
底部标签栏 |
delegate |
id<UITabBarControllerDelegate> |
代理 |
moreNavigationController |
UINavigationController(readonly) |
超过 5 个标签时的"更多"导航控制器 |
customizableViewControllers |
NSArray<UIViewController *> |
可在"更多"页面中编辑排序的控制器(nil 表示全部可编辑) |
2. 核心方法
// 设置子控制器(带动画)
- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers animated:(BOOL)animated;
3. UITabBarControllerDelegate
@interface ViewController () <UITabBarControllerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tabBarController.delegate = self;
}
// 是否允许选中某个控制器(返回 NO 阻止切换)
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
// 例如:未登录时阻止切换到"我的"页面
return YES;
}
// 已经选中某个控制器(常用:页面统计、刷新数据)
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(@"切换到: %@", NSStringFromClass([viewController class]));
}
// 即将开始自定义标签栏(点击"更多"页面的"编辑"按钮)
- (void)tabBarController:(UITabBarController *)tabBarController willBeginCustomizingViewControllers:(NSArray<UIViewController *> *)viewControllers {}
// 即将结束自定义标签栏
- (void)tabBarController:(UITabBarController *)tabBarController willEndCustomizingViewControllers:(NSArray<UIViewController *> *)viewControllers changed:(BOOL)changed {}
// 已经结束自定义标签栏
- (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizingViewControllers:(NSArray<UIViewController *> *)viewControllers changed:(BOOL)changed {}
@end
tabBar:didSelectItem:是UITabBarDelegate的方法,UITabBarController内部实现了它来处理标签点击。开发者应使用UITabBarControllerDelegate的tabBarController:didSelectViewController:,而非直接实现UITabBarDelegate。
4. 超过 5 个标签:More 页面
当 viewControllers 超过 5 个时,系统自动在第 5 个位置显示"更多"(More)标签,点击进入一个包含剩余标签的表格页面:
// 6 个标签:前 4 个直接显示,第 5 个是"更多",包含第 5、6 个
tabBarController.viewControllers = @[homeNav, exploreNav, messageNav, profileNav, moreVC1, moreVC2];
// moreNavigationController 是"更多"页面的导航控制器
UINavigationController *moreNav = tabBarController.moreNavigationController;
moreNav.navigationBar.barTintColor = [UIColor whiteColor];
// customizableViewControllers:控制哪些标签可在"更多"页面中编辑排序
// nil 表示全部可编辑,空数组表示全部不可编辑
tabBarController.customizableViewControllers = @[homeNav, exploreNav]; // 只有首页和发现可编辑
五、UITabBar 外观管理
1. 旧方式(iOS 13 之前)
UITabBar *tabBar = self.tabBarController.tabBar;
// 背景色
tabBar.barTintColor = [UIColor whiteColor];
// 选中项颜色(tintColor)
tabBar.tintColor = [UIColor systemBlueColor];
// 未选中项颜色(iOS 10+)
tabBar.unselectedItemTintColor = [UIColor grayColor];
// 背景图片
[tabBar setBackgroundImage:[UIImage imageWithColor:[UIColor whiteColor]]];
// 去除顶部分割线(shadowImage)
[tabBar setShadowImage:[[UIImage alloc] init]];
// 半透明效果(默认 YES,设为 NO 后背景不透明)
tabBar.translucent = NO;
2. 现代方式(iOS 13+:UITabBarAppearance)
if (@available(iOS 13.0, *)) {
UITabBarAppearance *appearance = [[UITabBarAppearance alloc] init];
// 不透明背景
[appearance configureWithOpaqueBackground];
appearance.backgroundColor = [UIColor whiteColor];
appearance.shadowColor = [UIColor clearColor]; // 去除顶部分割线
// 堆叠布局(图标在上、文字在下,iPhone 竖屏默认)
appearance.stackedLayoutAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor grayColor]};
appearance.stackedLayoutAppearance.normal.iconColor = [UIColor grayColor];
appearance.stackedLayoutAppearance.selected.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor systemBlueColor]};
appearance.stackedLayoutAppearance.selected.iconColor = [UIColor systemBlueColor];
// 内联布局(横屏或 iPad 时图标和文字并排)
appearance.inlineLayoutAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor grayColor]};
appearance.inlineLayoutAppearance.selected.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor systemBlueColor]};
// 紧凑布局(iPhone 横屏紧凑尺寸)
appearance.compactInlineLayoutAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor grayColor]};
// 应用外观
self.tabBarController.tabBar.standardAppearance = appearance;
// iOS 15+:scrollEdgeAppearance(内容滚动到边缘时的外观,通常与 standard 相同)
if (@available(iOS 15.0, *)) {
self.tabBarController.tabBar.scrollEdgeAppearance = appearance;
}
}
iOS 15 起,
UITabBar的scrollEdgeAppearance默认为透明背景(与UINavigationBar类似),如果不设置会导致 tabBar 背景透明。需要显式设置scrollEdgeAppearance与standardAppearance一致。
3. 全局外观设置(UIAppearance)
通过 UIAppearance 协议统一设置所有 UITabBar 的外观:
// 在 AppDelegate 中全局设置
UITabBar *tabBarAppearance = [UITabBar appearance];
tabBarAppearance.tintColor = [UIColor systemBlueColor];
tabBarAppearance.unselectedItemTintColor = [UIColor grayColor];
// 全局设置 UITabBarItem 标题文字
UITabBarItem *itemAppearance = [UITabBarItem appearance];
[itemAppearance setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor grayColor]} forState:UIControlStateNormal];
[itemAppearance setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor systemBlueColor]} forState:UIControlStateSelected];
六、切换与隐藏
1. 代码切换标签
// 切换到指定索引
self.tabBarController.selectedIndex = 2;
// 切换到指定控制器
self.tabBarController.selectedViewController = thirdVC;
selectedIndex不能超过viewControllers.count - 1,否则会崩溃。设置selectedIndex不会触发tabBarController:shouldSelectViewController:(只有用户点击才触发)。
2. push 时隐藏 TabBar
在导航栈中 push 到下一个页面时,隐藏底部 tabBar:
NextViewController *nextVC = [[NextViewController alloc] init];
nextVC.hidesBottomBarWhenPushed = YES; // 进入此页面时隐藏 tabBar
[self.navigationController pushViewController:nextVC animated:YES];
hidesBottomBarWhenPushed必须在 push 之前设置。返回上一级页面时 tabBar 自动恢复显示。
3. 手动显示/隐藏 TabBar
// 隐藏 tabBar(带动画)
[UIView animateWithDuration:0.25 animations:^{
self.tabBarController.tabBar.hidden = YES;
}];
// 显示 tabBar
self.tabBarController.tabBar.hidden = NO;
七、完整示例:四标签 TabBarController
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.delegate = self;
// 首页
HomeViewController *homeVC = [[HomeViewController alloc] init];
UINavigationController *homeNav = [[UINavigationController alloc] initWithRootViewController:homeVC];
homeNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"首页"
image:[UIImage imageNamed:@"home"]
selectedImage:[UIImage imageNamed:@"home_selected"]];
// 发现
ExploreViewController *exploreVC = [[ExploreViewController alloc] init];
UINavigationController *exploreNav = [[UINavigationController alloc] initWithRootViewController:exploreVC];
exploreNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"发现"
image:[UIImage imageNamed:@"explore"]
selectedImage:[UIImage imageNamed:@"explore_selected"]];
// 消息(带角标)
MessageViewController *messageVC = [[MessageViewController alloc] init];
UINavigationController *messageNav = [[UINavigationController alloc] initWithRootViewController:messageVC];
messageNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"消息"
image:[UIImage imageNamed:@"message"]
selectedImage:[UIImage imageNamed:@"message_selected"]];
messageNav.tabBarItem.badgeValue = @"5";
// 我的
ProfileViewController *profileVC = [[ProfileViewController alloc] init];
UINavigationController *profileNav = [[UINavigationController alloc] initWithRootViewController:profileVC];
profileNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"我的"
image:[UIImage imageNamed:@"profile"]
selectedImage:[UIImage imageNamed:@"profile_selected"]];
tabBarController.viewControllers = @[homeNav, exploreNav, messageNav, profileNav];
// 外观设置
[self setupTabBarAppearance:tabBarController.tabBar];
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)setupTabBarAppearance:(UITabBar *)tabBar {
if (@available(iOS 13.0, *)) {
UITabBarAppearance *appearance = [[UITabBarAppearance alloc] init];
[appearance configureWithOpaqueBackground];
appearance.backgroundColor = [UIColor whiteColor];
appearance.shadowColor = [UIColor colorWithWhite:0 alpha:0.1];
appearance.stackedLayoutAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor grayColor]};
appearance.stackedLayoutAppearance.selected.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor systemBlueColor]};
tabBar.standardAppearance = appearance;
if (@available(iOS 15.0, *)) {
tabBar.scrollEdgeAppearance = appearance;
}
} else {
tabBar.barTintColor = [UIColor whiteColor];
tabBar.tintColor = [UIColor systemBlueColor];
tabBar.unselectedItemTintColor = [UIColor grayColor];
}
}
#pragma mark - UITabBarControllerDelegate
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(@"切换到: %@", NSStringFromClass([viewController class]));
}
八、Swift 版本对照
@main
class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let tabBarController = UITabBarController()
tabBarController.delegate = self
// 首页
let homeNav = UINavigationController(rootViewController: HomeViewController())
homeNav.tabBarItem = UITabBarItem(title: "首页", image: UIImage(named: "home"), selectedImage: UIImage(named: "home_selected"))
// 消息(带角标)
let messageNav = UINavigationController(rootViewController: MessageViewController())
messageNav.tabBarItem = UITabBarItem(title: "消息", image: UIImage(named: "message"), selectedImage: UIImage(named: "message_selected"))
messageNav.tabBarItem.badgeValue = "5"
tabBarController.viewControllers = [homeNav, messageNav]
// 外观
if #available(iOS 13.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .white
appearance.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.gray]
appearance.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.systemBlue]
tabBarController.tabBar.standardAppearance = appearance
if #available(iOS 15.0, *) {
tabBarController.tabBar.scrollEdgeAppearance = appearance
}
}
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
// UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
print("切换到: \(type(of: viewController))")
}
}
// push 时隐藏 tabBar
let nextVC = NextViewController()
nextVC.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(nextVC, animated: true)
// 代码切换标签
tabBarController.selectedIndex = 1
九、总结
- 基本用法:
UITabBarController通过viewControllers数组管理子控制器,每个子控制器通过tabBarItem控制标签显示;实际开发中子控制器通常包装在UINavigationController中。 - UITabBarItem:支持标题、普通图片、选中图片、角标(badgeValue)、位置偏移等;
image默认被 tintColor 渲染为模板图。 - 代理:
UITabBarControllerDelegate提供shouldSelectViewController:(阻止切换)和didSelectViewController:(切换后回调),以及标签编辑的三个方法。 - More 页面:超过 5 个标签时自动生成"更多"页面,
moreNavigationController访问其导航控制器,customizableViewControllers控制可编辑排序的标签。 - 外观管理:iOS 13+ 推荐使用
UITabBarAppearance(standardAppearance + scrollEdgeAppearance),旧方式用barTintColor/tintColor/unselectedItemTintColor/backgroundImage/shadowImage。 - 切换与隐藏:
selectedIndex/selectedViewController代码切换;hidesBottomBarWhenPushed在 push 时隐藏 tabBar。 - iOS 15 注意:
scrollEdgeAppearance默认为透明,需显式设置否则 tabBar 背景透明。

浙公网安备 33010602011771号