第一個(gè)輪子——滑動(dòng)視圖導(dǎo)航控制器

這兩天趁著在公司里繼續(xù)做著不愛做的需求的空隙览妖,將很多App 常用的滑動(dòng)視圖控制器按照自己的想法造了個(gè)輪子瘾杭,在這記錄下整個(gè)流程站蝠。

Demo 地址
GitHub

演示:

demo.gif

介紹

1.CocoaPods

pod 'JCPageController'

2.Demo 使用

//創(chuàng)建pageController
- (JCPageContoller *)pageController{
    if (!_pageController) {
        _pageController = [[JCPageContoller alloc]init];
        _pageController.delegate = self;
        _pageController.dataSource = self;
        [self addChildViewController:_pageController];
        [self.view addSubview:_pageController.view];
        _pageController.lineAinimationType = self.lineAinimationType;
        _pageController.scaleSelectedBar = self.scaleSelectedBar;
    }
    return _pageController;
}

- (NSInteger)numberOfControllersInPageController{
    return count;
}

- (NSString *)reuseIdentifierForControllerAtIndex:(NSInteger)index;{
    return identifier;//用于controller重用
}

- (UIViewController *)pageContoller:(JCPageContoller *)pageContoller controllerAtIndex:(NSInteger)index{
    UIViewController *controller = [pageContoller dequeueReusableControllerWithReuseIdentifier:identifier atIndex:index];//獲取重用的controller
    if (!controller) {
        //controller init
    }
    return controller;
}

- (CGFloat)pageContoller:(JCPageContoller *)pageContoller widthForCellAtIndex:(NSInteger )index{
    return width;
}

- (NSString *)pageContoller:(JCPageContoller *)pageContoller titleForCellAtIndex:(NSInteger)index{
    return text;
}

更多使用方法請(qǐng)看Demo

原理

構(gòu)成

主要分兩個(gè)部分:

  • 上方的TabBar(UICollectionView 構(gòu)成)
  • 下方的容器ContentView(UIScrollView 構(gòu)成)
@property (nonatomic, strong) JCPageSlideBar *slideBar;
@property (nonatomic, strong) UIScrollView *contentView;  

目錄結(jié)構(gòu)

屏幕快照 2017-02-26 22.36.00.png

流程

  1. 通過數(shù)據(jù)源獲取子Controller 的數(shù)量汰具,以及相應(yīng)索引上tabBar 的寬度和title。
  2. 通過數(shù)據(jù)源獲取相應(yīng)索引上的Controller菱魔,先判斷如果有相同identifier 的可復(fù)用Controller留荔,若有,則返回,否則創(chuàng)建該Controller聚蝶,存入到緩存中杰妓。
  3. 當(dāng)手勢(shì)滑動(dòng)ContentView 或點(diǎn)擊TabBar 來切換頁(yè)面時(shí),處理ContentView 與TabBar 之間的協(xié)同問題碘勉。

主要邏輯

首先是重用Controller巷挥,這個(gè)對(duì)提高性能很重要,我是將Controller 都存到controllersMap 這個(gè)字典里验靡,用 @“index_identifier” 來當(dāng)做key倍宾,同一個(gè)identifier 的Controller 最多創(chuàng)建兩個(gè)。

@property (nonatomic, strong) NSMutableDictionary *controllersMap; //用于保存controllers 用 @“index_identifier” 來當(dāng)做key   value為controller

- (UIViewController *)dequeueReusableControllerWithReuseIdentifier:(NSString *)identifier atIndex:(NSInteger)index{
    if (!identifier) {
        return nil;
    }
    NSInteger count = [self.dataSource numberOfControllersInPageController];
    if (index >= count || index < 0) {
        return nil;
    }
    UIViewController *controller = nil;
    NSString *findKey = nil;
    for (NSString *key in self.controllersMap) {
        NSArray *components = [key componentsSeparatedByString:@"_"];
        NSString *indexStr = components.firstObject;
        NSString *identifierStr = components.lastObject;
        NSInteger gap = labs(indexStr.integerValue - index);
        if (self.didSelectBarToChangePage) {
            //點(diǎn)擊tabbar切換頁(yè)面
            if ([identifier isEqualToString:identifierStr]) {
                if (self.currentIndex != indexStr.integerValue) {
                    controller = self.controllersMap[key];
                    findKey = key;
                    break;
                }
            }
        }else{
            //手勢(shì)滑動(dòng)切換頁(yè)面
            if ([identifier isEqualToString:identifierStr] && gap > 1) {
                controller = self.controllersMap[key];
                findKey = key;
                break;
            }
        }
    }
    if (findKey) {
        if ([controller respondsToSelector:@selector(prepareForReuse)]) {
            [controller performSelector:@selector(prepareForReuse)];
        }
        [self.controllersMap removeObjectForKey:findKey];
    }else{
        if ([self getControllerFromMap:index]) {
            controller = [self getControllerFromMap:index];
        }
    }
    return controller;
}

為了性能考慮胜嗓,只有當(dāng)每次滑動(dòng)即將出現(xiàn)某個(gè)index 對(duì)應(yīng)的Controller 時(shí)高职,才去創(chuàng)建該Controller,將其add 到ContentView 相應(yīng)的ContentOffset 上的辞州。

手勢(shì)滑動(dòng)切換頁(yè)面時(shí)怔锌,主要邏輯在scrollViewDidScroll這個(gè)方法里,先判斷滑動(dòng)方向变过,然后配置相應(yīng)的Controller埃元。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    
    ...
    
    BOOL scrollToRight = YES;
    if (contentOffsetX - self.lastOffsetX > 0) {
        if (contentOffsetX <= curControllerOriginX) {
            return;
        }
        nextPage = page < totalCount - 1 ? page + 1 : totalCount - 1;
    }else{
        if (contentOffsetX >= curControllerOriginX) {
            return;
        }
        scrollToRight = NO;
        page = page < totalCount - 1 ? page+1 : totalCount-1;
        nextPage = page > 0 ? page - 1 : 0;
    }
    self.lastOffsetX = contentOffsetX;
    
    if (self.currentIndex != page) {
        //配置當(dāng)前顯示的controller
        self.currentIndex = page;
        self.currentController = self.nextController;
        [self.slideBar selectTabAtIndex:self.currentIndex];
    }
    //配置下個(gè)將要顯示的controller
    [self checkNeedConfigNextPage:scrollToRight nextPage:nextPage];
}

- (void)checkNeedConfigNextPage:(BOOL)scrollToRight nextPage:(NSInteger)nextPage{
    CGFloat contentOffsetX = self.contentView.contentOffset.x;
    BOOL needConfigNextPage = NO;
    if (scrollToRight) {
        if (contentOffsetX > self.currentIndex * self.contentView.frame.size.width) {
            needConfigNextPage = YES;
        }
    }else{
        if (contentOffsetX < self.currentIndex * self.contentView.frame.size.width) {
            needConfigNextPage = YES;
        }
    }
    if (needConfigNextPage && self.nextIndex != nextPage) {
        //配置下一個(gè)即將顯示的controller
        [self willDraggingToNextController:nextPage];
    }
}

當(dāng)點(diǎn)擊tabBar 切換頁(yè)面時(shí),主要實(shí)現(xiàn)JCPageSlideBarDelegate 代理方法媚狰,將nextVCL 放在當(dāng)前Controller 相鄰位置上岛杀,待滾動(dòng)結(jié)束后在恢復(fù)真正位置。

- (void)pageSlideBar:(JCPageSlideBar *)pageSlideBar didSelectBarAtIndex:(NSInteger)index{
    ...
    self.selectBarIndex = index;
    NSInteger realIndex = self.currentIndex < index ?  self.currentIndex + 1 : self.currentIndex - 1;
    UIViewController *nextVCL = [self willDraggingToNextController:index];
    if (nextVCL) {
        //將nextVCL 放在相鄰位置上哈雏,待滾動(dòng)結(jié)束后在恢復(fù)真正位置
        self.contentView.userInteractionEnabled = NO;//滾動(dòng)期間 不允許用戶手勢(shì)操作
        self.currentController = nextVCL;
        CGRect rect = nextVCL.view.frame;
        rect.origin.x = realIndex * self.contentView.frame.size.width;
        nextVCL.view.frame = rect;
    }
    [self.contentView setContentOffset:CGPointMake(realIndex * self.contentView.frame.size.width,0) animated:YES];
}

默認(rèn)提供了四種line 的切換動(dòng)畫

typedef NS_ENUM(NSUInteger, JCSlideBarLineAnimationType) {
    JCSlideBarLineAnimationFixedWidth = 0,       //固定寬度
    JCSlideBarLineAnimationDynamicWidth = 1,     //動(dòng)態(tài)寬度楞件,與標(biāo)題文字等寬
    JCSlideBarLineAnimationStretchFixedWidth = 2,          //拉伸效果 固定寬度
    JCSlideBarLineAnimationStretchDynamicWidth = 3,          //拉伸效果 動(dòng)態(tài)寬度衫生,與標(biāo)題文字等寬
};

其中拉伸效果需要計(jì)算當(dāng)前切換頁(yè)面滑動(dòng)的progress 裳瘪,以此來計(jì)算line 的origin.x 以及width。

這里也提供了tabBar 選中放大效果以及title 顏色漸變罪针,,主要使用的是CGAffineTransformMakeScale彭羹,

