網(wǎng)易新聞系列之自定義底部Dock欄目選擇器

作為系列第一篇文章,就先從最簡(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è)屏幕民鼓。


MainController中區(qū)域分配

那么既然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ō)明出處脏款。
如果你想看到完整的代碼围苫,可以去這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末撤师,一起剝皮案震驚了整個(gè)濱河市剂府,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剃盾,老刑警劉巖腺占,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痒谴,居然都是意外死亡衰伯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門积蔚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)意鲸,“玉大人,你說(shuō)我怎么就攤上這事尽爆≡豕耍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵教翩,是天一觀的道長(zhǎng)杆勇。 經(jīng)常有香客問(wèn)我贪壳,道長(zhǎng)饱亿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任闰靴,我火速辦了婚禮彪笼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚂且。我一直安慰自己配猫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布杏死。 她就那樣靜靜地躺著泵肄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淑翼。 梳的紋絲不亂的頭發(fā)上腐巢,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音玄括,去河邊找鬼冯丙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛遭京,可吹牛的內(nèi)容都是我干的胃惜。 我是一名探鬼主播泞莉,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼船殉!你這毒婦竟也來(lái)了鲫趁?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捺弦,失蹤者是張志新(化名)和其女友劉穎饮寞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體列吼,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡幽崩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寞钥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慌申。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖理郑,靈堂內(nèi)的尸體忽然破棺而出蹄溉,到底是詐尸還是另有隱情,我是刑警寧澤您炉,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布柒爵,位于F島的核電站,受9級(jí)特大地震影響赚爵,放射性物質(zhì)發(fā)生泄漏棉胀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一冀膝、第九天 我趴在偏房一處隱蔽的房頂上張望唁奢。 院中可真熱鬧,春花似錦窝剖、人聲如沸麻掸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)脊奋。三九已至,卻和暖如春疙描,著一層夾襖步出監(jiān)牢的瞬間诚隙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工淫痰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留最楷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像籽孙,于是被迫代替她去往敵國(guó)和親烈评。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,167評(píng)論 25 707
  • 問(wèn)答題47 /72 常見(jiàn)瀏覽器兼容性問(wèn)題與解決方案犯建? 參考答案 (1)瀏覽器兼容問(wèn)題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,754評(píng)論 1 92
  • Storyboard是最先在iOS 5引入的一項(xiàng)振奮人心的特性讲冠,大幅縮減構(gòu)建App用戶界面所需的時(shí)間。 要介紹St...
    余一波_Bobby閱讀 61,522評(píng)論 11 76
  • 蘋(píng)果原來(lái)的開(kāi)發(fā)文檔已更新适瓦,此翻譯已過(guò)時(shí)竿开。。玻熙。 來(lái)源自蘋(píng)果開(kāi)發(fā)文檔ViewController Programmin...
    MarkLin閱讀 1,740評(píng)論 4 8
  • 感覺(jué)面試的時(shí)候一直在稀里糊涂否彩,不知道在干嘛 一道題,求window對(duì)象有多少方法,一個(gè)考點(diǎn)是Object.keys...
    進(jìn)擊的前端閱讀 251評(píng)論 0 0