iOS-addChildViewController使用和選中下劃線實(shí)現(xiàn)

一、為什么蘋果在iOS5添加了addChildViewController阁苞?

蘋果新的API增加了addChildViewController方法差购,并且希望我們?cè)谑褂胊ddSubview時(shí)亭罪,同時(shí)調(diào)用[self addChildViewController:child]方法將sub view對(duì)應(yīng)的viewController也加到當(dāng)前ViewController的管理中复哆。
對(duì)于那些當(dāng)前暫時(shí)不需要顯示的subview欣喧,只通過(guò)addChildViewController把subViewController加進(jìn)去;需要顯示時(shí)再調(diào)用transitionFromViewController方法梯找。將其添加進(jìn)入底層的ViewController中。
這樣做的好處:
1.無(wú)疑益涧,對(duì)頁(yè)面中的邏輯更加分明了锈锤。相應(yīng)的View對(duì)應(yīng)相應(yīng)的ViewController。
2.當(dāng)某個(gè)子View沒(méi)有顯示時(shí)闲询,將不會(huì)被Load久免,減少了內(nèi)存的使用。
3.當(dāng)內(nèi)存緊張時(shí)扭弧,沒(méi)有Load的View將被首先釋放阎姥,優(yōu)化了程序的內(nèi)存釋放機(jī)制。
在iOS5中鸽捻,ViewController中新添加了下面幾個(gè)方法:
* addChildViewController:
* removeFromParentViewController
* transitionFromViewController:toViewController:duration:options:animations:completion:
* willMoveToParentViewController:
* didMoveToParentViewController:

能想到的其它的一些使用場(chǎng)景或者說(shuō)是優(yōu)勢(shì)吧

  • 不以navVC管理的呼巴,transition需要?jiǎng)赢嫷?(例如某些login模塊相關(guān)有注冊(cè),忘記密碼御蒲,修改密碼等)
  • 容器vc
  • 左右聯(lián)動(dòng)衣赶,或上下聯(lián)動(dòng)
  • 條件顯示,一個(gè)vc不是一直顯示的view厚满,且該view比較復(fù)雜府瞄,業(yè)務(wù)功能比較多

不管怎樣,開發(fā)過(guò)程中還是看具體使用場(chǎng)景碘箍,addChildViewController本質(zhì)優(yōu)點(diǎn)還是清晰易管理遵馆,但是濫用的話也會(huì)造成vc耦合度太高的問(wèn)題

二、該demo代碼實(shí)現(xiàn)的功能

1.addChildViewController使用
2.選中下劃線標(biāo)識(shí)(兩種實(shí)現(xiàn)方案)
3.切換標(biāo)簽下劃線動(dòng)畫
4.頭部標(biāo)簽headscrollview抽離封裝
5.childViewController里子view布局時(shí)機(jī)注意事項(xiàng)

三丰榴、addChildViewController使用(代碼有詳細(xì)的注釋和注意點(diǎn))

主容器VC--MainViewController

//
//  MainViewController.m
//  測(cè)試addChildViewController
//  1货邓、選中下劃線標(biāo)識(shí)(兩種實(shí)現(xiàn)方案) 2、切換標(biāo)簽下劃線動(dòng)畫 3多艇、headscrollview抽離封裝
//  Created by caohx on 2021/1/13.
//

#import "MainViewController.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
#import "HeadTitlesScrollView.h"

static CGFloat headHeight = 40;

@interface MainViewController ()
@property (nonatomic, strong) HeadTitlesScrollView *headScrollView;
@property (nonatomic, strong) UIViewController *currentVC;
@property (nonatomic, strong) FirstViewController *firstVC;
@property (nonatomic, strong) SecondViewController *secondVC;
@property (nonatomic, strong) ThirdViewController *thirdVC;
@end

@implementation MainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor grayColor];
    self.automaticallyAdjustsScrollViewInsets = NO;
    
    [self setupSubViews];
    [self requestData];
}

