MCCS 是什么? …… 如果你没在任何地方听说过这个词,那就对了。因为这个词是本文作者,也就是颐和园创造出来的。
MCCS 是作者创造的一种新的 iOS APP 构建方式和设计模式。它是对 MVC 模式的扩展。其目的是为了解决 mvc 模式中控制器变得日益膨胀的问题。MCCS 会将复杂 UI 界面切分为多个更小的单元,并通过子控制器的方式对这些更小的单元进行管理。这里,主控制器主要负责管理这些子控制器,而这些小的 UI 单元(或视图)则由子控制器进行管理。
也就是说,原来一个大控制器中的代码,被分散到多个子控制器中去了。一个单独 UI 需要被细分到什么程度,完全取决于该 UI 的复杂程度。最简单的 UI 可能完全不需要切分,那么这个控制器可能只包含一个子控制器,甚至根本不需要子控制器就能解决(又回到 MVC 模式了)。而最复杂的 UI 可能被切分成数十个控制器(极端情况)。
今天,我们简单介绍一下 MCCS。
MCCS 的全称是 Model - Cell - Controller - SubController 架构。
第一个字母 M 表示 ‘Model’ 即模型。和 MVC 一样,MCCS 也是由模型进行驱动的。
第二个字母 C 表示 ‘Cell’ 即被分割后的小视图单元。在 MCCS 中,视图即 cell,一个大的视图将由若干个小的 cell 组成。
第三个字母 C 表示 ‘Controller’ 即控制器。MCCS 将一个 iPhone 屏幕分成了2 个部分,如下图所示:
固定大小部分是由控制器自身负责管理的,而滚动视图部分是由子控制器管理的。主控制器负责管理这些子控制器的添加、删除和加载等等。
模型、cell、控制器、子控制器之间的关系如下图所示:
这里需要注意几点:
后面我们会详细介绍。
前面也说过,一个 app 的每一个 iPhone 屏幕界面,被分成两部分:一个是固定部分,比如导航栏和 TabBar;另外就是可滚动的部分,collectionView,而collectionView 是由 cell 构成的,因此 cell 指的就是 UICollectionViewCell。
在 MCCS 的第一个阶段,我们需要将 collectionView 中切分成一个个的 cell。由于 cell 是复用的,因此这里并不是简单地将 UI 画成一个一个的格子就行,而是将 cell 按照外观和功能的不同进行分类,使同一类的 cell 具有相同的功能和外观。
比如下面的例子:
整个 collectionView 中所包含的 cell 被分成了 4 种 cell。
这样,这个 UI 需要我们去实现 4 个 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
@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
#import
@interface MallMenusSC : SubController
@end
覆盖 SubController 的 5 个方法:
#import "MallMenusSC.h"
#import "MallMenusCell.h"
#import
// 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 宽度,这个宏是包含在
cellForItemAtIndex 方法
在这个方法中,实例化 cell。注意 cell 的实例化必须要调用 SubController 的 collectionContext 的 dequeueReusableCellOfxxx 方法,否则编译器会报错。在这里,我们分配了 cell 的块属性 menuClicked,这样当 cell 上的 3 颗按钮被点击,这里指定的代码块被执行。
didUpdateToObject 方法
这个方法永远不要实现。实际上,SubController 默认会对这个方法进行空实现。因此,正常的 SubController 不要去覆盖这个方法。
didSelectItemAtIndex
当 cell 被点击时,调用此方法,由于我们已经通过按钮的 TouchUpInside 事件进行了响应,所以这里不需要再实现了。
这 5 个方法不是都必须要实现。除了前 3 个方法外,后面两个方法都可以不实现。在上面的代码中,直接删掉后两个方法即可。
既然控制器原来的许多代码已经分散到子控制器中去运行了,那么控制器的代码是不是变得少很多呢?答案是:是。现在我们的控制器(ViewController) 可以写成这样:
首先是 .h 文件:
#import
#import
@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 的使用。感谢阅读。