CYLTabBarController

【中國(guó)特色 TabBar】一行代碼實(shí)現(xiàn) Lottie 動(dòng)畫(huà) TabBar,支持中間帶 + 號(hào)的 TabBar 樣式,自帶紅點(diǎn)角標(biāo),支持動(dòng)態(tài)刷新?!緄OS13 & Dark Mode & iPhone XS MAX supported】。

前言

首先:不僅僅是一行代碼!

官方聲稱的 "一行代碼實(shí)現(xiàn) Lottie 動(dòng)畫(huà) TabBar" 其實(shí)有虛晃一槍的嫌疑,讓你聽(tīng)起來(lái)誤以為用一行代碼就可以實(shí)現(xiàn)淘寶「閑魚(yú)」那種既帶 ? 號(hào)按鈕又帶 Lottie 動(dòng)畫(huà)的 TabBar 了。其實(shí)折騰下來(lái)還是要寫(xiě)個(gè)幾百行代碼的。正確的描述應(yīng)該是:集成 CYLTabBarController 之后,再導(dǎo)入所需的 JSON 文件(該文件用于描述 TabBar 的 Lottie 動(dòng)畫(huà)),并添加一行代碼,即可讓 TabBar 實(shí)現(xiàn) Lottie 動(dòng)畫(huà)。

因此,所謂的 "一行代碼實(shí)現(xiàn) Lottie 動(dòng)畫(huà)”,其實(shí)就是配置方法里面再加一個(gè) key 值為 CYLTabBarLottieURL 的屬性,它的值是一個(gè) NSURL 鏈接,指向你項(xiàng)目中描述 Lottie 動(dòng)畫(huà)的 JSON 文件。你還是需要自己提前準(zhǔn)備好 Lottie 的 JSON 文件的,所以說(shuō),所謂的一行代碼就是,當(dāng)涉及到 Lottie 動(dòng)畫(huà)時(shí),你給系統(tǒng)傳一個(gè)帶 Lottie 動(dòng)畫(huà)的 URL 文件(既然“一行代碼”是這個(gè)意思的話,我是不是可以說(shuō)一行代碼實(shí)現(xiàn)一個(gè)全套 Google 搜索引擎功能呢?打開(kāi)一個(gè) google.com 的 HTML 5 頁(yè)面不就實(shí)現(xiàn)了嘛)。

另外,一看見(jiàn) CYLTabBarController 這個(gè)框架是國(guó)人寫(xiě)的,而且 README 文檔是中文的,真的是喜極而泣??,讓我誤以為花個(gè) 5 分鐘便可以從入門(mén)到精通,最終折騰了好幾天,下載了好幾個(gè) Demo 才算弄明白。

鑒于官方 README.md 文檔的“含糊不清”,與配套 Demo 寫(xiě)法上也存在很大的出入,還是讓一開(kāi)始初入了解該框架的同學(xué)造成了很大的困擾。

接下來(lái),我會(huì)通過(guò)實(shí)現(xiàn)一個(gè)模仿「閑魚(yú)」TabBar 動(dòng)畫(huà)的 Demo 來(lái)讓大家重新了解它。

開(kāi)始使用

準(zhǔn)備:新建 Xcode 項(xiàng)目

在 Xcode 11 環(huán)境下新建一個(gè) Single View App 項(xiàng)目,打開(kāi)這個(gè)新的項(xiàng)目,可以看到系統(tǒng)除了會(huì)自動(dòng)生成 AppDelegate 文件外,還會(huì)自動(dòng)生成一個(gè)名為 SceneDelegate 的類文件。

項(xiàng)目目錄

?? SceneDelegate 是 iOS 13 下的新特性,查看 WWDC 2019: Optimizing App Launch 可以知道這到底是啥,但是 CYLTabBarController 還未適配該特性(截止 1.28.3 版本),基于 SceneDelegate 集成該框架應(yīng)用會(huì)崩潰!所以我們要先把 SceneDelegate 特性刪除才行。

步驟:

  1. 首先打開(kāi) Info.plist 文件,找到下面這兩個(gè)屬性并刪除。
  1. 刪除 SceneDelegate 類文件,其實(shí)可刪可不刪,但既然我們用不到就刪了吧。
  2. 修改 AppDelegate.h 文件,加上 UIWindow 屬性。
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow * window;

@end
  1. 修改 AppDelegate.m 文件,設(shè)置 UIWindow 設(shè)置主窗口,并刪除多余的 <UISceneSession> 代理協(xié)議
#import "AppDelegate.h"
#import "ViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // 設(shè)置根視圖控制器
    ViewController *controller = [[ViewController alloc] init];
    // 窗口根視圖控制器
    self.window.rootViewController = controller;
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    
    return YES;
}

// 下面多余的代碼請(qǐng)刪除

@end

運(yùn)行項(xiàng)目,無(wú)報(bào)錯(cuò)則繼續(xù)往下。

第一步:使用 CocoaPods 導(dǎo)入 CYLTabBarController

可以很良心的說(shuō),README.md 文檔中在 第一步使用 CocoaPods 導(dǎo)入 CYLTabBarController 一章算是描述地最詳盡的了,它居然還教你如何安裝 CocoaPods,堪比 CocoaPods 環(huán)境搭建教程了。

但是,有一點(diǎn)需要提醒的是,安裝 CocoaPods 請(qǐng)勿使用 sudo gem install cocoapods 這個(gè)命令,如果運(yùn)行該命令提示存在權(quán)限問(wèn)題:

# 錯(cuò)誤示例
$ sudo gem install cocoapods
Password:
Fetching cocoapods-1.8.4.gem
Fetching cocoapods-core-1.8.4.gem
Successfully installed cocoapods-core-1.8.4
ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /usr/bin directory.
# Mac OS 系統(tǒng)升級(jí)之后,系統(tǒng)把 /usr/bin 目錄的寫(xiě)入權(quán)限禁用了,因此我們需要指定安裝到別的目錄下。

# 正確示例,需要安裝 cocoapods 到指定目錄下
$ sudo gem install cocoapods -n /usr/local/bin
Successfully installed cocoapods-1.8.4
Parsing documentation for cocoapods-1.8.4
Installing ri documentation for cocoapods-1.8.4
Done installing documentation for cocoapods after 2 seconds
1 gem installed

另外,Podfile 示例文件如下:

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

target 'CYLTabBarControllerDemo' do
  # Comment the next line if you don't want to use dynamic frameworks
  # use_frameworks!

  # Pods for CYLTabBarControllerDemo
  pod 'CYLTabBarController', '~> 1.28.3'        # 默認(rèn)不依賴Lottie
  pod 'CYLTabBarController/Lottie', '~> 1.28.1' # 依賴Lottie庫(kù)
  pod 'ChameleonFramework'                      # 顏色框架
  pod 'YYKit'                                   # 會(huì)用到幾個(gè)輔助方法

end

第二步:新建 AppDelegate 分類文件,初始化并設(shè)置 CYLTabBarController

如果你查看 CYLTabBarController 框架的 README.md 文檔,作者會(huì)讓你把很多配置方法寫(xiě)在 AppDelegate 這個(gè)類中。

但實(shí)際應(yīng)用場(chǎng)景中,有很多配置方法都要寫(xiě)在這個(gè)文件里面,比如日志框架的配置、推送通知框架、第三方支付回調(diào)配置...還有一大堆工具類配置。

所以我習(xí)慣上會(huì)把不同的配置文件單獨(dú)寫(xiě)在各自的分類(Category)中。

具體原因可以去看看《Effective Objective-C 2.0 編寫(xiě)高質(zhì)量 iOS 與 OS X 代碼的 52 個(gè)有效方法 》一書(shū)中的第 24 條建議。