/// 請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)
- (void)requestData {
    //nothing
}

/// 初始化并布局子view
- (void)setupSubViews {
    NSArray *headTitles = @[@"第一個(gè)",@"第二個(gè)",@"第三個(gè)",@"第一個(gè)",@"第二個(gè)",@"第三個(gè)"];
    //初始換headTitleview
    self.headScrollView = [[HeadTitlesScrollView alloc] initWithFrame:CGRectMake(0, 0, ScreenW, 40) titles:headTitles];
    self.headScrollView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:self.headScrollView];
    //點(diǎn)擊頭部標(biāo)簽回調(diào)事件
    __weak typeof(self) weakSelf = self;
    self.headScrollView.blockClickHeadTitle = ^(NSInteger btnTag) {
        [weakSelf clickHeadBtn:btnTag];
    };
    
    //初始化子vc
    CGFloat originY = headHeight;
    CGFloat childVCHeight = ScreenH - headHeight;
    self.firstVC = [FirstViewController new];
    self.firstVC.view.frame = CGRectMake(0, originY, ScreenW, childVCHeight);
    self.secondVC = [SecondViewController new];
    self.secondVC.view.frame = CGRectMake(0, originY, ScreenW-100, childVCHeight-100);
    self.thirdVC = [ThirdViewController new];
    self.thirdVC.view.frame = CGRectMake(0, originY, ScreenW, childVCHeight);
    
    //默認(rèn)添加第一個(gè)子vc
    [self.view addSubview:self.firstVC.view]; //這個(gè)不能刪除
    [self addChildViewController:self.firstVC];
    self.currentVC = self.firstVC;
}


/// 處理點(diǎn)擊某個(gè)頭部標(biāo)簽逻恐,展示對(duì)應(yīng)的vc
/// @param btnTag 正在被點(diǎn)擊按鈕的tag
- (void)clickHeadBtn:(NSInteger)btnTag {
    //點(diǎn)擊的是同一個(gè)則返回
    if (((self.currentVC == self.firstVC) && btnTag == 100) ||
        ((self.currentVC == self.secondVC) && btnTag == 101) ||
        ((self.currentVC == self.thirdVC) && btnTag == 102)) {
        return;
    }
    
    //改變子vc
    switch (btnTag) {
        case 100:
            //顯示第一個(gè)vc
            [self replaceOldVC:self.currentVC toNewVC:self.firstVC];
            break;
        case 101:
            //顯示第二個(gè)vc
            [self replaceOldVC:self.currentVC toNewVC:self.secondVC];
            break;
        case 102:
            //顯示第三個(gè)vc
            [self replaceOldVC:self.currentVC toNewVC:self.thirdVC];
            break;
        default:
            break;
    }
}

- (void)replaceOldVC:(UIViewController*)oldVC toNewVC:(UIViewController*)newVC {
    [self addChildViewController:newVC];
    [self transitionFromViewController:self.currentVC toViewController:newVC duration:1 options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) {
        if (finished) {
            //下面兩句刪除不會(huì)影響
            [self.view addSubview:newVC.view];
            [newVC didMoveToParentViewController:self];
            //移除舊的的vc
            [oldVC willMoveToParentViewController:nil];
            [oldVC removeFromParentViewController];
            self.currentVC = newVC;
        }else {
            self.currentVC = oldVC;
        }
    }];
}

@end

抽離封裝的HeadTitlesScrollView

#import "HeadTitlesScrollView.h"

//以下都可以暴露給外面,動(dòng)態(tài)設(shè)置
static CGFloat headScrollViewHeight = 40;
static CGFloat headScrollViewBtnWidth = 100;
static CGFloat lineWidth = 50.f;
//按鈕的樣式:顏色,字體复隆,背景色等
//底部線的樣式:顏色拨匆,高度,寬度等

@interface HeadTitlesScrollView () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *mainScrollView;
@property (nonatomic, strong) UILabel *bLine;
@property (nonatomic, strong) NSArray *arrTitles;
@property (nonatomic, strong) NSMutableArray *mutArrBtns;
@property (nonatomic, strong) NSMutableArray *mutArrBottomLines;
@end

