iOS标签栏动态化

一、TabBar架构

RDVTabBarController开源地址

文件 说明
defauftTabBar.json 默认标签栏数据
MagicDownloader 标签栏item下载
MainTabBarManager 管理标签栏
MainTabBarModel 标签栏item模型
RDVTabBarController 项目中用到的根视图控制器
1.1

二、说明

defauftTabBar.json文件

若未成功获取到服务器返回的TabBar配置数据,则默认使用本地defauftTabBar.json文件中的数据。

字段 配置说明
normal String 未点击状态url
focus String 已点击状态url
page String 需要显示的控制器
params String 自定义参数
track String 埋点、标识
(startTime) String 定时显示控制器
(endTime) String 定时隐藏控制器
[
  {
  "normal":"http://cdn1.showjoy.com/images/a3/a301101989e84950adf7a4c5b2a4f6c5.png",
  "focus":"http://cdn1.showjoy.com/images/ae/ae61cacb829d41268811b3c2d7cf9b86.png",
  "page":"HomeMainViewController",
  "params":"",
  "track":"tab_home"
  },
  {
  "normal":"http://cdn1.showjoy.com/images/d3/d3fed5dd8db3406bbd48e3931874af64.png",
  "focus":"http://cdn1.showjoy.com/images/7f/7f74216350c54eabaa8d2777b71294b7.png",
  "page":"ClassifyViewController",
  "params":"",
  "track":"tab_category"
  },
  {
  "normal":"http://cdn1.showjoy.com/images/5d/5d6feb9284d046619411c4fadd81720c.png",
  "focus":"http://cdn1.showjoy.com/images/50/50e00a05ac184e1b935bf63f37d8aee6.png",
  "page":"PersonViewController",
  "params":"",
  "track":"tab_user"
  }
]
MainTabBarModel文件

TabBar数据对应的Model模型

MainTabBarModel.h

#import <Foundation/Foundation.h>
@interface MainTabBarModel : NSObject
@property (nonatomic, copy) NSString  *focus;
@property (nonatomic, copy) NSString  *normal;
@property (nonatomic, copy) NSString  *page;
@property (nonatomic, copy) NSString  *params;
@property (nonatomic, copy) NSString  *track;
@property (nonatomic, copy) NSString  *startTime;
@property (nonatomic, copy) NSString  *endTime;
@end

MainTabBarModel.m

#import "MainTabBarModel.h"
@implementation MainTabBarModel
@end
MagicDownloader文件

GCD多线程处理,任务完成通过completion回调
1、根据TabBar数据解析的Model下载icon文件。
2、校验icon是否已经下载:

  • url和magic_local_url一致则根据magic_local_filePath获取本地icon文件。
  • url和magic_local_url不一致或magic_local_filePath获取本地icon文件失败,则网络下载icon文件。

3、icon文件保存到/Documents/路径下,NSUserDefaults持久化保存形式如下:

字段 说明
magic_local_filePath track值 + _normaltrack值 + _focus icon文件对应的本地路径
magic_local_url icon文件url icon文件对应的网络url

注意:
因为page字段配置的Controller名称不具备唯一性,所以使用具有唯一性的字段track + ...的形式来作为已经下载的icon名称。
replaceHttpToHttps方法作用是将url字符串的http协议转为https协议。

Documents中保存的icon

MagicDownloader.h

#import <Foundation/Foundation.h>
#import "MainTabBarModel.h"

#define magic_tabbar_normal(key)       [NSString stringWithFormat:@"%@_normal", key]
#define magic_tabbar_focus(key)        [NSString stringWithFormat:@"%@_focus", key]

typedef void (^MagicDownloaderCompletion)(BOOL success);

@interface MagicDownloader : NSObject
+ (MagicDownloader *)shareManager;
- (void)startDownloadImageWithAllTabBarModels:(NSArray *)allTabBarModels Completion:(MagicDownloaderCompletion)completion;
- (UIImage *)sandboxOptionGetImageWithFileName:(NSString *)fileName;
@end

MagicDownloader.m

#import "MagicDownloader.h"

#define magic_local_url           @"local_url"
#define magic_local_filePath      @"local_filePath"

@implementation MagicDownloader

+ (MagicDownloader *)shareManager{
 
    static MagicDownloader *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [MagicDownloader new];
    });
    return manager;
    
}