因此,我們需要新建一個(gè) AppDelegate 分類文件,然后把所有與初始化 CYLTabBarController 框架相關(guān)的代碼都寫(xiě)在 AppDelegate+CYLTabBar 文件里面,保持代碼整潔,方便修改。

  1. 新建一個(gè) AppDelegate 分類文件。

    點(diǎn)擊 Xcode 導(dǎo)航欄 — File — New — File...— 選擇 iOS Source 列表下的「Objective-C File」,新建文件類型和文件名如下:

    新建 AppDelegate 分類
  2. 在 Assets.xcassets 資產(chǎn)庫(kù)中導(dǎo)入你所需要的圖片文件。

  3. 在分類中新建一個(gè)方法,用于配置主窗口:

#import "AppDelegate.h"

NS_ASSUME_NONNULL_BEGIN

/// 這是 AppDelegate 的分類,用于配置 CYLTabBarController
@interface AppDelegate (CYLTabBar)

/// 配置主窗口
- (void)hql_configureForTabBarController;

@end

NS_ASSUME_NONNULL_END

然后我們?cè)?.m 文件中實(shí)現(xiàn)它,設(shè)置 CYLTabBarController 的兩個(gè)數(shù)組:控制器數(shù)組和配置 tabBar 外觀樣式的屬性數(shù)組:

@implementation AppDelegate (CYLTabBar)

#pragma mark - Public

- (void)hql_configureForTabBarController {
    // 設(shè)置主窗口,并設(shè)置根視圖控制器
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    // 初始化 CYLTabBarController 對(duì)象
    CYLTabBarController *tabBarController =
        [CYLTabBarController tabBarControllerWithViewControllers:[self viewControllers]
                                           tabBarItemsAttributes:[self tabBarItemsAttributes]];
    // 設(shè)置遵守委托協(xié)議
    tabBarController.delegate = self;
    // 將 CYLTabBarController 設(shè)置為 window 的 RootViewController
    self.window.rootViewController = tabBarController;
}

#pragma mark - Private

/// 控制器數(shù)組
- (NSArray *)viewControllers {
    // 首頁(yè)
    HomeViewController *homeVC = [[HomeViewController alloc] init];
    homeVC.navigationItem.title = @"首頁(yè)";
    CYLBaseNavigationController *homeNC = [[CYLBaseNavigationController alloc] initWithRootViewController:homeVC];
    [homeNC cyl_setHideNavigationBarSeparator:YES];
    
    // 同城
    MyCityViewController *myCityVC = [[MyCityViewController alloc] init];
    myCityVC.navigationItem.title = @"同城";
    CYLBaseNavigationController *myCityNC = [[CYLBaseNavigationController alloc] initWithRootViewController:myCityVC];
    [myCityNC cyl_setHideNavigationBarSeparator:YES];
    
    // 消息
    MessageViewController *messageVC = [[MessageViewController alloc] init];
    messageVC.navigationItem.title = @"消息";
    CYLBaseNavigationController *messageNC = [[CYLBaseNavigationController alloc] initWithRootViewController:messageVC];
    [messageNC cyl_setHideNavigationBarSeparator:YES];
    
    // 我的
    AccountViewController *accountVC = [[AccountViewController alloc] init];
    accountVC.navigationItem.title = @"我的";
    CYLBaseNavigationController *accountNC = [[CYLBaseNavigationController alloc] initWithRootViewController:accountVC];
    [accountNC cyl_setHideNavigationBarSeparator:YES];
    
    NSArray *viewControllersArray = @[homeNC, myCityNC, messageNC, accountNC];
    return viewControllersArray;
}

