今天我們來自定義iOS的一種控制器,平時(shí)在項(xiàng)目中大部分用的都是tabBar控制器(微信),也有側(cè)滑式控制器(QQ),但是如果類似抽屜式的控制器呢? 原理一樣,只是加了一點(diǎn)數(shù)學(xué)運(yùn)算在里面。
有很多優(yōu)秀的第三方,但我們只需要分隔控制器這塊功能就好,在項(xiàng)目中減少代碼量,不妨自己自定義一個(gè)。 顯示如下:

功能要求:
? ? ? ? ? ? ? ? 豎直方向堆疊,每個(gè)控制器如卷起的畫卷一樣,點(diǎn)擊,畫卷展開,再次點(diǎn)擊,畫卷合上(點(diǎn)擊第一封畫卷時(shí),畫卷展開,點(diǎn)擊第二封時(shí),第一封關(guān)閉,第二封展開,以此類推。再次點(diǎn)擊畫卷,畫卷合上)。

注意:之后提到“畫卷”就是“控制器”的意思。
實(shí)現(xiàn)邏輯:
? ? ? ? ? ? ? 1,繼承控制器UIViewController,實(shí)現(xiàn)方法定制;在里面添加extension進(jìn)行“畫卷“自定義。
? ? ? ? ? ? ? 2,實(shí)現(xiàn)相關(guān)數(shù)學(xué)邏輯運(yùn)算(請(qǐng)大家在這里想想畫卷坐標(biāo)變換的實(shí)現(xiàn),不要想得太復(fù)雜哦)。
? ? ? ? ? ? ? 3,以此為基類,創(chuàng)建controller,進(jìn)行UI界面實(shí)現(xiàn)。
? ? ? ? ? ? ? ? ? ?本文只描述了一些核心代碼與邏輯思維,詳細(xì)代碼可參考下面Demo。
一,自定義實(shí)現(xiàn):我們要自定義控制器,顯然得有controller容器,類似tag的標(biāo)記,相關(guān)可見和不可見controller相互轉(zhuǎn)換,以及相關(guān)“畫卷的屬性”,因此在繼承的控制里面定義:
@interface ExpandableViewController : UIViewController
@property (nonatomic, readonly, retain) UIView* contentView;
@property (nonatomic, readonly, retain) UIViewController* expandedViewController; //the top view controller on the stack
@property (nonatomic, readonly, retain) UIViewController* visibleViewController; //Return modal view controller if it exists. Otherwise the top view controller.
@property (nonatomic, copy) NSArray *viewControllers; //the current view controller stack.
@property (nonatomic) NSInteger expandedIndex; //default is 0 is the stack is not empty, NSNotFound otherwise
@property (nonatomic) CGFloat headerViewHeight; // height of each headerView
@end
從而,我們要?jiǎng)討B(tài)加入方法,此時(shí)大家已經(jīng)想到:objc_getAssociatedObject、objc_setAssociatedObject即可實(shí)現(xiàn)。
在調(diào)用此方法是,不要忘記倒入頭文件:
#import <objc/runtime.h>
此時(shí)也注意到,數(shù)個(gè)控制器要在同一界面實(shí)現(xiàn),所以我們不能采用push壓棧方式,或者present的model方式,是的,你也想到了,我們采用以下方式:
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(5_0);
使用上面方法有時(shí)一不小心就把操作步驟弄反了,或漏掉了,會(huì)帶來不必要的麻煩。大致步驟為:加入控制器,開啟響應(yīng)鏈,移除控制器,結(jié)束響應(yīng)鏈
[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
[toViewController didMoveToParentViewController:self];
[fromViewController removeFromParentViewController];
[[UIApplication sharedApplication]endIgnoringInteractionEvents ];
二,邏輯關(guān)系
手勢觸摸,當(dāng)然保證觸摸在響應(yīng)的View上,點(diǎn)擊展開畫卷,再次點(diǎn)擊關(guān)閉畫卷,這里用屬性expandedIndex做標(biāo)記來區(qū)分,判斷響應(yīng)在UIGestureRecognizerDelegate中進(jìn)行:

數(shù)學(xué)邏輯中, controller轉(zhuǎn)換,我們假設(shè)點(diǎn)擊的畫卷標(biāo)記為fromIndex,再次點(diǎn)擊的畫卷為toIndex,則fromIndex > toIndex或者 fromIndex < toIndex。條件為前者時(shí),
CGRect fromViewFrameAfter = fromViewController.view.frame;
fromViewFrameAfter.origin.y += fromViewFrameAfter.size.height;
fromViewController.view.frame = fromViewFrameAfter;
相反為后者時(shí),
CGRect toViewFrameBefore = [self frameForViewControllerAtIndex:toIndex];
toViewFrameBefore.origin.y += toViewFrameBefore.size.height;
toViewController.view.frame = toViewFrameBefore;
讀者可能會(huì)有疑問:frameForViewControllerAtIndex:toIndex這又是什么?這就是來斷定畫卷坐標(biāo)的,看一下代碼您將豁然開朗:

關(guān)鍵布局,則y就是contentView的height加上畫卷的height * 數(shù)量.詳細(xì)看代碼(可能有更簡單的做法):

三,創(chuàng)建controller;因?yàn)槲覀冏远x為了達(dá)到共用效果,面向?qū)ο蠖?,每個(gè)controller都可以調(diào)用并使用;我們暫時(shí)以三個(gè)控制器為例,本Demo中舉例代碼:V1,V2,V3為三個(gè)控制器
[self setViewControllers:@[ [VC1 alloc] init], [[VC2 alloc] init], [[VC3 alloc] init],]];
for (NSUInteger i =0 ; i < [self.viewControllers count]; ++i) {
UIViewController* viewController = self.viewControllers[i];
ExpandableHeaderView* expandableHeaderView = (ExpandableHeaderView *)[viewController expandableHeaderView];
expandableHeaderView.backgroundColor = COLOR_INT32(kColors[i]);
expandableHeaderView.titleLabel.textColor = [UIColor whiteColor];
expandableHeaderView.titleLabel.text = titles[i];
}
詳細(xì)代碼,參考Demo: