最终效果图:
BeyondViewController.m
//// BeyondViewController.m// 20_帅哥no微博//// Created by beyond on 14-8-3.// Copyright (c) 2014年 com.beyond. All rights reserved.// 这个就是主控制器,分为两块,下面是Dock栏,上面是显示不同的子控制器的view,子控制器最好用导航控制器包装一下,这样子控制器就自带了导航条,左按钮,标题,右按钮/* 无法点击,或点击 无响应的原因: userInteractionEnabled = NO; hidden = YES alpha <= 0.01 clearColor ,view的颜色为透明,不可以被点击 */#import "BeyondViewController.h"#import "Dock.h"#import "DockBtn.h"#import "Column.h"// 主控制器下面Dock的高度#define kDockHeight 44@interface BeyondViewController (){ // 从plist中加载 的栏目对象数组 NSMutableArray *_columns; // 主控制器下方的Dock选项栏 Dock *_dock; // 记录当前选中的子控制器,目的是将其view从父控制器的view中移除,为添加新的子控制器的view做准备 UIViewController *_currentChildVC;}@end@implementation BeyondViewController- (BOOL)prefersStatusBarHidden{ return NO; }- (void)viewDidLoad{ [super viewDidLoad]; // 0.从plist加载 栏目数组,遍历数组,根据字典,生成一个一个栏目对象,存入栏目对象数组中 _columns = [NSMutableArray array]; NSBundle *mainBundle = [NSBundle mainBundle]; NSString *fullPath = [mainBundle pathForResource:@"ColumnList.plist" ofType:nil]; NSArray *arr = [NSArray arrayWithContentsOfFile:fullPath]; for (NSDictionary *dict in arr) { Column *column = [Column columnWithDict:dict]; [_columns addObject:column]; } // 1.添加dock到主控制器方的下方 [self addDock]; // 2.一次性创建所有的子控制器,并用导航包装后,添加到当前控制器的childViewControllers [self createAllChildViewControllers]; // 3.默认选中第0个控制器 [self changeChildViewAtIndex:0 andChildVCClassName:@"HomeViewController"]; // 4.一次性设置全局的导航栏上面的颜色主题样式 [self setGlobalNavigationItemColorTheme]; }#pragma mark 添加dock- (void)addDock{ // 1.添加dock到主控制器方的下方 _dock = [[Dock alloc] init]; // 2.监听Dock内部Btn的点击,让控制器成为dock的代理属性,或者,为dock的成员blok赋值 __unsafe_unretained BeyondViewController *beyond = self; _dock.btnClickBlock = ^(DockBtn *btn) { // 调用自定义方法,更改子视图,参数1:索引号,参数2:子控制器的类名 [beyond changeChildViewAtIndex:btn.tag andChildVCClassName:btn.viewControllerClassName]; }; // 3,设置Dock的frame _dock.frame = CGRectMake(0, self.view.frame.size.height - kDockHeight, self.view.frame.size.width, kDockHeight); log(@"_dock frame--%@",NSStringFromCGRect(_dock.frame)); // 4,添加dock到主控制器方的view [self.view addSubview:_dock]; // 2.遍历column对象数组,批量添加dock里面的DockBtn for (Column *column in _columns) { [_dock addDockBtnWithIconName:column.columnImgName title:column.columnName viewControllerClassName:column.columnClassName]; } // 3.设置dock默认选中第0个 [_dock setDockBtnClickedAtIndex:0]; }// 自定义方法,更改子视图,参数1:索引号,参数2:子控制器的类名- (void)changeChildViewAtIndex:(int)index andChildVCClassName:(NSString *)viewControllerClassName{ log(@"点击了 %@",viewControllerClassName); if (self.childViewControllers.count > 0) { // 0,先取出新的子控制器,如果 新的子控制器就是当前的这个控制器,直接返回 好吗? UIViewController *childVC = [self childViewControllers][index]; if (childVC == _currentChildVC) return ; // 1,先移除先前的子控制器的view [_currentChildVC.view removeFromSuperview]; // 2,添加新的子控制器的view到主控制器的view childVC.view.frame = CGRectMake(0, 20, 320, 416); //log(@"self view --%@",NSStringFromCGRect(self.view.frame)); //log(@"childVC view --%@",NSStringFromCGRect(childVC.view.frame)); // 不会重复添加view,因为一旦发现重复添加某个view,就会将它置于最上面,最好是,先移除旧的view,再添加新的view [self.view addSubview:childVC.view ]; // 3,重要,必须更新当前的子控制器,为下次移除做准备 _currentChildVC = childVC; }}#pragma mark 创建所有的子控制器(一共5个,首面,消息,我,广场,更多)- (void)createAllChildViewControllers{ // 1.遍历栏目对象数组,批量创建所有的子控制器,并用导航控制器包装,最后添加到self childViewControllers数组中保存 for (Column *column in _columns) { Class c = NSClassFromString(column.columnClassName); UIViewController *childVC =nil; if ([NSStringFromClass(c) isEqualToString:@"MoreViewController"]) { // 特别注意:在继承了tableView之后,要想再使用group样式,必须在创建的时候指定样式为group,这儿特别指的是moreViewController childVC = [[c alloc]initWithStyle:UITableViewStyleGrouped]; } else { childVC = [[c alloc]init]; } // 设置导航栏的标题 childVC.navigationItem.title = column.columnName; // 重写父类的方法 [self addChildViewController:childVC]; }}#pragma marck - 重写父类的方法// 为了在添加子控制器时,全部包装成一个个导航控制器,所以重写addChildViewController方法- (void)addChildViewController:(UIViewController *)childVC{ UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:childVC]; // 将包装成导航控制器的子控制器添加到主控制器中,这样每一个子控制器就拥有自己的特有的导航条了 [super addChildViewController:nav];}// 4.一次性设置全局的导航栏上面的颜色主题样式- (void)setGlobalNavigationItemColorTheme{ // 1.导航栏 // 1.1.操作navBar相当操作整个应用中的所有导航栏 UINavigationBar *navBar = [UINavigationBar appearance]; // 1.2.设置导航栏UINavigationBar的背景图片(拉伸) [navBar setBackgroundImage:[UIImage imageStretchedWithName:@"navigationbar_background.png"] forBarMetrics:UIBarMetricsDefault]; // 1.3.设置状态栏背景,没有效果??? [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; // 1.4.设置导航栏UINavigationBar的Title文字属性,通过字典 设置 NSMutableDictionary *navigationBarTitleDict = [NSMutableDictionary dictionary]; // 前景色,即文字的颜色 [navigationBarTitleDict setObject:[UIColor darkGrayColor] forKey:NSForegroundColorAttributeName]; // 文字阴影取消,字典中不能放结构体,要用NSValue包装一下 [navigationBarTitleDict setObject:[NSValue valueWithUIOffset:UIOffsetZero] forKey:NSShadowAttributeName]; // 2.导航栏上面的item UIBarButtonItem *barBtnItem =[UIBarButtonItem appearance]; // 2.1.设置背景 // 按钮正常状态时侯的背景 [barBtnItem setBackgroundImage:[UIImage imageNamed:@"navigationbar_button_background.png"] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault]; // 按钮高亮状态时侯的背景 [barBtnItem setBackgroundImage:[UIImage imageNamed:@"navigationbar_button_background_pushed.png"] forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault]; // 按钮未选中状态时侯的背景 [barBtnItem setBackgroundImage:[UIImage imageNamed:@"navigationbar_button_background_disable.png"] forState:UIControlStateDisabled barMetrics:UIBarMetricsDefault]; // 2.2.设置barBtnItem的文字属性 NSMutableDictionary *barItemTitleDict = [NSMutableDictionary dictionary]; // barItemDict的文字颜色 [barItemTitleDict setValue:[UIColor darkGrayColor] forKey:NSForegroundColorAttributeName]; // barItemDict的字体 [barItemTitleDict setValue:[UIFont systemFontOfSize:13] forKey:NSFontAttributeName]; // 2.3.用字典 设置barBtnItem的标题文字属性 [barBtnItem setTitleTextAttributes:barItemTitleDict forState:UIControlStateNormal]; [barBtnItem setTitleTextAttributes:barItemTitleDict forState:UIControlStateHighlighted];}@end
Dock.h
//// Dock.h// 20_帅哥no微博//// Created by beyond on 14-8-3.// Copyright (c) 2014年 com.beyond. All rights reserved.// Dock就是主控制器下面的一条bar,它里面是由一个个按钮DockBtn组成#import <UIKit/UIKit.h>@class DockBtn;@interface Dock : UIView// 添加一个item到Dock(View),参数是图标名,和要显示 的标题 ,以及对应的子控制器的类名- (void)addDockBtnWithIconName:(NSString *)iconName title:(NSString *)title viewControllerClassName:(NSString *)viewControllerClassName;// 当Dock里面的某一个按钮被点击了的时候,调用代码块,处理相应的点击事件@property (copy,nonatomic) void(^btnClickBlock)(DockBtn *);// 自定义方法,通过代码决定哪一个dockBtn被点击了,参数是 Dock栏里面的那个将要被点击的按钮的索引- (void)setDockBtnClickedAtIndex:(int)index;@end
Dock.m
//// Dock.m// 20_帅哥no微博//// Created by beyond on 14-8-3.// Copyright (c) 2014年 com.beyond. All rights reserved.// 这个就是主控制器下面那一栏,Tabbar,也叫Dock,里面有五个按钮,分别是首页,我,消息,广场,更多#import "Dock.h"#import "DockBtn.h"@interface Dock(){ // 当前选中了那个dockBtn DockBtn *_currentDockBtn;}@end@implementation Dock// init方法内部会调用initWithFramne- (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { // 固有固定属性,设置Dock背景颜色(分类方法,使用imageName就可进行平铺) self.backgroundColor = [UIColor colorWithPatternImageNamed:@"tabbar_background.png"]; } return self;}// 给外部提供一个接口,添加一个DockBtn(按钮)到Dock(View),参数是图标名,和要显示 的标题,以及对应的子控制器的类名- (void)addDockBtnWithIconName:(NSString *)iconName title:(NSString *)title viewControllerClassName:(NSString *)viewControllerClassName{ // 1.创建dock里面的按钮,并添加到dock里面 DockBtn *dockBtn = [DockBtn buttonWithType:UIButtonTypeCustom]; [self addSubview:dockBtn]; // 2.设置dockBtn正常状态下显示 的文字 [dockBtn setTitle:title forState:UIControlStateNormal]; // 3.分类方法,设置按钮正常和选中状态下的图片,返回图片尺寸 [dockBtn setBtnImgForNormalAndSelectedWithName:iconName]; // 4.设置dockBtn对应点击后,要实例化的子控制器的类名 [dockBtn setViewControllerClassName:viewControllerClassName]; // 5.监听点击,只要按下就响应,(事件先传递给Dock的方法,Dock的方法中再通过调用属性block代码块,从而调用到主控制器里面的代码,原因是:在主控制器里面实例化的dock,在Dock里面才实例化的dockBtn,因此,主控制器并不知道dockItem的存在) [dockBtn addTarget:self action:@selector(dockBtnClick:) forControlEvents:UIControlEventTouchDown]; // 6.遍历设置Dock里面所有按钮的frame (使之平均分布) [self setDockBtnFrames];}// 遍历设置Dock里面所有按钮的frame (使之平均分布)- (void)setDockBtnFrames{ // 1,获取dock里面所有的按钮个数 int dockBtnNum = self.subviews.count; // 2,根据dock中,当前当前有多少个DockBtn,计算出每个dockBtn的宽度(self是dock,320*44) CGFloat dockBtnWidth = self.frame.size.width / dockBtnNum; CGFloat dockBtnHeight = self.frame.size.height; for (int i = 0; i < dockBtnNum; i++) { // 1.逐个取出子控件 DockBtn *btn = self.subviews[i]; // 2.根据索引 计算它的x btn.frame = CGRectMake(i * dockBtnWidth, 0, dockBtnWidth, dockBtnHeight); // 3.初始化的时候,将第0个btn(即首页)选中 if (i == 0) { btn.selected = YES; // 最重要的是,将选中的,置为当前的按钮,用成员变量记住,当点击dock上button的时候,先将current置为未选中,然后就被点击的按钮选中,最后最重要的是,将被点击的按钮重新置为当前 的按钮,用成员变量记住 _currentDockBtn = btn; } // 4.因为点击dock里面的按钮的时候,要知道点击了哪一个按钮,所以给每个按钮绑定一个tag,作为它的索引 btn.tag = i; }}// 最重要的是,当点击dock上button的时候,先将current置为未选中,然后就被点击的按钮选中,最后最重要的是,将被点击的按钮重新置为当前 的按钮,用成员变量记住- (void)dockBtnClick:(DockBtn *)btn{ // 1.让当前的btn取消选中 _currentDockBtn.selected = NO; // 2.让新的btn选中 btn.selected = YES; // 3.最后,让新的btn变为当前选中btn _currentDockBtn = btn; // 4.调用block,即主控制中传递过来的代码块,目的是处理点击之后的实例化对应的子控制器 if (_btnClickBlock) { // 将参数 DockBtn传递过去,给主控制器,它里面成员变量记住了它对应的控制器的类名 _btnClickBlock(btn); }}// 自定义方法,通过代码决定哪一个dockBtn被点击了,参数是 Dock栏里面的那个将要被点击的按钮的索引- (void)setDockBtnClickedAtIndex:(int)index{ // 1.robust判断 if (index < 0 || index >= self.subviews.count) return; // 2.通过索引 拿到对应的DockBtn viewWithTag也行 DockBtn *btn = self.subviews[index]; // 3.手动调用下面方法,相当于用户用手点击了dock里面对应的按钮 [self dockBtnClick:btn];}@end
DockBtn.h
//// DockBtn.h// 20_帅哥no微博//// Created by beyond on 14-8-4.// Copyright (c) 2014年 com.beyond. All rights reserved.// 一个DockBtn代表Dock上面的一个按钮,它有个成员是对应子控制器的类名,比如Home按钮,成员属性的值就是叫:HomeViewController#import <UIKit/UIKit.h>@interface DockBtn : UIButton// 每个dockBtn中,用一个成员记住 它对应的控制器的类名@property (nonatomic,copy) NSString *viewControllerClassName;@end
DockBtn.m
//// DockBtn.m// 20_帅哥no微博//// Created by beyond on 14-8-4.// Copyright (c) 2014年 com.beyond. All rights reserved.// 一个DockBtn代表Dock上面的一个按钮,它有个成员是对应子控制器的类名,比如Home按钮,成员属性的值就是叫:HomeViewController#import "DockBtn.h"// 按钮的内容的总宽度#define kBtnContentWidth contentRect.size.width// 按钮的内容的总高度#define kBtnContentHeight contentRect.size.height// 按钮里的图片的所占的高度比例#define kImageHeightRatio 0.6// 按钮里的文本标签的所占的高度比例#define kLabelHeightRatio (1- kImageHeightRatio)@implementation DockBtn// 一些默认的通用的属性一定要写在构造方法里面- (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { // 1.设置按钮文字属性 (局中,字体大小) self.titleLabel.textAlignment = NSTextAlignmentCenter; self.titleLabel.font = [UIFont systemFontOfSize:12]; // 2.设置按钮图片属性 (放大模式,取消按钮默认的点击高亮时的变色) self.imageView.contentMode = UIViewContentModeScaleAspectFit; // 取消按钮默认的点击高亮时的变色(image is drawn darker when highlighted or pressed) self.adjustsImageWhenHighlighted = NO; // 3.分类方法,设置按钮选中时的背景 [self setBgImgForSelected:@"tabbar_slider.png"]; } return self;}#pragma mark 重写父类的方法(覆盖父类在高亮时所作的行为)- (void)setHighlighted:(BOOL)highlighted{ // 因为 这里只需用按钮的选中和默认状态时的图片,所以要取消高亮状态的一些默认变色行为 // 这里什么也不写,即取消,按钮本身 在高亮的时候执行的那些行为 }#pragma mark 返回是按钮内部UIImageView的边框(按钮中的图片在上方,居中)- (CGRect)imageRectForContentRect:(CGRect)contentRect{ // 要居中,最快办法就是让按钮中的图片宽度和按钮一样宽 return CGRectMake(0, 0, kBtnContentWidth, kBtnContentHeight * kImageHeightRatio);}#pragma mark 返回是按钮内部UILabel的边框(按钮中的文字在下方,居中)- (CGRect)titleRectForContentRect:(CGRect)contentRect{ // 要居中,最快办法就是让按钮中的Label宽度和按钮一样宽 // 文字的y位于图片的下边线的上方5个单位距离,即距离图片上方5 CGFloat labelY = kBtnContentHeight * kImageHeightRatio - 5; // 文字的高度是占按钮余下的所有高度 CGFloat labelHeight = kBtnContentHeight - labelY; return CGRectMake(0, labelY, kBtnContentWidth, labelHeight);}@end
模型Column.h
//// Column.h// 20_帅哥no微博//// Created by beyond on 14-8-4.// Copyright (c) 2014年 com.beyond. All rights reserved.// 1个Column模型对应Dock上面的一个按钮,类别#import <Foundation/Foundation.h>// 数据模型 代表一个栏目@interface Column : NSObject// 栏目名称@property (nonatomic,copy)NSString *columnName;// 栏目图片名称@property (nonatomic,copy)NSString *columnImgName;// 栏目对应的控制器的类名@property (nonatomic,copy)NSString *columnClassName;// UI控件用weak,字符串用copy,其他对象用strong// 提供一个类方法,即构造函数,返回封装好数据的对象(返回id亦可)+ (Column *)columnNamed:(NSString *)columnName imgName:(NSString*)columnImgName className:(NSString *)columnClassName;// 类方法,字典 转 对象 类似javaBean一次性填充+ (Column *)columnWithDict:(NSDictionary *)dict;// 对象方法,设置对象的属性后,返回对象- (Column *)initWithDict:(NSDictionary *)dict;@end
模型Column.m
//// Column.m// 20_帅哥no微博//// Created by beyond on 14-8-4.// Copyright (c) 2014年 com.beyond. All rights reserved.//#import "Column.h"@implementation Column// 返回一个包含了 栏目对应控制器名字的 对象实例+ (Column *)columnNamed:(NSString *)columnName imgName:(NSString *)columnImgName className:(NSString *)columnClassName{ // 为了兼容子类 使用self Column *column = [[self alloc]init]; column.columnName = columnName; column.columnImgName = columnImgName; column.columnClassName = columnClassName; return column;}// 类方法,字典 转 对象 类似javaBean一次性填充+ (Column *)columnWithDict:(NSDictionary *)dict{ // 只是调用对象的initWithDict方法,之所以用self是为了对子类进行兼容 return [[self alloc]initWithDict:dict];}// 对象方法,设置对象的属性后,返回对象- (Column *)initWithDict:(NSDictionary *)dict{ // 必须先调用父类NSObject的init方法 if (self = [super init]) { // 设置对象自己的属性 [self setValuesForKeysWithDictionary:dict]; } // 返回填充好的对象 return self;}@end
Dock里面的五个栏目按钮的数据来源ColumnList.plist