/// tabBar 屬性數(shù)組
- (NSArray *)tabBarItemsAttributes {
    NSDictionary *homeTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"首頁(yè)",
        CYLTabBarItemImage: @"home_normal",
        CYLTabBarItemSelectedImage: @"home_highlight",
    };
    NSDictionary *myCityTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"同城",
        CYLTabBarItemImage: @"mycity_normal",
        CYLTabBarItemSelectedImage: @"mycity_highlight",
    };
    NSDictionary *messageTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"消息",
        CYLTabBarItemImage: @"message_normal",
        CYLTabBarItemSelectedImage: @"message_highlight",
    };
    NSDictionary *accountTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"我的",
        CYLTabBarItemImage: @"account_normal",
        CYLTabBarItemSelectedImage: @"account_highlight",
    };

    NSArray *tabBarItemsAttributes = @[
        homeTabBarItemsAttributes,
        myCityTabBarItemsAttributes,
        messageTabBarItemsAttributes,
        accountTabBarItemsAttributes
    ];
    return tabBarItemsAttributes;
}

@end
  1. 最后,我們回到 AppDelegate.m 文件,導(dǎo)入上一步新建的分類頭文件 :
#import "AppDelegate+CYLTabBar.h"
  1. 然后一行代碼調(diào)用配置主窗口的方法:
#import "AppDelegate.h"
#import "AppDelegate+CYLTabBar.h" // 導(dǎo)入的分類文件
   
@implementation AppDelegate
   
   
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
       
    // 調(diào)用分類文件中的配置主窗口方法:
    [self hql_configureForTabBarController];
       
    return YES;
}
   
@end
  1. 至此,常見(jiàn)的帶 4 個(gè) TabBarItem 的應(yīng)用框架就搭建好啦:
通用的 tabBar 樣式

小結(jié):將 CYLTabBarController 實(shí)例化為窗口的根視圖控制器即可。

第三步:添加不規(guī)則加號(hào)按鈕

創(chuàng)建一個(gè)繼承自 CYLPlusButton 的子類對(duì)象 CYLPlusButtonSubclass。

CYLPlusButtonSubclass.h 聲明遵守 <CYLPlusButtonSubclassing> 協(xié)議

#import "CYLPlusButton.h"

NS_ASSUME_NONNULL_BEGIN

@interface CYLPlusButtonSubclass : CYLPlusButton <CYLPlusButtonSubclassing>

@end

NS_ASSUME_NONNULL_END

CYLPlusButtonSubclass.m 中實(shí)現(xiàn)創(chuàng)建按鈕的方法和遵守的協(xié)議方法

#import "CYLPlusButtonSubclass.h"

@implementation CYLPlusButtonSubclass

#pragma mark - Lifecycle

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        self.adjustsImageWhenHighlighted = NO;
    }
    return self;
}

//上下結(jié)構(gòu)的 button
- (void)layoutSubviews {
    [super layoutSubviews];

    // 控件大小,間距大小
    // 注意:一定要根據(jù)項(xiàng)目中的圖片去調(diào)整下面的0.7和0.9,Demo之所以這么設(shè)置,因?yàn)閐emo中的 plusButton 的 icon 不是正方形。
    CGFloat const imageViewEdgeWidth   = self.bounds.size.width * 0.7;
    CGFloat const imageViewEdgeHeight  = imageViewEdgeWidth;

    CGFloat const centerOfView    = self.bounds.size.width * 0.5;
    CGFloat const labelLineHeight = self.titleLabel.font.lineHeight;
    CGFloat const verticalMargin  = (self.bounds.size.height - labelLineHeight - imageViewEdgeHeight) * 0.5;

    // imageView 和 titleLabel 中心的 Y 值
    CGFloat const centerOfImageView  = verticalMargin + imageViewEdgeHeight * 0.5;
    CGFloat const centerOfTitleLabel = imageViewEdgeHeight  + verticalMargin * 2 + labelLineHeight * 0.5 - 1;

    //imageView position 位置
    self.imageView.bounds = CGRectMake(0, 0, imageViewEdgeWidth, imageViewEdgeHeight);
    self.imageView.center = CGPointMake(centerOfView, centerOfImageView);

    //title position 位置
    self.titleLabel.bounds = CGRectMake(0, 0, self.bounds.size.width, labelLineHeight);
    self.titleLabel.center = CGPointMake(centerOfView, centerOfTitleLabel);
}