@implementation HeadTitlesScrollView

#pragma mark- 初始化
- (instancetype)initWithFrame:(CGRect)frame titles:(NSArray*)titles {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor whiteColor];
        self.arrTitles = titles.copy;
        self.mutArrBtns = [NSMutableArray array];
        self.mutArrBottomLines = [NSMutableArray array];
        headScrollViewHeight = self.frame.size.height;
        [self setupSubViews];
    }
    
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    NSAssert(NO, @"請(qǐng)使用initWithFrame:titles:初始化");
    return [self initWithFrame:CGRectZero titles:@[]];
}

- (instancetype)initWithFrame:(CGRect)frame {
    return [self initWithFrame:frame titles:@[]];
}

#pragma mark- 設(shè)置并布局子view
- (void)setupSubViews {
    //主scrollview
    self.mainScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, ScreenW, headScrollViewHeight)];
    self.mainScrollView.backgroundColor = [UIColor blueColor];
    CGFloat contentSizeWidth = self.arrTitles.count*headScrollViewBtnWidth;
    self.mainScrollView.contentSize = CGSizeMake(contentSizeWidth, 0);
//    self.mainScrollView.pagingEnabled = YES;
    self.mainScrollView.scrollEnabled = YES;
    self.mainScrollView.bounces = NO;
    self.mainScrollView.showsHorizontalScrollIndicator = NO;
    [self addSubview:self.mainScrollView];
    
    //頭部scrollview添加button 和 button初始化
    for (int i = 0; i<self.arrTitles.count; i++) {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
        btn.frame = CGRectMake(i*headScrollViewBtnWidth, 0, headScrollViewBtnWidth, headScrollViewHeight);
        [btn setTitle:self.arrTitles[i] forState:UIControlStateNormal];
        [btn setTitleColor:[UIColor purpleColor] forState:UIControlStateNormal];
        btn.titleLabel.textAlignment = NSTextAlignmentCenter;
        btn.tag = 100+i;
        [self.mainScrollView addSubview:btn];
        [btn addTarget:self action:@selector(clickHeadBtn:) forControlEvents:UIControlEventTouchUpInside];
        
//        //選中標(biāo)示線方案一:每一個(gè)btn對(duì)應(yīng)一個(gè)下劃線挽拂,通過(guò)hidden來(lái)控制
//        UILabel *bottomLine = [UILabel new];
//        bottomLine.backgroundColor = [UIColor blackColor];
//        CGFloat lineOriginY = headScrollViewBtnWidth*i + (headScrollViewBtnWidth - lineWidth)/2.0;
//        bottomLine.frame = CGRectMake(lineOriginY, headScrollViewHeight-2, lineWidth, 2);
//        [self.mainScrollView addSubview:bottomLine];
//        //默認(rèn)第一個(gè)選中惭每,顯示下劃線標(biāo)識(shí)線
//        bottomLine.hidden = !(i == 0);
//        [self.mutArrBottomLines addObject:bottomLine];
    }
    
    //選中標(biāo)示線方案二:一個(gè)line 通過(guò)改變offset.x來(lái)改變選中標(biāo)識(shí)
    UILabel *bottomLine = [UILabel new];
    bottomLine.backgroundColor = [UIColor blackColor];
    CGFloat lineOriginX = headScrollViewBtnWidth*0 + (headScrollViewBtnWidth - lineWidth)/2.0;
    bottomLine.frame = CGRectMake(lineOriginX, headScrollViewHeight-2, lineWidth, 2);
    [self.mainScrollView addSubview:bottomLine];
    self.bLine = bottomLine;
}