#pragma mark - 多线程处理
- (void)startDownloadImageWithAllTabBarModels:(NSArray *)allTabBarModels Completion:(MagicDownloaderCompletion)completion{
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    dispatch_group_t downloadImage = dispatch_group_create();
    
    [allTabBarModels enumerateObjectsUsingBlock:^(MainTabBarModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
        //NSLog(@"网络下载并保存到沙盒...");
        dispatch_group_async(downloadImage, queue, ^{
            
            NSString *key_normal = magic_tabbar_normal(model.track);
            NSString *key_focus = magic_tabbar_focus(model.track);
            [self checkSandBoxHaveImageWithFileName:key_normal url:model.normal];
            [self checkSandBoxHaveImageWithFileName:key_focus url:model.focus];
            
        });
    }];
    
    dispatch_group_notify(downloadImage, dispatch_get_main_queue(), ^{
        if (completion) {
            completion(YES);
        }
    });
    
}

#pragma mark - 检查图片是否下载
- (void)checkSandBoxHaveImageWithFileName:(NSString *)fileName url:(NSString *)url{
    
    UIImage *resultImage = nil;
    NSDictionary *local_dic = [self databaseCacheGetWithKey:fileName];
    NSString *local_url = [local_dic objectForKey:magic_local_url];
    NSString *local_filePath = [local_dic objectForKey:magic_local_filePath];

    //已下载 - 从本地获取
    if ([url isEqualToString:local_url]) {
        resultImage = [UIImage imageWithContentsOfFile:local_filePath];
    }
    if (resultImage == nil) {
        //未下载 — 网络下载
        resultImage = [self networkDownloadImageWithImageUrl:url];
    }
    //存储 - 图片
    if (resultImage) {
        [self databaseCacheSaveWithKey:fileName image:resultImage url:url];
    }
    
}

#pragma mark - 下载网络图片
- (UIImage *)networkDownloadImageWithImageUrl:(NSString *)imageUrl{
    
    if (!imageUrl.length) {
        return nil;
    }
    NSURL *url = [NSURL URLWithString:[imageUrl replaceHttpToHttps]];
    NSData *responseData = [NSData dataWithContentsOfURL:url];
    UIImage *resultImage = [UIImage imageWithData:responseData];
    return resultImage;
    
}
#pragma mark - 沙盒操作
/**
 存储图片
 */
- (NSString *)sandboxOptionSaveImage:(UIImage *)image fileName:(NSString *)fileName{
    
    NSString *homePath = NSHomeDirectory();
    NSString *imagePathName = [NSString stringWithFormat:@"/Documents/%@.png", fileName];
    NSString *filePath = [homePath stringByAppendingString:imagePathName];
    [UIImagePNGRepresentation(image) writeToFile:filePath atomically:YES];
    return [NSString stringWithFormat:@"%@", filePath];
    
}

/**
 获取图片
 */
- (UIImage *)sandboxOptionGetImageWithFileName:(NSString *)fileName{

    NSString *homePath = NSHomeDirectory();
    NSString *filePath = [homePath stringByAppendingString:[NSString stringWithFormat:@"/Documents/%@", fileName]];
    UIImage *image = [UIImage imageWithContentsOfFile:filePath];
    return image;
    
}

#pragma mark - 持久化存储操作
/**
 存储图片
 */
- (void)databaseCacheSaveWithKey:(NSString *)key image:(UIImage *)image url:(NSString *)url{
    
    NSString *filePath = [self sandboxOptionSaveImage:image fileName:key];
    NSDictionary *dic = @{magic_local_filePath : [NSString stringWithFormat:@"%@", filePath],
                          magic_local_url : [NSString stringWithFormat:@"%@", url]};
    [[NSUserDefaults standardUserDefaults] setObject:dic forKey:key];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
}

/**
 获取显示图片
 */
- (NSDictionary *)databaseCacheGetWithKey:(NSString *)key{
    
    NSDictionary *result = [[NSUserDefaults standardUserDefaults] objectForKey:key];
    return result;
    
}
MainTabBarManager文件

1、根据网络TabBar配置的json数据转为NSArray传递到dataSource中,生成一个根视图控制器,其中包含了需要显示的控制器。
2、page字段,用于生成对应的Controller。
3、params字段,用于WebViewController跳转URL。
4、startTime字段,用于定时显示Controller。
5、endTime字段,用于定时隐藏Controller。
6、NSUserDefaults持久化保存每次生成的TabBar数据,key为tabbar_lastDataSource,用于校验TabBar是否发生变化。

注意:
WeexViewController和ActivityWebViewController为业务中需要显示的控制器,这里不再详细说明。

MainTabBarManager.h

#import <Foundation/Foundation.h>
#import "RDVTabBarController.h"
@interface MainTabBarManager : NSObject
+ (MainTabBarManager *)shareManager;
// 加载根视图控制器
- (RDVTabBarController *)loadingMainTabBarControllerWithDataSource:(NSArray *)dataSource; 
// 获取上次TabBar数据
- (NSArray *)localGetLastDataSource;
// 清除上次TabBar数据
- (void)clearLocalGetLastDataSource;
@end

MainTabBarManager.m

