AppDelegate.m#application:didFinishLaunchingWithOptions主要分为三步:
1. 创建窗体
2. 设置窗体的根视图控制器
3. 显示窗体

iOS 自定义UITabBarController_#import


iOS 自定义UITabBarController_自定义_02


iOS 自定义UITabBarController_ico_03


iOS 自定义UITabBarController_#import_04

父子控制器:当控制器的view互为父子关系,那么控制器最好也互为父子关系


问题:所有代码写到AppDelegate中不够简介,方法太臃肿,而且①:UITabBarController默认会对tabBarItem选中状态的图片进行渲染成蓝色,②:标题的字体的颜色也是蓝色,③:发布图片显示也有问题

自定义UITabBarController

将创建子控制器的代码封住到自定义的类中, 将创建子控制器和给子控制器的属性赋值的代码进一步抽取

#import "BWTabBarController.h"

#import "BWEssenceViewController.h"
#import "BWFriendTrendViewController.h"
#import "BWMeViewController.h"
#import "BWNewViewController.h"
#import "BWPublishViewController.h"

@interface BWTabBarController ()

@end

@implementation BWTabBarController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self addAllChildViewController];

    [self setupAllTabBarItemTitleAndImage];
}

// 添加子视图控制器
- (void)addAllChildViewController {
    // 2.1 精华
    BWEssenceViewController *essenceViewController = [[BWEssenceViewController alloc] init];
    UINavigationController *essenceNavigationController = [[UINavigationController alloc] initWithRootViewController:essenceViewController];
    [self addChildViewController:essenceNavigationController];

    // 2.2 新帖
    BWNewViewController *newViewController = [[BWNewViewController alloc] init];
    UINavigationController *newNavigationController = [[UINavigationController alloc] initWithRootViewController:newViewController];
    [self addChildViewController:newNavigationController];

    // 2.3 发布
    BWPublishViewController *publishViewController = [[BWPublishViewController alloc] init];
    [self addChildViewController:publishViewController];

    // 2.4 关注
    BWFriendTrendViewController *friendTrendViewController = [[BWFriendTrendViewController alloc] init];
    UINavigationController *friendTrendNavigationController = [[UINavigationController alloc] initWithRootViewController:friendTrendViewController];
    [self addChildViewController:friendTrendNavigationController];

    // 2.5 我
    BWMeViewController *meViewController = [[BWMeViewController alloc] init];
    UINavigationController *meNavigationController = [[UINavigationController alloc] initWithRootViewController:meViewController];
    [self addChildViewController:meNavigationController];
}

- (void)stupAllTabBarItemTitleAndImage {
    // 1. 精华
    UINavigationController *essenceNavigationController = self.childViewControllers[0];
    essenceNavigationController.tabBarItem.title = @"精华";
    essenceNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_essence_icon"];
    essenceNavigationController.tabBarItem.selectedImage = [UIImage imageNamed:@"tabBar_essence_click_icon"];

    // 2. 新帖
    UINavigationController *newNavigationController = self.childViewControllers[1];
    newNavigationController.tabBarItem.title = @"新帖";
    newNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_new_icon"];
    newNavigationController.tabBarItem.selectedImage = [UIImage imageNamed:@"tabBar_new_click_icon"];

    // 3. 发布
    BWPublishViewController *publishViewController = self.childViewControllers[2];
    publishViewController.tabBarItem.image = [UIImage imageNamed:@"tabBar_publish_icon"];
    publishViewController.tabBarItem.selectedImage = [UIImage imageNamed:@"tabBar_publish_click_icon"];

    // 关注
    UINavigationController *friendTrendNavigationController = self.childViewControllers[3];
    friendTrendNavigationController.tabBarItem.title = @"关注";
    friendTrendNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_friendTrends_icon"];
    friendTrendNavigationController.tabBarItem.selectedImage = [UIImage imageNamed:@"tabBar_friendTrends_click_icon"];

    // 我
    UINavigationController *meNavigationController = self.childViewControllers[4];
    meNavigationController.tabBarItem.title = @"我";
    meNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_me_icon"];
    meNavigationController.tabBarItem.selectedImage = [UIImage imageNamed:@"tabBar_me_click_icon"];
}
@end

AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 1. 创建窗口
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

    // 2. 设置窗口的根试图控制器
    BWTabBarController *tabBarController = [[BWTabBarController alloc] init];
    self.window.rootViewController = tabBarController;

    // 3. 显示窗口
    [self.window makeKeyAndVisible];

    return YES;
}

AppDelegate代码变得简洁了,但是上述三个问题还没有解决

UITabBarItem选中的图片展示有问题是因为图片被系统渲染了

方式一: 通过Xcode修改图片的渲染方式

原因: UITabBarItem中Image的图片的默认的渲染方式是:Default方式,这样通过[UIImage imageNamed:]方式是获取渲染后的图片,将渲染后的图片赋值给UITabBarItem.selectedImage,所以才会出现这种问题; 修改图片的渲染方式为原始图片即可: Render As:Original Image

具体操作:全选tabBar所有的图片并设置Render As:Original Image

iOS 自定义UITabBarController_#import_05

方式二:使用代码方式

方式一分析了问题出现的根本原因是将渲染后的图片赋值给了tabBarItem.selectedImage, 所以解决办法就是 从UIImage对象中获取原始图片,然后直接赋值即可

// 获取Default 渲染模式Render Mode的图片
UIImage *image = [UIImage imageNamed:@"tabBar_essence_click_icon"]; 
// 获取图片的原始图片 
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];  
essenceNavigationController.tabBarItem.selectedImage = image;

可以提供一个类别

@implementation UIImage (Image)

+ (UIImage *)imageOriginalWithName:(NSString *)name {
    UIImage *image = [UIImage imageNamed:name];
    return [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
}
@end

essenceNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_essence_click_icon"];

两种方式比较:
方式一简单,方式二稍微麻烦,但是方式一有可能美工换图后,忘记设置渲染方式而使用代码方式就没有这种顾虑

设置每个UITabBarItem选中状态的标题颜色

设置UITabBarItem选中状态下的标题颜色即可

NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
attrs[NSForegroundColorAttributeName] = [UIColor blackColor];

[essenceNavigationController.tabBarItem setTitleTextAttributes:attrs forState:UIControlStateSelected];

每个都要设置太麻烦,可以在load中统一设置一下

+ (void)load {
    [super load];
    // 统一设置当前类的所有UITabBarItem的外观 (外观当包含在的时候)
    UITabBarItem *tabBarItem = [UITabBarItem appearanceWhenContainedIn:self, nil];
    // Selected State
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSForegroundColorAttributeName] = [UIColor blackColor];
    [tabBarItem setTitleTextAttributes:attrs forState:UIControlStateSelected];

    // Normal State
    NSMutableDictionary *attrsNormal = [NSMutableDictionary dictionary];
    attrsNormal[NSFontAttributeName] = [UIFont systemFontOfSize:13];
    [tabBarItem setTitleTextAttributes:attrsNormal forState:UIControlStateNormal];
}

UIAppearance是一个协议,是用于全局一次性统一设置外貌的,只能在控件显示之前使用
[UIXxx appearance]; // 调用该方法会统一对App中所有的UIXxx 的外观进行设置, App中所有的,一般不用

[UITabBarItem appearanceWhenContainedIn:self, nil]; // 该方法设置只对当前类有效,一般使用这种方式


发布按钮显式不出来

发布按钮显示不出来是因为图片的尺寸太大了,从而系统在渲染的图片的时候渲染有问题,解决办法是:将图片对应的原始图片赋值给UITabBarItem.selectedImage即可

publishViewController.tabBarItem.image = [UIImage imageOriginalWithName:@"tabBar_publish_icon"];

发布图片的位置居中:

方式一:调整图片的内边距imageInsets

图片不居中修改图片对应的内边距即可,左右内边距不变,上下内边距下调即可居中
publishViewController.tabBarItem.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0);
但这种方式一但选中就一直处于选中状态,当选中别的按钮,此时发布按钮还是选中状态,这是有问题的