/// 回調(diào)點(diǎn)擊標(biāo)簽事件
/// @param currClickedBtn 當(dāng)前被點(diǎn)擊的btn
- (void)clickHeadBtn:(UIButton*)currClickedBtn {
    NSInteger currIndex = currClickedBtn.tag -100;
//    //改變選中下劃線 + 方案一
//    [self.mutArrBottomLines enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//        UILabel *currLine = obj;
//        currLine.hidden = !(idx == currIndex);
//    }];
    
    //改變選中下劃線 + 方案二
    CGFloat lineOriginX = headScrollViewBtnWidth*currIndex + (headScrollViewBtnWidth - lineWidth)/2.0;
    [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        self.bLine.frame = CGRectMake(lineOriginX, headScrollViewHeight-2, lineWidth, 2);
    } completion:nil];
    
    
    if (self.blockClickHeadTitle) {
        self.blockClickHeadTitle(currClickedBtn.tag);
    }
}

#pragma mark- UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    //監(jiān)聽(tīng)contentoffset 改變標(biāo)識(shí)下劃線位置
}
@end

childViewController里子view布局時(shí)機(jī)注意

#import "SecondViewController.h"

@interface SecondViewController () <UITableViewDataSource,UITableViewDelegate>
@property (nonatomic, strong) UITableView *mainTb;
@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor purpleColor];
    
//    //不能在這布局;該vc的view此時(shí)還沒(méi)布局完成亏栈,取的會(huì)是主容器view的frame
//    [self.view addSubview:self.mainTb];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    
    //子view正確布局時(shí)機(jī)
    [self.view addSubview:self.mainTb];
}

#pragma mark- UITableViewDatasource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellid"];
    cell.textLabel.text = @"test";
    return cell;
}

#pragma mark- getter
- (UITableView *)mainTb {
    if (!_mainTb) {
        _mainTb = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) style:UITableViewStylePlain];
        _mainTb.delegate = self;
        _mainTb.dataSource = self;
        _mainTb.rowHeight = 100;
        [_mainTb registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellid"];
    }
    return _mainTb;
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
}

@end

效果圖

效果圖台腥,抱歉不會(huì)上傳mp4視頻

結(jié)束語(yǔ)

addChildViewController參考鏈接
本來(lái)想放demo地址的,但是覺(jué)得還是自己敲一遍比較好绒北,主要代碼都在上面黎侈,能力有限 有什么不對(duì)的地方,歡迎指正[Salute]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末闷游,一起剝皮案震驚了整個(gè)濱河市峻汉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脐往,老刑警劉巖休吠,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異业簿,居然都是意外死亡瘤礁,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門梅尤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)柜思,“玉大人,你說(shuō)我怎么就攤上這事克饶≡脱眩” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵矾湃,是天一觀的道長(zhǎng)亡脑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)邀跃,這世上最難降的妖魔是什么霉咨? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮拍屑,結(jié)果婚禮上途戒,老公的妹妹穿的比我還像新娘。我一直安慰自己僵驰,他們只是感情好喷斋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布唁毒。 她就那樣靜靜地躺著,像睡著了一般星爪。 火紅的嫁衣襯著肌膚如雪浆西。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天顽腾,我揣著相機(jī)與錄音近零,去河邊找鬼。 笑死抄肖,一個(gè)胖子當(dāng)著我的面吹牛久信,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播漓摩,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼裙士,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了幌甘?” 一聲冷哼從身側(cè)響起潮售,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锅风,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞍泉,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡皱埠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咖驮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片边器。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖托修,靈堂內(nèi)的尸體忽然破棺而出忘巧,到底是詐尸還是另有隱情,我是刑警寧澤睦刃,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布砚嘴,位于F島的核電站,受9級(jí)特大地震影響涩拙,放射性物質(zhì)發(fā)生泄漏际长。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一兴泥、第九天 我趴在偏房一處隱蔽的房頂上張望工育。 院中可真熱鬧,春花似錦搓彻、人聲如沸如绸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怔接。三九已至搪泳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜕提,已是汗流浹背森书。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谎势,地道東北人凛膏。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像脏榆,于是被迫代替她去往敵國(guó)和親猖毫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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