#pragma mark - IBActions

- (void)clickPublish {
    // 如果按鈕的作用是觸發(fā)點(diǎn)擊事件,則調(diào)用此方法
}

#pragma mark - CYLPlusButtonSubclassing

+ (id)plusButton {
    CYLPlusButtonSubclass *button = [CYLPlusButtonSubclass buttonWithType:UIButtonTypeCustom];
    // 圖片尺寸:56*56、67*66、49*48(凸出 15)
    UIImage *normalButtonImage = [UIImage imageNamed:@"post_normal"];
    UIImage *hlightButtonImage = [UIImage imageNamed:@"post_highlight"];
    [button setImage:normalButtonImage forState:UIControlStateNormal];
    [button setImage:hlightButtonImage forState:UIControlStateHighlighted];
    [button setImage:hlightButtonImage forState:UIControlStateSelected];
    
    // 設(shè)置背景圖片
//    UIImage *normalButtonBackImage = [UIImage imageNamed:@"tabBar_post_back"];
//    [button setBackgroundImage:normalButtonBackImage forState:UIControlStateNormal];
//    [button setBackgroundImage:normalButtonBackImage forState:UIControlStateSelected];

    
    // 按鈕圖片距離上邊距增加 5,即向下偏移,按鈕圖片距離下邊距減少 5,即向下偏移。
    //button.imageEdgeInsets = UIEdgeInsetsMake(5, 0, -5, 0);
    [button setTitle:@"發(fā)布" forState:UIControlStateNormal];
    [button setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
    
    button.titleLabel.font = [UIFont systemFontOfSize:10 weight:UIFontWeightBold];
    [button sizeToFit]; // or set frame in this way `button.frame = CGRectMake(0.0, 0.0, 250, 100);`
    
    
    //自定義寬度
    button.frame = CGRectMake(0.0, 0.0, 59, 59);
    // button.backgroundColor = [UIColor redColor];
    
    // if you use `+plusChildViewController` , do not addTarget to plusButton.
    // [button addTarget:button action:@selector(clickPublish) forControlEvents:UIControlEventTouchUpInside];
    return button;
}

// 用來(lái)自定義加號(hào)按鈕的位置,如果不實(shí)現(xiàn)默認(rèn)居中。
+ (NSUInteger)indexOfPlusButtonInTabBar {
    return 2;
}

// 實(shí)現(xiàn)該方法后,能讓 PlusButton 的點(diǎn)擊效果與跟點(diǎn)擊其他 TabBar 按鈕效果一樣,跳轉(zhuǎn)到該方法指定的 UIViewController
+ (UIViewController *)plusChildViewController {

    UIViewController *v = [[UIViewController alloc] init];
    return v;
}

// 該方法是為了調(diào)整 PlusButton 中心點(diǎn)Y軸方向的位置,建議在按鈕超出了 tabbar 的邊界時(shí)實(shí)現(xiàn)該方法。
// 返回值是自定義按鈕中心點(diǎn) Y 軸方向的坐標(biāo)除以 tabbar 的高度,小于 0.5 表示 PlusButton 偏上,大于 0.5 則表示偏下。
// PlusButtonCenterY = multiplierOfTabBarHeight * tabBarHeight + constantOfPlusButtonCenterYOffset;
+ (CGFloat)multiplierOfTabBarHeight:(CGFloat)tabBarHeight {
    return  0.3;
}

// constantOfPlusButtonCenterYOffset 大于 0 會(huì)向下偏移,小于 0 會(huì)向上偏移。
+ (CGFloat)constantOfPlusButtonCenterYOffsetForTabBarHeight:(CGFloat)tabBarHeight {
    return (CYL_IS_IPHONE_X ? - 6 : 4);
}

@end

AppDelegate+CYLTabBar.m 中注冊(cè)該按鈕

在初始化 CYLTabBarController 對(duì)象步驟之前注冊(cè)按鈕:

- (void)hql_configureForTabBarController {
    // 設(shè)置主窗口,并設(shè)置根視圖控制器
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    
    // ?????? 注冊(cè)加號(hào)按鈕
    [CYLPlusButtonSubclass registerPlusButton];
    
    // 初始化 CYLTabBarController 對(duì)象
    CYLTabBarController *tabBarController =
        [CYLTabBarController tabBarControllerWithViewControllers:[self viewControllers]
                                           tabBarItemsAttributes:[self tabBarItemsAttributes]];
    // 設(shè)置遵守委托協(xié)議
    tabBarController.delegate = self;
    self.window.rootViewController = tabBarController;
}

自此,加號(hào)按鈕也添加完成啦。

帶不規(guī)則加號(hào)的 tabBar 樣式

第四步:設(shè)置自定義 TabBar 樣式

自定義 TabBar 字體、背景、陰影。

AppDelegate+CYLTabBar.m 文件中新增一個(gè)方法,用于設(shè)置 TabBar 樣式:

/// 自定義 TabBar 字體、背景、陰影
- (void)customizeTabBarInterface {
    // 設(shè)置文字屬性
    if (@available(iOS 10.0, *)) {
        [self cyl_tabBarController].tabBar.unselectedItemTintColor = [UIColor cyl_systemGrayColor];
        [self cyl_tabBarController].tabBar.tintColor = [UIColor cyl_labelColor];
    } else {
        UITabBarItem *tabBar = [UITabBarItem appearance];
        // 普通狀態(tài)下的文字屬性
        [tabBar setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor cyl_systemGrayColor]}
                              forState:UIControlStateNormal];
        // 選中狀態(tài)下的文字屬性
        [tabBar setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor cyl_labelColor]}
                              forState:UIControlStateSelected];
    }

    // 設(shè)置 TabBar 背景顏色:白色
    // ??[UIImage imageWithColor] 表示根據(jù)指定顏色生成圖片,該方法來(lái)自 <YYKit> 框架
    [[UITabBar appearance] setBackgroundImage:[UIImage imageWithColor:[UIColor whiteColor]]];
    
    // 去除 TabBar 自帶的頂部陰影
    [[self cyl_tabBarController] hideTabBarShadowImageView];
    
    // 設(shè)置 TabBar 陰影,無(wú)效
    // [[UITabBar appearance] setShadowImage:[UIImage imageNamed:@"tabBar_background_shadow"]];
    
    // 設(shè)置 TabBar 陰影
    CYLTabBarController *tabBarController = [self cyl_tabBarController];
    tabBarController.tabBar.layer.shadowColor = [UIColor blackColor].CGColor;
    tabBarController.tabBar.layer.shadowRadius = 15.0;
    tabBarController.tabBar.layer.shadowOpacity = 0.2;
    tabBarController.tabBar.layer.shadowOffset = CGSizeMake(0, 3);
    tabBarController.tabBar.layer.masksToBounds = NO;
    tabBarController.tabBar.clipsToBounds = NO;
}