方式二:自定义发布按钮UIButton,即自定义UITabBar

实现思路:在自定义的UITabBarController中先将发布控制器不添加到UITabBarController中,
然后在自定义的UITabBar的layoutSubviews中调整这4个按钮的横坐标位置,将发布的中间位置预先空着,然后在再创建一个UIButton作为发布按钮添加到UITabBar的中间位置即可,这样由于是UIButton这样按钮就会有正常和选中两种状态,这样当点击发布按钮后,按钮状态能从选中状态自动回到正常状态 。

该方式 发布的子视图控制器就不用再添加了

// 去掉发布的子视图控制器
- (void)addAllChildViewController {
    // 1 精华
    BWEssenceViewController *essenceViewController = [[BWEssenceViewController alloc] init];
    UINavigationController *essenceNavigationController = [[UINavigationController alloc] initWithRootViewController:essenceViewController];
    [self addChildViewController:essenceNavigationController];

    // 2 新帖
    BWNewViewController *newViewController = [[BWNewViewController alloc] init];
    UINavigationController *newNavigationController = [[UINavigationController alloc] initWithRootViewController:newViewController];
    [self addChildViewController:newNavigationController];


    // 4 关注
    BWFriendTrendViewController *friendTrendViewController = [[BWFriendTrendViewController alloc] init];
    UINavigationController *friendTrendNavigationController = [[UINavigationController alloc] initWithRootViewController:friendTrendViewController];
    [self addChildViewController:friendTrendNavigationController];

    // 5 我
    BWMeViewController *meViewController = [[BWMeViewController alloc] init];
    UINavigationController *meNavigationController = [[UINavigationController alloc] initWithRootViewController:meViewController];
    [self addChildViewController:meNavigationController];
}

// 去掉发布的子视图控制器对应的tabBarItem 设置
- (void)stupAllTabBarItemTitleAndImage {
    // 1. 精华
    UINavigationController *essenceNavigationController = self.childViewControllers[0];
    essenceNavigationController.tabBarItem.title = @"精华";
    essenceNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_essence_icon"];
    essenceNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_essence_click_icon"];

    // 2. 新帖
    UINavigationController *newNavigationController = self.childViewControllers[1];
    newNavigationController.tabBarItem.title = @"新帖";
    newNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_new_icon"];
    newNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_new_click_icon"];

    // 4. 关注
    UINavigationController *friendTrendNavigationController = self.childViewControllers[2];
    friendTrendNavigationController.tabBarItem.title = @"关注";
    friendTrendNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_friendTrends_icon"];
    friendTrendNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_friendTrends_click_icon"];

    // 5. 我
    UINavigationController *meNavigationController = self.childViewControllers[3];
    meNavigationController.tabBarItem.title = @"我";
    meNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_me_icon"];
    meNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_me_click_icon"];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self addAllChildViewController];

    [self stupAllTabBarItemTitleAndImage];

    [self setUpTabBar];  // 初始化tabBar
}

- (void)setupTabBar {
    BWTabBar *tabBar = [[BWTabBar alloc] init];
    [self setValue:tabBar forKey:@"tabBar"];  //该属性是私有属性,只能通过KVC方式赋值 将系统的tabBar替换成BWTabBar
}
@interface BWTabBar ()
@property (weak, nonatomic) UIButton *plusButton;
@end

@implementation BWTabBar

