作為系列第一篇文章,就先從最簡(jiǎn)單的底部的這個(gè)Dock欄開(kāi)始墙贱。
其實(shí)說(shuō)是最簡(jiǎn)單的,也是應(yīng)用最廣泛的褂萧。我們應(yīng)該看過(guò)很多APP的導(dǎo)航界面的樣式稻薇∩┒常基本上就以下兩種:
-
系統(tǒng)原生的UITabBar始終在頁(yè)面底部,并不跟隨界面跳轉(zhuǎn)而隱藏和顯示系統(tǒng)原生
-
網(wǎng)易新聞?lì)愃频牡撞縏abBar只在每個(gè)欄目的首頁(yè)顯示塞椎,在第二級(jí)或第三級(jí)頁(yè)面隱藏(ps:請(qǐng)自動(dòng)忽略新聞內(nèi)容)
網(wǎng)易新聞
那么首先桨仿,既然是自定義控件,就要盡量做到低耦合案狠,高內(nèi)聚服傍。也就是說(shuō)外部不用知道內(nèi)部的具體實(shí)現(xiàn)原理钱雷,只用公開(kāi)出一個(gè)接口供使用方調(diào)用即可。
在這里吹零,外部告訴我要添加一個(gè)新的item罩抗,同時(shí)告訴我這個(gè)item的普通圖片、選中圖片以及顯示文字灿椅。Dock內(nèi)部會(huì)根據(jù)當(dāng)前添加的item數(shù)量規(guī)范的調(diào)整顯示每一個(gè)item的位置及狀態(tài)套蒂。
在Dock.h文件中我有一個(gè)這個(gè)方法:
- (void)addItemWithIcon:(NSString *)icon selectedIcon:(NSString *)selectedIcon title:(NSString *)title;
內(nèi)部實(shí)現(xiàn)為:
- (void)addItemWithIcon:(NSString *)icon selectedIcon:(NSString *)selectedIcon title:(NSString *)title {
DockItem *dockItem = [[DockItem alloc]init];
[dockItem setImage:[UIImage imageNamed:icon] forState:UIControlStateNormal];
[dockItem setImage:[UIImage imageNamed:selectedIcon] forState:UIControlStateSelected];
[dockItem setTitle:title forState:UIControlStateNormal];
// 這里你也可以把正常狀態(tài)和選中狀態(tài)下的字體顏色開(kāi)放出去在外部設(shè)置
[dockItem setTitleColor:[UIColor colorWithRed:137/255.0f green:137/255.0f blue:137/255.0f alpha:1] forState:UIControlStateNormal];
[dockItem setTitleColor:[UIColor colorWithRed:223/255.0f green:41/255.0f blue:43/255.0f alpha:1] forState:UIControlStateSelected];
[dockItem addTarget:self action:@selector(itemClick:) forControlEvents:UIControlEventTouchDown];
[self addSubview:dockItem];
int count = (int)self.subviews.count;
// Dock默認(rèn)顯示第一項(xiàng)
if (count == 1) {
[self itemClick:dockItem];
}
CGFloat width = self.frame.size.width / count;
CGFloat height = self.frame.size.height;
for (int i = 0; i < count; i++) {
DockItem *item = self.subviews[i];
item.tag = i;
item.frame = CGRectMake(width * i, 0, width, height)
}
}
在每一次添加新的item的同時(shí),其內(nèi)部自動(dòng)的重新去設(shè)置每個(gè)item的位置以達(dá)到友好的顯示茫蛹。
其次操刀,我們知道每一個(gè)item實(shí)是上是一個(gè)UIButton,所以要做到這個(gè)Button的顯示跟系統(tǒng)的不一樣麻惶,我重寫(xiě)了UIButton的兩個(gè)方法以達(dá)到圖片與文字上下顯示的效果,給定一個(gè)圖片與文字的高度的比例信夫,我這里設(shè)置的kImageRatio = 0.7窃蹋,代碼如下:
#pragma mark 調(diào)整內(nèi)部ImageView的frame
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
CGFloat imageX = 0;
CGFloat imageY = 0;
CGFloat imageWidth = self.frame.size.width;
CGFloat imageHeight = self.frame.size.height * kImageRatio;
return CGRectMake(imageX, imageY, imageWidth, imageHeight);
}
#pragma mark 調(diào)整內(nèi)部UILable的frame
- (CGRect)titleRectForContentRect:(CGRect)contentRect
{
CGFloat titleWidth = self.frame.size.width;
CGFloat titleHeight = self.frame.size.height * (1 - kImageRatio);
CGFloat titleX = 0;
CGFloat titleY = self.imageView.frame.size.height - 3;
return CGRectMake(titleX, titleY, titleWidth, titleHeight);
}
至此,比如說(shuō)我現(xiàn)在要給Dock欄添加一個(gè)新聞item静稻,并把這個(gè)item需要的兩張圖片傳進(jìn)去警没,那么我只需要執(zhí)行[self.dock addItemWithIcon:@"tabbar_icon_news_normal" selectedIcon:@"tabbar_icon_news_highlight" title:@"新聞"];
就可以了。
接著我們就來(lái)介紹如何做到讓底部的這個(gè)Dock欄隨著控制器的切換而做到自動(dòng)的跟隨每一個(gè)需要它跟隨的控制器振湾。
在日常的開(kāi)發(fā)中杀迹,相信大家基本上每個(gè)人都做過(guò)頂部標(biāo)題下部菜單類似的APP。并且我相信更多的是第二種的顯示方式押搪,細(xì)心的人會(huì)發(fā)現(xiàn)這個(gè)Dock欄始終都會(huì)跟隨每個(gè)欄目的第一個(gè)控制器树酪,那么我們是不是可以這樣考慮,假設(shè)說(shuō)每一個(gè)欄目都是一個(gè)導(dǎo)航控制器大州,那么這個(gè)導(dǎo)航控制器下可能會(huì)push出N多個(gè)子控制器续语,但是這個(gè)導(dǎo)航控制器有且只會(huì)有一個(gè)根(root)控制器,也就是說(shuō)我們的Dock欄只需要跟隨這個(gè)導(dǎo)航控制器的根控制器即可厦画。SO...在這里邊其實(shí)要處理的無(wú)非就是底部Dock這個(gè)View疮茄。
那么先說(shuō)一下我的思路:
- 整個(gè)APP一般可能會(huì)有3-5個(gè)一級(jí)的界面,那么先搞一個(gè)MainController來(lái)管理這么多子頁(yè)面
- 一般情況下每個(gè)一級(jí)的界面都會(huì)有2-3個(gè)子頁(yè)面根暑,可見(jiàn)每個(gè)子頁(yè)面都是有一個(gè)導(dǎo)航控制器包著的頁(yè)面
- 我們要實(shí)現(xiàn)的功能就是在每個(gè)導(dǎo)航控制器的根控制器顯示在最前的時(shí)候力试,顯示底部Dock欄,那么如果當(dāng)前顯示在最前的控制器不是導(dǎo)航控制器的根控制器時(shí)排嫌,就要隱藏Dock欄
- 這樣就很容易想到UINavigationController的代理方法畸裳,我們讓MainController成為它的每一個(gè)子頁(yè)面的導(dǎo)航控制器的代理,然后統(tǒng)一在代理方法里去做處理
我先在MainController里添加了五個(gè)子控制器
#pragma mark - Private Methods
- (void)addChildViewControllers {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:FirstViewController.new];
nav.delegate = self;
[self addChildViewController:nav];
UIViewController *vc = [[UIViewController alloc] init];
nav = [[UINavigationController alloc] initWithRootViewController:vc];
vc.view.backgroundColor = [UIColor redColor];
nav.delegate = self;
[self addChildViewController:nav];
vc = [[UIViewController alloc] init];
nav = [[UINavigationController alloc] initWithRootViewController:vc];
vc.view.backgroundColor = [UIColor orangeColor];
nav.delegate = self;
[self addChildViewController:nav];
vc = [[UIViewController alloc] init];
nav = [[UINavigationController alloc] initWithRootViewController:vc];
vc.view.backgroundColor = [UIColor blueColor];
nav.delegate = self;
[self addChildViewController:nav];
vc = [[UIViewController alloc] init];
nav = [[UINavigationController alloc] initWithRootViewController:vc];
vc.view.backgroundColor = [UIColor grayColor];
nav.delegate = self;
[self addChildViewController:nav];
}
既然是MainController來(lái)管理所有的子欄目淳地,那么很顯然Dock應(yīng)該屬于MainController的View的子view躯畴。也就是說(shuō)實(shí)事上在MainController上顯示的每一個(gè)子導(dǎo)航控制器的frame的size的height并不是填充了整個(gè)屏幕民鼓。
那么既然Dock欄并不屬于某一個(gè)子欄目,我們要怎樣使它隨著任何一個(gè)導(dǎo)航控制器的根控制器來(lái)回移動(dòng)呢蓬抄,一個(gè)處理方法就是:當(dāng)我們?cè)赨INavigationController的代理方法中來(lái)做處理丰嘉。因?yàn)楫?dāng)前主控制器(Maincontroller)是所有子欄目導(dǎo)航控制器的代理,這樣無(wú)論哪一個(gè)欄目中的控制器被點(diǎn)擊嚷缭,MainController都能及時(shí)的做出處理饮亏。
既然我們已經(jīng)知道當(dāng)前頁(yè)面中的邏輯關(guān)系,那么接下來(lái)要做的就是監(jiān)聽(tīng)每一次將要push或pop新控制器時(shí)阅爽,判斷當(dāng)前控制器是不是根控制器路幸。
假如現(xiàn)在要展示一個(gè)新的控制器,如果它不是根控制器那么就要拉長(zhǎng)當(dāng)前導(dǎo)航控制器的height付翁,并且此時(shí)將Dock從main上邊移除简肴,將其添加到root控制器上,這樣在某一個(gè)導(dǎo)航控制器push出一個(gè)新的子頁(yè)面時(shí)百侧,Dock就會(huì)跟隨root控制器一起被隱藏砰识;同樣如果它是根控制器那么就要把拉長(zhǎng)的導(dǎo)航控制器的height變回初始高度,并且把Dock從根控制器上重新移回到main上佣渴。這里要注意的一點(diǎn)就是辫狼,一個(gè)操作是在新控制器將要展示之前就要做,另一個(gè)是在新控制器展示出來(lái)之后再做辛润。至于哪一個(gè)是will哪一個(gè)是did膨处,大家可以自己考慮一下,如果你真的不太理解可以去看一下這個(gè)項(xiàng)目demo的源碼(地址在下面會(huì)給出)砂竖。
Dock欄的移動(dòng)與隱藏基本上就這樣真椿,那么Dock欄既然充當(dāng)?shù)氖且粋€(gè)UITabBar的作用,也就是說(shuō)我們?cè)谶x擇某一個(gè)欄目的同時(shí)MainController應(yīng)該及時(shí)的隱藏舊的欄目并顯示新的欄目乎澄,然而這并不難辦瀑粥,使用代理很簡(jiǎn)單的就能實(shí)現(xiàn)這個(gè)功能,在Dock上的item接收到點(diǎn)擊事件時(shí)改變自身顯示的同時(shí)告訴自己的代理你現(xiàn)在要給我顯示第幾個(gè)子控制器就行了三圆。至于代碼我就不再在這貼了狞换。
最后,我又給這個(gè)Dock添加了一個(gè)新的小功能舟肉,大家在上圖應(yīng)該也能看到第三個(gè)和第四個(gè)item右上角的一個(gè)mark小紅點(diǎn)修噪。提示用戶某一個(gè)欄目中可能有新消息或是活動(dòng),添加新消息提示的mark點(diǎn)共有兩種模式路媚,一種是只顯示有新消息標(biāo)示而不顯示具體數(shù)目黄琼,另一種是直接顯示新消息的條數(shù),那么具體使用哪種模式就看個(gè)人愛(ài)好了。
注:此文章首發(fā)在簡(jiǎn)書(shū)轉(zhuǎn)載請(qǐng)說(shuō)明出處脏款。
如果你想看到完整的代碼围苫,可以去這里。