#import "MainTabBarManager.h"
#import "RDVTabBarItem.h"
#import "MainTabBarModel.h"
#import "MagicDownloader.h"

//ViewControllers
#import "WeexViewController.h"
#import "ActivityWebViewController.h"

@interface MainTabBarManager ()
@property (nonatomic, strong)RDVTabBarController *rootTabBarController;
@end

@implementation MainTabBarManager

+ (MainTabBarManager *)shareManager{
    
    static MainTabBarManager *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [MainTabBarManager new];
    });
    return manager;
    
}


/**
 标签栏
 */
- (RDVTabBarController *)loadingMainTabBarControllerWithDataSource:(NSArray *)dataSource{
    
    [self localSaveLastDataSource:dataSource];
    self.rootTabBarController = nil;
    self.rootTabBarController = [RDVTabBarController new];
    self.rootTabBarController.tabBar.backgroundView.backgroundColor = [[OnlineManager shareOnlineManager] useOnlineTabbarBackGroundColor];
    self.rootTabBarController.viewControllers = [self buildAllViewControllersWithDataSource:dataSource];
    return self.rootTabBarController;
    
}

/**
 构建ViewController
 */
- (NSArray *)buildAllViewControllersWithDataSource:(NSArray *)dataSource{
    
    
    NSMutableArray *results = [NSMutableArray array];
    NSMutableArray *allModels = [NSMutableArray array];
    [dataSource enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if ([obj isKindOfClass:[NSDictionary class]]) {
            MainTabBarModel *model = [MainTabBarModel objectWithKeyValues:obj];
            UIViewController *controller = nil;
            
            // 时间控制
            if ([self canAddRootViewControllerWithStartTime:model.startTime EndTime:model.endTime]) {
                if (model.params.length) {
                    // Weex 、WebView
                    controller = [self loadWeexOrWebViewControllerWithParams:model.params];
                }else{
                    // 普通
                    controller = [self loadNormalControllerWithPage:model.page];
                }
                if (controller) {
                    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
                    [results addObject:navigationController];
                    [allModels addObject:model];
                }
            }
            
        }
        
    }];
    
    //多线程加载
    [[MagicDownloader shareManager] startDownloadImageWithAllTabBarModels:allModels Completion:^(BOOL success) {
        [self reloadTabBarItemsWithAllModels:allModels];
    }];
    
    return results;
    
}


/**
 普通Controller
 */
- (UIViewController *)loadNormalControllerWithPage:(NSString *)page{
    
    Class cls = NSClassFromString(page);
    return (UIViewController *)[[cls alloc] init];
    
}

/**
 Weex或WebView
 */
- (UIViewController *)loadWeexOrWebViewControllerWithParams:(NSString *)params{
    
    CGRect frame = CGRectMake(0, 0, Screen_width, Screen_height - TabbarHeight - NavagationBarHeight);
    NSMutableDictionary *weexDic = [NSMutableDictionary dictionaryWithDictionary:[[JumpAgreement sharedJumpAgreement] getDicPushToWeexController:params]];
    if (kValidDic(weexDic) && [[weexDic objectForKey:@"url"] length] > 0) {
        //Weex
        [weexDic setValue:@"1" forKey:@"Main"];
        return [[WeexViewController alloc] initWithDict:weexDic withFrame:frame];
    }
    
    //WebView
    NSDictionary * mdicValue = [NSDictionary dictionaryWithObjectsAndKeys:params,@"url",@"1",@"Main", nil];
    return [[ActivityWebViewController alloc] initWithDict:mdicValue withFrame:frame];

}


/**
 刷新TabBarItem
 */
- (void)reloadTabBarItemsWithAllModels:(NSArray *)allModels{
    
    NSMutableArray *array = [NSMutableArray array];
    for (NSInteger i = 0; i < allModels.count; i++) {
        MainTabBarModel *model = [allModels objectAtIndex:i];
        RDVTabBarItem *item = [RDVTabBarItem new];
        item.title = nil;
        UIImage *normalImage = [[MagicDownloader shareManager] sandboxOptionGetImageWithFileName:magic_tabbar_normal(model.track)];
        UIImage *focusImage = [[MagicDownloader shareManager] sandboxOptionGetImageWithFileName:magic_tabbar_focus(model.track)];
        [item setFinishedSelectedImage:focusImage withFinishedUnselectedImage:normalImage];
        [array addObject:item];
        
    }
    self.rootTabBarController.tabBar.items = array;

}