// 重新调整其他4个按钮的x坐标,并将中间的发布按钮位置暂时空出来
- (void)layoutSubviews {
    [super layoutSubviews];

    NSInteger count = self.items.count;
    CGFloat buttonWidth = self.bounds.size.width / (count + 1);
    CGFloat buttonHeight = self.bounds.size.height;
    CGFloat x = 0;
    int i = 0;

    for (UIView *tabBarButton in self.subviews) {
        if ([tabBarButton isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
            if (i == 2) {
                i += 1;
            }

            x = i * buttonWidth;
            tabBarButton.frame = CGRectMake(x, 0, buttonWidth, buttonHeight);
            i++;
        }
    }

    // 将发布按钮添加到tabBar的中间位置
    self.plusButton.center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
}


// 发布按钮:懒加载
- (UIButton *)plusButton {
    if (_plusButton == nil) {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setImage:[UIImage imageNamed:@"tabBar_publish_icon"] forState:UIControlStateNormal];
        [btn setImage:[UIImage imageNamed:@"tabBar_publish_click_icon"] forState:UIControlStateSelected];
        [btn sizeToFit];

        [self addSubview:btn];
        _plusButton = btn;
    }

    return _plusButton;
}
@end

额外话题:.pch文件用于定义公用的宏、公用的头文件、用于定于Log(发布时不打印),定义好需要在Build Setting中设置一下对应的路径


完整代码

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 1. 创建窗口--------------------------------------------------
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

    // 2. 设置窗口的根试图控制器--------------------------------------
    BWTabBarController *tabBarController = [[BWTabBarController alloc] init];
    self.window.rootViewController = tabBarController;

    // 3. 显示窗口--------------------------------------------------
    [self.window makeKeyAndVisible];

    return YES;
}

BWTabBarController

#import "BWTabBarController.h"

#import "BWEssenceViewController.h"
#import "BWFriendTrendViewController.h"
#import "BWMeViewController.h"
#import "BWNewViewController.h"
#import "BWPublishViewController.h"

#import "BWTabBar.h"
#import "UIImage+Image.h"

@interface BWTabBarController ()

@end

@implementation BWTabBarController

+ (void)load {
    [super load];
    UITabBarItem *tabBarItem = [UITabBarItem appearanceWhenContainedIn:self, nil];
    // Selected
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSForegroundColorAttributeName] = [UIColor blackColor];
    [tabBarItem setTitleTextAttributes:attrs forState:UIControlStateSelected];

    // normal
    NSMutableDictionary *attrsNormal = [NSMutableDictionary dictionary];
    attrsNormal[NSFontAttributeName] = [UIFont systemFontOfSize:13];
    [tabBarItem setTitleTextAttributes:attrsNormal forState:UIControlStateNormal];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self addAllChildViewController];

    [self stupAllTabBarItemTitleAndImage];

    [self setupTabBar];
}

- (void)addAllChildViewController {
    // 2.1 精华
    BWEssenceViewController *essenceViewController = [[BWEssenceViewController alloc] init];
    UINavigationController *essenceNavigationController = [[UINavigationController alloc] initWithRootViewController:essenceViewController];
    [self addChildViewController:essenceNavigationController];

    // 2.2 新帖
    BWNewViewController *newViewController = [[BWNewViewController alloc] init];
    UINavigationController *newNavigationController = [[UINavigationController alloc] initWithRootViewController:newViewController];
    [self addChildViewController:newNavigationController];


    // 2.4 关注
    BWFriendTrendViewController *friendTrendViewController = [[BWFriendTrendViewController alloc] init];
    UINavigationController *friendTrendNavigationController = [[UINavigationController alloc] initWithRootViewController:friendTrendViewController];
    [self addChildViewController:friendTrendNavigationController];

    // 2.5 我
    BWMeViewController *meViewController = [[BWMeViewController alloc] init];
    UINavigationController *meNavigationController = [[UINavigationController alloc] initWithRootViewController:meViewController];
    [self addChildViewController:meNavigationController];
}
- (void)stupAllTabBarItemTitleAndImage {
    // 精华
    UINavigationController *essenceNavigationController = self.childViewControllers[0];
    essenceNavigationController.tabBarItem.title = @"精华";
    essenceNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_essence_icon"];
    essenceNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_essence_click_icon"];

    // 新帖
    UINavigationController *newNavigationController = self.childViewControllers[1];
    newNavigationController.tabBarItem.title = @"新帖";
    newNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_new_icon"];
    newNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_new_click_icon"];

    // 关注
    UINavigationController *friendTrendNavigationController = self.childViewControllers[2];
    friendTrendNavigationController.tabBarItem.title = @"关注";
    friendTrendNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_friendTrends_icon"];
    friendTrendNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_friendTrends_click_icon"];

    // 我
    UINavigationController *meNavigationController = self.childViewControllers[3];
    meNavigationController.tabBarItem.title = @"我";
    meNavigationController.tabBarItem.image = [UIImage imageNamed:@"tabBar_me_icon"];
    meNavigationController.tabBarItem.selectedImage = [UIImage imageOriginalWithName:@"tabBar_me_click_icon"];

}