然后在該文件的配置方法中調(diào)用:

- (void)hql_configureForTabBarController {
    // 設(shè)置主窗口,并設(shè)置根視圖控制器
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    
    // 注冊(cè)加號(hào)按鈕
    [CYLPlusButtonSubclass registerPlusButton];
    
    // 初始化 CYLTabBarController 對(duì)象
    CYLTabBarController *tabBarController =
        [CYLTabBarController tabBarControllerWithViewControllers:[self viewControllers]
                                           tabBarItemsAttributes:[self tabBarItemsAttributes]];
    // 設(shè)置遵守委托協(xié)議
    tabBarController.delegate = self;
    self.window.rootViewController = tabBarController;
    
    // ?????? 自定義 TabBar 字體、背景、陰影
     [self customizeTabBarInterface];
}

另外,不規(guī)則加號(hào)按鈕的背景圖片需要在 CYLPlusButtonSubclass.m 文件的 + (id)plusButton 中設(shè)置:

// 設(shè)置背景圖片
UIImage *normalButtonBackImage = [UIImage imageNamed:@"tabBar_post_back"];
[button setBackgroundImage:normalButtonBackImage forState:UIControlStateNormal];
[button setBackgroundImage:normalButtonBackImage forState:UIControlStateSelected];

下面是自定義后的 TabBar 樣式示例:

