iOS MMDrawerController源码解读

提前说好,本文绝对不是教你如何使用MMDrawerController这个第三方库,因为那太多人写了 ,也太简单了。这篇文章主要带你分析MMDrawerController是怎么实现抽屉效果,明白第三方库的内部原理。例外,本人也封装了一个FHSegmentControl的第三方库,欢迎大家去看看啊。

1.MMDrawerController的实例化方法

先明确几个变量的意义。

// 中央控制器
@property (nonatomic, strong) UIViewController * centerViewController;
// 左侧控制器、右侧控制器,统称侧边控制器
@property (nonatomic, strong) UIViewController * leftDrawerViewController;
@property (nonatomic, strong) UIViewController * rightDrawerViewController;

我分析第三方库,都是从实例化方法开始的。那么多类,方法更多,必须有一个开始。从实例化方法开始,可以找出作者的思考方式,这里也就是怎么实现抽屉效果的。

//  1.
-(instancetype)initWithCenterViewController:(UIViewController *)centerViewController leftDrawerViewController:(UIViewController *)leftDrawerViewController rightDrawerViewController:(UIViewController *)rightDrawerViewController{
    NSParameterAssert(centerViewController);
    self = [super init];
    if(self){
        [self setCenterViewController:centerViewController];
        [self setLeftDrawerViewController:leftDrawerViewController];
        [self setRightDrawerViewController:rightDrawerViewController];
    }
    return self;
}
// 2.
-(instancetype)initWithCenterViewController:(UIViewController *)centerViewController leftDrawerViewController:(UIViewController *)leftDrawerViewController{
    return [self initWithCenterViewController:centerViewController leftDrawerViewController:leftDrawerViewController rightDrawerViewController:nil];
}
//3.
-(instancetype)initWithCenterViewController:(UIViewController *)centerViewController rightDrawerViewController:(UIViewController *)rightDrawerViewController{
    return [self initWithCenterViewController:centerViewController leftDrawerViewController:nil rightDrawerViewController:rightDrawerViewController];
}

一共有3个实例方法,第2.3个实例方法,最后都得调用第1个实例方法,所以我们只要分析第1个实例方法就可以了。第一个实例方法调用了三个方法。

    // 4
    [self setCenterViewController:centerViewController];
    // 5
    [self setLeftDrawerViewController:leftDrawerViewController];
    // 6
    [self setRightDrawerViewController:rightDrawerViewController];

我们先看第5、6个方法。

-(void)setRightDrawerViewController:(UIViewController *)rightDrawerViewController{
   [self setDrawerViewController:rightDrawerViewController forSide:MMDrawerSideRight];
}

-(void)setLeftDrawerViewController:(UIViewController *)leftDrawerViewController{
   [self setDrawerViewController:leftDrawerViewController forSide:MMDrawerSideLeft];
}

这两个方法其实都是调用了同一个方法:

- (void)setDrawerViewController:(UIViewController *)viewController forSide:(MMDrawerSide)drawerSide;

综上所述,我们要明白MMDrawerController是怎样创建的,只要明白下面的两个方法就可以了,顿时感觉工作量少了很多有没有。

// 1.添加中央控制器
-(void)setCenterViewController:(UIViewController *)centerViewController;
// 2.添加左右两边的控制器
- (void)setDrawerViewController:(UIViewController *)viewController forSide:(MMDrawerSide)drawerSide;

2.添加中央控制器