@property (nonatomic) BOOL scaleSelectedBar;//是否有選中放大效果
- (void)scaleTitleFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex progress:(CGFloat)progress{
    if (!self.scaleSelectedBar) {
        return;
    }
    CGFloat scale = kSlideBarCellScaleSize;
    CGFloat currentTransform = (scale - 1) * progress;
    UICollectionViewCell *fromCell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:fromIndex inSection:0]];
    UICollectionViewCell *toCell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:toIndex inSection:0]];
    fromCell.transform = CGAffineTransformMakeScale(scale - currentTransform , scale - currentTransform);
    toCell.transform = CGAffineTransformMakeScale(1 + currentTransform, 1 + currentTransform);
    
    if (self.lineAinimationType < JCSlideBarLineAnimationStretchFixedWidth) {
        //不是拉伸效果就不用變顏色了
        return;
    }
    CGFloat narR,narG,narB,narA;
    [kTitleNormalColor getRed:&narR green:&narG blue:&narB alpha:&narA];
    CGFloat selR,selG,selB,selA;
    [kTitleSelectedColor getRed:&selR green:&selG blue:&selB alpha:&selA];
    CGFloat detalR = narR - selR ,detalG = narG - selG,detalB = narB - selB,detalA = narA - selA;
    
    UILabel *fromTitle = [fromCell viewWithTag:kSlideBarCellTitleTag];
    UILabel *toTitle = [toCell viewWithTag:kSlideBarCellTitleTag];
    fromTitle.textColor = [UIColor colorWithRed:selR + detalR * progress green:selG+detalG * progress blue:selB+detalB * progress alpha:selA+detalA * progress];
    toTitle.textColor = [UIColor colorWithRed:narR-detalR * progress green:narG-detalG * progress blue:narB-detalB * progress alpha:narA-detalA * progress];
}

總結(jié)

由于自身的能力以及是第一版,尚且存在很多不足之處泪酱,例如不能自由的定制化派殷,代碼注釋不夠,一些方法邏輯躲起來不夠順暢墓阀,總的來說還是可以滿足基本需求。日后有時(shí)間將會(huì)繼續(xù)完善。歡迎大家指出問題而钞,一起交流础锐。

Demo 地址
GitHub

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市勿锅,隨后出現(xiàn)的幾起案子帕膜,更是在濱河造成了極大的恐慌枣氧,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垮刹,死亡現(xiàn)場(chǎng)離奇詭異达吞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荒典,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門酪劫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人种蝶,你說我怎么就攤上這事契耿。” “怎么了螃征?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵搪桂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我盯滚,道長(zhǎng)踢械,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任魄藕,我火速辦了婚禮内列,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘背率。我一直安慰自己话瞧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布寝姿。 她就那樣靜靜地躺著交排,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饵筑。 梳的紋絲不亂的頭發(fā)上埃篓,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音根资,去河邊找鬼架专。 笑死,一個(gè)胖子當(dāng)著我的面吹牛玄帕,可吹牛的內(nèi)容都是我干的部脚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼裤纹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼委刘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤钱雷,失蹤者是張志新(化名)和其女友劉穎骂铁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罩抗,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拉庵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了套蒂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钞支。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖操刀,靈堂內(nèi)的尸體忽然破棺而出烁挟,到底是詐尸還是另有隱情,我是刑警寧澤骨坑,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布撼嗓,位于F島的核電站,受9級(jí)特大地震影響欢唾,放射性物質(zhì)發(fā)生泄漏且警。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一礁遣、第九天 我趴在偏房一處隱蔽的房頂上張望斑芜。 院中可真熱鬧,春花似錦祟霍、人聲如沸杏头。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)醇王。三九已至,卻和暖如春垂谢,著一層夾襖步出監(jiān)牢的瞬間厦画,已是汗流浹背疮茄。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工滥朱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人力试。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓徙邻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畸裳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缰犁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,065評(píng)論 4 62
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,777評(píng)論 25 707
  • 她帅容,我深深愛著的她颇象。 她,我難以忘記的她并徘。 她遣钳,我永得不到的她。 多少次為她哭泣的我麦乞, 想永遠(yuǎn)的忘記她蕴茴。 多少次為...
    彼岸花s閱讀 324評(píng)論 0 2
  • 黃昏一簾花雨香,倦書沉夢(mèng)逅謝娘姐直。 摘花別是傷心人倦淀,時(shí)有風(fēng)微吹茶涼。
    拾肆十四14閱讀 142評(píng)論 0 0
  • 1 基本介紹 2 tcp_wrapper 3 PAM 認(rèn)證機(jī)制 PAM 認(rèn)證機(jī)制
    一橋長(zhǎng)書閱讀 531評(píng)論 0 0