自定義 TabBar 樣式

第五步:添加點(diǎn)擊旋轉(zhuǎn)動(dòng)畫(huà)

當(dāng)點(diǎn)擊 TabBarItem 時(shí),為它設(shè)置旋轉(zhuǎn)動(dòng)畫(huà),需要讓 AppDelegate 遵守 <CYLTabBarControllerDelegate, UITabBarControllerDelegate> 委托協(xié)議,以監(jiān)聽(tīng)點(diǎn)擊觸發(fā)事件:

// ?????? 聲明遵守委托協(xié)議
@interface AppDelegate () <CYLTabBarControllerDelegate, UITabBarControllerDelegate>

@end

@implementation AppDelegate (CYLTabBar)

- (void)hql_configureForTabBarController {
        
        // 省略...
    
    // 初始化 CYLTabBarController 對(duì)象
    CYLTabBarController *tabBarController =
        [CYLTabBarController tabBarControllerWithViewControllers:[self viewControllers]
                                           tabBarItemsAttributes:[self tabBarItemsAttributes]];
    // ?????? 設(shè)置讓 AppDelegate 遵守委托協(xié)議
    tabBarController.delegate = self;
    self.window.rootViewController = tabBarController;
}

接下來(lái)是讓 AppDelegate 實(shí)現(xiàn)委托協(xié)議方法,點(diǎn)擊 TabBarItem 時(shí)觸發(fā)旋轉(zhuǎn)動(dòng)畫(huà),點(diǎn)不規(guī)則加號(hào)按鈕時(shí)觸發(fā)放大動(dòng)畫(huà):

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
    // 確保 PlusButton 的選中狀態(tài)
    [[self cyl_tabBarController] updateSelectionStatusIfNeededForTabBarController:tabBarController shouldSelectViewController:viewController];
    return YES;
}

- (void)tabBarController:(UITabBarController *)tabBarController didSelectControl:(UIControl *)control {
    UIView *animationView;
    NSLog(@"??\n 類名與方法名:%@,\n 第 %@ 行,\n description : %@,\n tabBarChildViewControllerIndex: %@, tabBarItemVisibleIndex : %@", @(__PRETTY_FUNCTION__), @(__LINE__), control, @(control.cyl_tabBarChildViewControllerIndex), @(control.cyl_tabBarItemVisibleIndex));
    
    // 即使 PlusButton 也添加了點(diǎn)擊事件,點(diǎn)擊 PlusButton 后也會(huì)觸發(fā)該代理方法。
    if ([control cyl_isPlusButton]) {
        UIButton *button = CYLExternPlusButton;
        animationView = button.imageView;
        // 為加號(hào)按鈕添加「縮放動(dòng)畫(huà)」
        [self addScaleAnimationOnView:animationView repeatCount:1];
    } else if ([control isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
        for (UIView *subView in control.subviews) {
            if ([subView isKindOfClass:NSClassFromString(@"UITabBarSwappableImageView")]) {
                animationView = subView;
                // 為其他按鈕添加「旋轉(zhuǎn)動(dòng)畫(huà)」
                [self addRotateAnimationOnView:animationView];
            }
        }
    }
}

/// 縮放動(dòng)畫(huà)
- (void)addScaleAnimationOnView:(UIView *)animationView repeatCount:(float)repeatCount {
    //需要實(shí)現(xiàn)的幀動(dòng)畫(huà),這里根據(jù)需求自定義
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"transform.scale";
     animation.values = @[@1.0,@1.3,@0.9,@1.15,@0.95,@1.02,@1.0];
    animation.duration = 0.5;
    animation.repeatCount = repeatCount;
    animation.calculationMode = kCAAnimationCubic;
    [animationView.layer addAnimation:animation forKey:nil];
}

/// 旋轉(zhuǎn)動(dòng)畫(huà)
- (void)addRotateAnimationOnView:(UIView *)animationView {
   [UIView animateWithDuration:0.32 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
       animationView.layer.transform = CATransform3DMakeRotation(M_PI, 0, 1, 0);
   } completion:nil];
   
   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       [UIView animateWithDuration:0.70 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
           animationView.layer.transform = CATransform3DMakeRotation(2 * M_PI, 0, 1, 0);
       } completion:nil];
   });
}