#pragma mark - 持久化存储
- (void)localSaveLastDataSource:(NSArray *)dataSource{
    [[NSUserDefaults standardUserDefaults] setObject:dataSource forKey:@"tabbar_lastDataSource"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

/**
 上次数据源
 */
- (NSArray *)localGetLastDataSource{
    return [[NSUserDefaults standardUserDefaults] objectForKey:@"tabbar_lastDataSource"];
}

- (void)clearLocalGetLastDataSource{
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"tabbar_lastDataSource"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

#pragma mark - 时间控制
- (BOOL)canAddRootViewControllerWithStartTime:(NSString *)startTime EndTime:(NSString *)endTime{
    
    NSDate *startDate = [NSDate dateWithString:startTime formatString:@"yyyy-MM-dd HH:mm:ss"];
    NSDate *endDate = [NSDate  dateWithString:endTime formatString:@"yyyy-MM-dd HH:mm:ss"];
    NSDate *nowDate = [NSDate date];
    
    if (startTime.length <= 0 && endTime.length <= 0) {
        return YES;
    }
    
    if ([nowDate isLaterThanOrEqualTo:startDate] && [nowDate isEarlierThanOrEqualTo:endDate]) {
        return YES;
    }
    
    if ([nowDate isEarlierThan:startDate] && startTime.length) {
        return NO;
    }
    if ([nowDate isLaterThan:endDate] && endTime.length) {
        return NO;
    }
    return NO;
    
}
@end
RDVTabBarController文件

仅根据UI需求修改了源代码的item大小和TabBar的颜色,这里就不再过多说明。

AppDelegate文件

在需要校验刷新刷新根视图控制器的地方调用reloadRootTabbarController方法

注意:
[[OnLineParameter getOnLineParameter:@"homeBar"] toArrayOrDictionary] 方法用于获取网络TagBar数据转为NSArray

/**
 刷新根视图控制器
 */
- (void)reloadRootTabbarController{
    
    NSArray *homeBarArray =  [[OnLineParameter getOnLineParameter:@"homeBar"] toArrayOrDictionary];
    NSArray *lastHomeBarArray = [[MainTabBarManager shareManager] localGetLastDataSource];
    
    if (homeBarArray.count <= 0 || [APPManager getIsCheck]) {
        //默认数据
        NSString *jsonString = [NSString stringWithContentsOfFile:Magic_bundle(@"defauftTabBar", @"json") encoding:NSUTF8StringEncoding error:nil];
        NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
        homeBarArray = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
    }
    
    if (![homeBarArray isEqualToArray:lastHomeBarArray]) {
        //网络数据
        self.viewController = [[MainTabBarManager shareManager] loadingMainTabBarControllerWithDataSource:homeBarArray];
        self.window.rootViewController = self.viewController;
        return;
    }

    if (self.viewController == nil) {
        //防止空视图
        self.viewController = [[MainTabBarManager shareManager] loadingMainTabBarControllerWithDataSource:homeBarArray];
        self.window.rootViewController = self.viewController;
        return;
    }
    
}

三、使用

动态显示标签栏,通过后台网络配置TabBar数据实现,示例如下:

[
    {
        "normal":"http://cdn1.showjoy.com/images/a3/a301101989e84950adf7a4c5b2a4f6c5.png",
        "focus":"http://cdn1.showjoy.com/images/ae/ae61cacb829d41268811b3c2d7cf9b86.png",
        "page":"HomeMainViewController",
        "params":"",
        "track":"tab_home"
    },
    {
        "normal":"http://cdn1.showjoy.com/images/d3/d3fed5dd8db3406bbd48e3931874af64.png",
        "focus":"http://cdn1.showjoy.com/images/7f/7f74216350c54eabaa8d2777b71294b7.png",
        "page":"ClassifyViewController",
        "params":"",
        "track":"tab_category"
    },
   {
        "normal":"https://cdn1.showjoy.com/images/ee/ee993cdac0c540a0a3687853623c7d07.png",
        "focus":"https://cdn1.showjoy.com/images/ee/ee993cdac0c540a0a3687853623c7d07.png",
        "page": "MainActivityController",
        "startTime":"2017-07-09 10:00:00",
        "endTime":"2017-07-14 23:59:59",
        "params":"https://shop.m.showjoy.com/activity/shop/22.html",
        "track": "tab_activity"
    },
    {
        "normal":"http://cdn1.showjoy.com/images/0c/0c252712186a46d2a06ac2acd95ecdc6.png",
        "focus":"http://cdn1.showjoy.com/images/12/12792c6085f04136829763610d31ba5f.png",
        "page":"MainActivityController",
        "params":"http://shop.m.showjoy.com/shop/activityExposure/hotHomeWeex.html",
        "track":"tab_hotHome"
    },
    {
        "normal":"http://cdn1.showjoy.com/images/5d/5d6feb9284d046619411c4fadd81720c.png",
        "focus":"http://cdn1.showjoy.com/images/50/50e00a05ac184e1b935bf63f37d8aee6.png",
        "page":"PersonViewController",
        "params":"",
        "track":"tab_user"
    }
]
Demo

Demo整理中。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容