- (void)setupTabBar {
    BWTabBar *tabBar = [[BWTabBar alloc] init];
    [self setValue:tabBar forKey:@"tabBar"];  // 将系统的tabBar替换成BWTabBar
}

@end

BWTabBar

#import "BWTabBar.h"

@interface BWTabBar ()
@property (weak, nonatomic) UIButton *plusButton;
@end

@implementation BWTabBar

- (void)layoutSubviews {
    [super layoutSubviews];

    NSInteger count = self.items.count;
    CGFloat buttonWidth = self.bounds.size.width / (count + 1);
    CGFloat buttonHeight = self.bounds.size.height;
    CGFloat x = 0;
    int i = 0;

    for (UIView *tabBarButton in self.subviews) {
        if ([tabBarButton isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
            if (i == 2) {
                i += 1;
            }

            x = i * buttonWidth;
            tabBarButton.frame = CGRectMake(x, 0, buttonWidth, buttonHeight);
            i++;
        }
    }

    // 调整发布按钮居中
    self.plusButton.center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
}


- (UIButton *)plusButton {
    if (_plusButton == nil) {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setImage:[UIImage imageNamed:@"tabBar_publish_icon"] forState:UIControlStateNormal];
        [btn setImage:[UIImage imageNamed:@"tabBar_publish_click_icon"] forState:UIControlStateSelected];
        [btn sizeToFit];

        [self addSubview:btn];
        _plusButton = btn;
    }

    return _plusButton;
}
@end

UITabBar内部结构:

  • UITabBar
  • UITabBarButton
  • UITabBarSwappableImageView
  • UITabBarButtonLabel
  • UITabBarButton

UITabBar的UITabBarButton是什么时候添加到UITabBar上的???

上面代码有一点疑惑:
就是setupTabBar的方法实现,该方法直接实例化就赋值,然后BWTabBar中也没有重写初始化操作将其他几个UITabBarButton添加到BWTabBar,就直接在layoutSubviews中进行布局,而且self.subviews还能拿到子视图,那么这些子视图代码没有显式去添加,一定是系统自动添加的了,那么系统是什么时候添加的,经研究是在调用[super viewWillAppear:animated]中将其他UITabBarButton添加到tabBar上,而我们调用setUpTabBar 方法是在viewDidLoad 中调用的, 也就是说按照调用顺序,我们是先将系统的UITabBar替换成自己的BWTabBar,然后系统接着调用viewWillAppear:方法,然后将其他UIBarButton添加到tabBar上,然而此时的tabBar已经不是系统的UITabBar了而是我们自己的BWTabBar了,所以BWTabBar上就有其他4个按钮,接着就可以在layoutSubviews中对这四个UITabBarButton进行布局操作了;

iOS 自定义UITabBarController_#import_06

- (void)setUpTabBar {
    BWTabBar *tabBar = [[BWTabBar alloc] init];
    [self setValue:tabBar forKey:@"tabBar"];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self addAllChildViewController];

    [self stupAllTabBarItemTitleAndImage];

    [self setupTabBar];
}

最终解决方案

上述代码虽解决了所有问题,但是耦合度还是太高,UITabBarController 和其他几个视图控制器,包括图片名字都写死在自定义的UITabBarController了,要将该自定义的UITabBarController 移植到其他项目中还要修改,最好的办法是将这些作为方法的参数继续重构代码,保证移植时不修改UITabBarController中的代码, 可以自己对以上代码进行重构,也可以使用github上的开源项目,别人已经做的很完美了,直接使用即可