實(shí)現(xiàn)效果:

帶旋轉(zhuǎn)動(dòng)畫(huà)的 TabBar

第六步:添加 Lottie 動(dòng)畫(huà)

參考:https://github.com/ChenYilong/CYLTabBarController/issues/341

首先,為方便演示,我們從官方 Demo 中獲取 Lottie 動(dòng)畫(huà)的 JSON 文件和相關(guān)資源文件。

Lottie 資源文件

然后,修改配置 tabBar 屬性數(shù)組的方法 - (NSArray *)tabBarItemsAttributes,把 Lottie 文件的 URL 路徑加上:

/*
 * tabBar 屬性數(shù)組,帶 Loggie 動(dòng)畫(huà)
 * 參考:<https://github.com/ChenYilong/CYLTabBarController/issues/341>
 *
 * 與上面的配置相比,需要再多設(shè)置兩個(gè)屬性:
 *   CYLTabBarLottieURL: 傳入 lottie 動(dòng)畫(huà) JSON 文件所在路徑
 *   CYLTabBarLottieSize: LottieView 大小,選填
 *
 */
- (NSArray *)tabBarItemsAttributes {
    NSDictionary *homeTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"首頁(yè)",
        CYLTabBarLottieURL : [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"tab_home_animate" ofType:@"json"]],
        CYLTabBarLottieSize: [NSValue valueWithCGSize:CGSizeMake(33, 33)],
    };
    NSDictionary *myCityTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"同城",
        CYLTabBarLottieURL : [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"tab_search_animate" ofType:@"json"]],
        CYLTabBarLottieSize: [NSValue valueWithCGSize:CGSizeMake(33, 33)],
    };
    NSDictionary *messageTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"消息",
        CYLTabBarLottieURL : [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"tab_message_animate" ofType:@"json"]],
        CYLTabBarLottieSize: [NSValue valueWithCGSize:CGSizeMake(33, 33)],
    };
    NSDictionary *accountTabBarItemsAttributes = @{
        CYLTabBarItemTitle: @"我的",
        CYLTabBarLottieURL : [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"tab_me_animate" ofType:@"json"]],
        CYLTabBarLottieSize: [NSValue valueWithCGSize:CGSizeMake(33, 33)],
    };

    NSArray *tabBarItemsAttributes = @[
        homeTabBarItemsAttributes,
        myCityTabBarItemsAttributes,
        messageTabBarItemsAttributes,
        accountTabBarItemsAttributes
    ];
    return tabBarItemsAttributes;
}
帶 Lottie 動(dòng)畫(huà)的 TabBar 示例

自此,我們通過(guò) CYLTabBarController 框架實(shí)現(xiàn)了一個(gè)仿「閑魚(yú)」TabBar 動(dòng)畫(huà)的應(yīng)用。

Demo:HQLTableViewDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容