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 内部实现了它来处理标签点击。开发者应使用 UITabBarControllerDelegatetabBarController: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 起,UITabBarscrollEdgeAppearance 默认为透明背景(与 UINavigationBar 类似),如果不设置会导致 tabBar 背景透明。需要显式设置 scrollEdgeAppearancestandardAppearance 一致。

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 背景透明。

posted @ 2015-08-02 23:21  Mr.陳  阅读(389)  评论(0)    收藏  举报