MCCS 是什么?
MCCS 是什么? …… 如果你没在任何地方听说过这个词,那就对了。因为这个词是本文作者,也就是颐和园创造出来的。
MCCS 是作者创造的一种新的 iOS APP 构建方式和设计模式。它是对 MVC 模式的扩展。其目的是为了解决 mvc 模式中控制器变得日益膨胀的问题。MCCS 会将复杂 UI 界面切分为多个更小的单元,并通过子控制器的方式对这些更小的单元进行管理。这里,主控制器主要负责管理这些子控制器,而这些小的 UI 单元(或视图)则由子控制器进行管理。
也就是说,原来一个大控制器中的代码,被分散到多个子控制器中去了。一个单独 UI 需要被细分到什么程度,完全取决于该 UI 的复杂程度。最简单的 UI 可能完全不需要切分,那么这个控制器可能只包含一个子控制器,甚至根本不需要子控制器就能解决(又回到 MVC 模式了)。而最复杂的 UI 可能被切分成数十个控制器(极端情况)。
今天,我们简单介绍一下 MCCS。
MCCS 架构
MCCS 的全称是 Model - Cell - Controller - SubController 架构。
第一个字母 M 表示 ‘Model’ 即模型。和 MVC 一样,MCCS 也是由模型进行驱动的。
第二个字母 C 表示 ‘Cell’ 即被分割后的小视图单元。在 MCCS 中,视图即 cell,一个大的视图将由若干个小的 cell 组成。
第三个字母 C 表示 ‘Controller’ 即控制器。MCCS 将一个 iPhone 屏幕分成了2 个部分,如下图所示:
- 固定大小的部分(红框),比如状态栏、导航栏、TabBar。这些元素是可选的。
- 滚动视图(蓝框)。这部分显示内容的 frame 是不固定的,根据模型进行展示。在 MCCS 中,这实际上是一个 CollectionView。
固定大小部分是由控制器自身负责管理的,而滚动视图部分是由子控制器管理的。主控制器负责管理这些子控制器的添加、删除和加载等等。
模型、cell、控制器、子控制器之间的关系如下图所示:
这里需要注意几点:
- Model 是作用到子控制器上,而不是控制器上的。也就是说子控制器自己管理了数据源。
- 控制器并不能直接管理 cell,cell 是由子控制器管理的。而控制器又管理了子控制器。
- 控制器和子控制器之间的关系是 1 对多的关系,一个控制器管理了多个子控制器。
- 子控制器和 cell 之间的关系是 1 对多的关系,一个子控制器管理了多个 cell。
- 模型可以作用于子控制器,当模型改变,子控制器会刷新 cell,但 cell 不直接受 Model 影响。cell 想要改变(编辑)模型的内容,需要通过子控制器进行。虽然从技术上可以让 cell 直接更新模型,但在 MCCS 中,这是不推荐的,并容易导致一些问题的出现。
- 模型和控制器之间可能存在关联,但这不是必须的,所以用虚线表示。控制器可以通过这种关联间接影响子控制器,从改变视图的显示。
后面我们会详细介绍。
cell 即视图
前面也说过,一个 app 的每一个 iPhone 屏幕界面,被分成两部分:一个是固定部分,比如导航栏和 TabBar;另外就是可滚动的部分,collectionView,而collectionView 是由 cell 构成的,因此 cell 指的就是 UICollectionViewCell。
切分 UI
在 MCCS 的第一个阶段,我们需要将 collectionView 中切分成一个个的 cell。由于 cell 是复用的,因此这里并不是简单地将 UI 画成一个一个的格子就行,而是将 cell 按照外观和功能的不同进行分类,使同一类的 cell 具有相同的功能和外观。
比如下面的例子:
整个 collectionView 中所包含的 cell 被分成了 4 种 cell。
- 最上面的轮播图显然可以划分为一类的 cell 。
- 第二排的 3 个 cell 划分为另一类。
- 第三排的向你推荐是一个类。
- 后面所有 cell 展示了商品列表,应该划分为一个类。
这样,这个 UI 需要我们去实现 4 个 UICollectionViewCell 子类。
实现 UICollectionViewCell
为了提高效率,在 MCCS 框架中,提供了一个 NibCollectionViewCell,你的 UICollectionViewCell 实际上只需要继承自这个 NibCollectionViewCell,就可以更容易被子控制器所使用。
以前面的例子为例,假设我们要实现第三排的 UICollectionViewCell,那么我们可以这样做:
-
New File -> Cocoa Touch Class,Subclass of 选择 UICollectionViewCell,然后勾上 Also create the XIB file,类名不妨就叫做:MallMenusCell。
-
打开 MallMenusCell.h 文件,让 MallMenusCell 类继承 NibCollectionViewCell 类:
#import <MCCSframework/NibCollectionViewCell.h> @interface MallMenusCell : NibCollectionViewCell
同时定义一个属性:
@property (strong, nonatomic) void(^menuClicked)(NSInteger index);
这是一个 block(代码块)属性,将 cell 中的用户触摸事件暴露或委托给子控制器处理。
-
在故事板编辑器中,设计 MallMenusCell.xib 如下图所示:
画布中放入了 3 个按钮。你可以使用 StackView 把它们包裹起来。Cell 的大小你可以在 Size Inspector 中修改。
-
分别将 3 个按钮的 TouchUpInside 事件关联到源文件的 buttonClicked: 方法:
- (IBAction)buttonClicked:(UIButton *)sender { if(_menuClicked){ _menuClicked(sender.tag); } }
在这里,我们直接调用了从子控制器中传递过来的 menuClicked 块。
注意,我们为每个 button 设置了不同的 Tag 值,所以在 buttonClicked 方法中,我们将 Tag 值传递给了 menuClicked 块,以便子控制器识别这是哪一个按钮。
可以看到,一个 cell 的实现十分简单,主要的 UI 创建和布局工作都是在故事板编辑器中进行的,使得效率大大提高——不可否认,“画代码”要比“写代码”轻松得多,对于熟练的人来说,画一个这样的 cell 最多 20 分钟。同时,这也减轻了类文件中的代码,从而减少了程序员在以前无论如何都避免不了的一些错误。
设计好 cell 之后,就应该来看看子控制器了。
实现子控制器
子控制器与 cell 之间并不是简单的 1 对多的关系,实际工程中,更可能的是多对多关系。但无论如何,一个 cell 起码要在一个子控制器中得到调用。我们刚才设计了第一个 cell,现在就来实现调用和管理这个 cell 的子控制器。
在 MCCS 中,子控制器用 SubController 表示,你的子控制器必须继承这个类。继续以前面的例子为例,实现一个子控制器的过程大致如下:
-
New File -> Cocoa Touch Class,Subclass of 选择 NSObject,类名叫做:MallMenusSC。
-
修改 MallMenusSC.h,让它继承 SubController:
#import <Foundation/Foundation.h> #import <MCCSframework/SubController.h> @interface MallMenusSC : SubController @end
-
覆盖 SubController 的 5 个方法:
#import "MallMenusSC.h" #import "MallMenusCell.h" #import <MCCSframework/dimensions.h> // 1 - (NSInteger)numberOfItems{ return 1; } // 2 - (CGSize)sizeForItemAtIndex:(NSInteger)index{ return CGSizeMake(SCREEN_WIDTH, 95); } // 3 -(UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index{ MallMenusCell* cell = [self.collectionContext dequeueReusableCellOfClass:MallMenusCell.class forSectionController:self atIndex:index]; cell.menuClicked = ^(NSInteger index) { [self menuClicked:index]; }; return cell; } // 4 - (void)didUpdateToObject:(id)object{ } // 5 - (void)didSelectItemAtIndex:(NSInteger)index{ }
如果你之前用过 IGListKit,那么你会发现这 5 个方法很眼熟。没错, 这 5 个方法实际上来自于 IGListSectionType 协议。MCCS 在底层使用了一个 IGListCollectionView 来作为它的 collectionView,因此 SubController 其实是 IGListSectionController 的子类。
-
numberOfItems 方法
实现这个方法并返回一个整型,用于决定这个子控制器中将包含几个 cell。因为我们将所有的按钮都放到一个 cell 中去了所以,只需要一个 cell 就可以了。如果将一个按钮设计到一个 cell 中,那么我们需要 3个 cell 才能显示完这 3 个按钮,这这里需要返回 3.
-
sizeForItemAtIndex 方法
在这个方法里指定每个 cell 的大小。因为这个 cell 实际上占据了整个屏幕宽度,因此我们使用了 SCREEN_WIDTH 宏作为 cell 宽度,这个宏是包含在 <MCCSframework/dimensions.h> 头文件中的。95 是它的高度,这个高度不但是我们在 xib 里面指定的高度,也是 UI 标注图上的实际高度。
-
cellForItemAtIndex 方法
在这个方法中,实例化 cell。注意 cell 的实例化必须要调用 SubController 的 collectionContext 的 dequeueReusableCellOfxxx 方法,否则编译器会报错。在这里,我们分配了 cell 的块属性 menuClicked,这样当 cell 上的 3 颗按钮被点击,这里指定的代码块被执行。
-
didUpdateToObject 方法
这个方法永远不要实现。实际上,SubController 默认会对这个方法进行空实现。因此,正常的 SubController 不要去覆盖这个方法。
-
didSelectItemAtIndex
当 cell 被点击时,调用此方法,由于我们已经通过按钮的 TouchUpInside 事件进行了响应,所以这里不需要再实现了。
这 5 个方法不是都必须要实现。除了前 3 个方法外,后面两个方法都可以不实现。在上面的代码中,直接删掉后两个方法即可。
-
将子控制器添加到控制器中
既然控制器原来的许多代码已经分散到子控制器中去运行了,那么控制器的代码是不是变得少很多呢?答案是:是。现在我们的控制器(ViewController) 可以写成这样:
-
首先是 .h 文件:
#import <UIKit/UIKit.h> #import <MCCSframework/BaseController.h> @interface MallVC : BaseController @end
MCCS 提供了一个基本的 UIViewController,叫做 BaseController,你的控制器只需要继承它就可以了。
-
然后是 .m 文件:
#import "MallVC.h" #import "MallMenusSC.h" @interface MallVC @property (strong, nonatomic) MallMenusSC* menuSC;// 菜单按钮 @end @implementation MallVC - (void)viewDidLoad { [super viewDidLoad]; [self addSC:self.menuSC]; [self.adapter reloadDataWithCompletion:nil]; } -(MallMenusSC*)menuSC{ if(!_menuSC){ _menuSC = [MallMenusSC new]; } return _menuSC; } @end
就这么多代码了。归结起来有 4 步:
-
首先声明要使用的子控制器属性,比如:
@property (strong, nonatomic) MallMenusSC* menuSC;// 菜单按钮 -
然后以懒加载的方式初始化这个子控制器:
-(MallMenusSC*)menuSC{ if(!_menuSC){ _menuSC = [MallMenusSC new]; } return _menuSC; }
-
在 viewDidLoad 方法中调用 addSC 方法添加子控制器:
[self addSC:self.menuSC];
-
调用 reloadDataWithCompletion:
[self.adapter reloadDataWithCompletion:nil];
注意,addSC 方法将子控制器添加到主控制器的 subControllers 数组中,每当 subControllers 数组有改变时,要想在屏幕上看到这种改变,必需调用 reloadDataWithCompletion 方法。如果你 addSC 之后运行 app,发现界面上没有任何改变,请检查一下你是否在 addSC 之后调用过 reloadDataWithCompletion 方法。
按照上面的模式,你可以在一个控制器中添加更多的子控制器。例如,如果你将我们之前划分的 4 种 cell 和对应的控制器都分别实现完了,那么 MallVC.m 中的代码可能会变成这个样子:
@interface MallVC (
// Section Controller
@property (strong, nonatomic) BannerItemsSC *bannerSC;// Banner
@property (strong, nonatomic) MallMenusSC* menuSC;// 菜单按钮
@property (strong, nonatomic) SectionTitleSC* recommendTitleSC;// 推荐商品的标题
@property (strong, nonatomic) RecommendGoodsSC* recommendSC;// 推荐商品
...
- (void)viewDidLoad {
[super viewDidLoad];
// 添加 SectionController
[self addSC:self.bannerSC]; // banner
[self addSC:self.menuSC]; // 菜单按钮
[self addSC:self.recommendTitleSC];// 推荐商品标题
[self addSC: self.recommendSC];// 推荐商品
[self.adapter reloadDataWithCompletion:nil];
}
...
// MARK: - Lazy load
-(MallMenusSC*)menuSC{
if(!_menuSC){
_menuSC = [MallMenusSC new];
}
return _menuSC;
}
-(BannerItemsSC*)bannerSC{
if(!_bannerSC){
_bannerSC = [BannerItemsSC new];
}
return _bannerSC;
}
-(SectionTitleSC*)recommendTitleSC{
if(!_recommendTitleSC){
_recommendTitleSC = [SectionTitleSC new];
}
return _recommendTitleSC;
}
-(RecommendGoodsSC*)recommendSC{
if(!_recommendSC){
_recommendSC = [RecommendGoodsSC new];
}
return _ recommendSC;
}
结束
关于 MCCS 的概念今天就讲到这里。接下来一段时间,我会连续用一系列的文章来介绍 MCCS 的使用。感谢阅读。
转载:https://blog.csdn.net/kmyhy/article/details/100310363