// 1.
-(void)setCenterViewController:(UIViewController *)centerViewController{
    [self setCenterViewController:centerViewController animated:NO];
}
//2.直接看这个方法就好了,因为第一个方法也是调用这个方法
-(void)setCenterViewController:(UIViewController *)centerViewController animated:(BOOL)animated{
    // 如果将要添加的中央控制器和原来的一样,就不要再添加了
    // self.centerViewController 已经添加的中央内容控制器。第一次实例化,为nil。
   if ([self.centerViewController isEqual:centerViewController]) {
       
        return;
    }
  // 添加中央内容视图,也就是:往self.childControllerContainerView上添加self.centerContainerView
  if (_centerContainerView == nil) {
     // self.childControllerContainerView 是一个添加在self.view 上面和self.view位置和大小一样的UIView。
    CGRect centerFrame = self.childControllerContainerView.bounds;
    if(self.centerContainerView == nil){
        // MMDrawerCenterContainerView 先当做UIView,后面在慢慢分析
        self.centerContainerView = [[MMDrawerCenterContainerView alloc] initWithFrame:centerFrame];
        [self.centerContainerView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
        [self.centerContainerView setBackgroundColor:[UIColor clearColor]];
        //开始时: self.openSide = MMDrawerSideNone;因为我们实例化的时候,并没有展示左右边两边的抽屉视图
        [self.centerContainerView setOpenSide:self.openSide];
        //开始时: self.centerHiddenInteractionMode = MMDrawerOpenCenterInteractionModeNavigationBarOnly;设置响应模式
        [self.centerContainerView setCenterInteractionMode:MMDrawerOpenCenterInteractionModeNone];
        [self.childControllerContainerView addSubview:self.centerContainerView];
    }
  }
    // 因为要添加新的中央控制器,如果旧的存在,就要删除
    UIViewController * oldCenterViewController = self.centerViewController;
    if(oldCenterViewController){
        [oldCenterViewController willMoveToParentViewController:nil];
        if(animated == NO){
            [oldCenterViewController beginAppearanceTransition:NO animated:NO];
        }
        [oldCenterViewController.view removeFromSuperview];
        if(animated == NO){
            [oldCenterViewController endAppearanceTransition];
        }
        [oldCenterViewController removeFromParentViewController];
    }
    // 添加新的中央控制器
    _centerViewController = centerViewController;
    [self addChildViewController:self.centerViewController];
    [self.centerViewController.view setFrame:self.childControllerContainerView.bounds];
    [self.centerContainerView addSubview:self.centerViewController.view];
    // 把中央视图放在子视图容器最前面
    [self.childControllerContainerView bringSubviewToFront:self.centerContainerView];
    [self.centerViewController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
    // 设置阴影,以后讲
    [self updateShadowForCenterView];
    
    if(animated == NO){
       // 实例化的时候,这个方法不走
       // self.view.window 是判断是否添加到了手机屏幕上,也就是keyWindow上。
        if(self.view.window) {
            [self.centerViewController beginAppearanceTransition:YES animated:NO];
            [self.centerViewController endAppearanceTransition];
        }
        //告诉系统有一个视图控制器从容器中添加或者移除
        [self.centerViewController didMoveToParentViewController:self];
    }
}

其实这里一共有四个View,这四个View的大小和位置都是一样的。self 是MMDrawerController 的对象。它们的层级结构就是:
self.view -> self.childControllerContainerView ->self.centerContainerView -> self.centerViewController.view。中央控制器的view在最上面,运行APP的时候,最先看到。

3.添加侧边控制器

- (void)setDrawerViewController:(UIViewController *)viewController forSide:(MMDrawerSide)drawerSide{
    // 如果你添加的是中央控制器,程序就会崩溃。因为这个方法只能添加左右两边的控制器。
    NSParameterAssert(drawerSide != MMDrawerSideNone);
    // 这个方法,可以获得当前的左边或右边的控制器
    UIViewController *currentSideViewController = [self sideDrawerViewControllerForSide:drawerSide];
   // 如果和原来的侧边视图控制器相同,就不必添加了
    if (currentSideViewController == viewController) {
        return;
    }
    // 如果原来已经有了侧边视图控制器,移除
    if (currentSideViewController != nil) {
        [currentSideViewController beginAppearanceTransition:NO animated:NO];
        [currentSideViewController.view removeFromSuperview];
        [currentSideViewController endAppearanceTransition];
        [currentSideViewController willMoveToParentViewController:nil];
        [currentSideViewController removeFromParentViewController];
    }
    // 添加新的侧边视图控制器
    // 左边
    UIViewAutoresizing autoResizingMask = 0;
    if (drawerSide == MMDrawerSideLeft) {
        _leftDrawerViewController = viewController;
        autoResizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight;
        
    }
    // 右边
    else if(drawerSide == MMDrawerSideRight){
        _rightDrawerViewController = viewController;
        autoResizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
    }
    // viewController不为空,代表你想添加侧边控制器
    if(viewController){
        [self addChildViewController:viewController];
        // 实例化的时候不走,直接看else里面的方法
        if((self.openSide == drawerSide) &&
           [self.childControllerContainerView.subviews containsObject:self.centerContainerView]){
            [self.childControllerContainerView insertSubview:viewController.view belowSubview:self.centerContainerView];
            [viewController beginAppearanceTransition:YES animated:NO];
            [viewController endAppearanceTransition];
        }
        else{
            // 添加新的侧边控制器
            [self.childControllerContainerView addSubview:viewController.view];
            [self.childControllerContainerView sendSubviewToBack:viewController.view];
            [viewController.view setHidden:YES];
        }
        [viewController didMoveToParentViewController:self];
        [viewController.view setAutoresizingMask:autoResizingMask];
        //侧边栏的最大宽度默认280.
        // H:代表self.view的高,W代表self.view的宽,w代表侧边栏的最大的宽度
        // 左侧视图的frame: (0,0,w,H);右侧视图的frame: (W-w,0,w,H);
        [viewController.view setFrame:viewController.mm_visibleDrawerFrame];
    }
}
视图控制器的层级.png
侧边视图的位置和大小.png

  这篇文章介绍了MMDrawerController的实例化方法和各个控制器与视图的层级结构,下篇文章主要讲侧边栏的切换动画。如果喜欢就点个赞吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,482评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,123评论 29 470
  • 亲爱的们,你们有没有在把自己未来的一年按照人生规划的很美满的时候,当年末到来的时候却发现一事无成,在失败的道路上跌...
    一片麦田阅读 170评论 2 0
  • 当兔子喜欢对方时就会不停地转圈圈,这是高兴地欢呼雀跃,还是焦虑不安呢?
    长发及腰的路上阅读 216评论 0 0