MCCSframework 是什么
在上一篇介绍 MCCS:一种全新的 iOS APP 构建方式中,我们介绍了什么是 MCCS。MCCS 是一种设计模式,它的具体实现是 MCCSframework。
MCCSframework 当前只有 O-C 语言的版本,目前还没有 Swift 版本。
MCCSframework 的目前版本是 0.6.6,它的地址是:https://gitee.com/kmyhy/MCCSframework。
你可以直接下载它,也可以用 CocoaPods 来安装。它目前提供了 framework (二进制方式),没有源代码。
安装 MCCSframework
用 CocoaPods 安装非常简单,这是我们推荐的安装方式。编辑你的 Podfile 文件,添加 :
pod 'MCCSframework','~>0.5.1'
然后 pod install。注意,MCCSframework 依赖了一些第三方库,pod install 后会自动在你的项目中安装它们。这等同于在你的 Podfile 文件中增加了以下内容:
pod 'IGListKit', '~> 2.0.0'
pod 'UICollectionViewLeftAlignedLayout'
pod 'AFNetworking'
pod 'MBProgressHUD', '~> 0.9.2'
pod 'JSONModel'
pod 'Masonry'
pod 'ReactiveObjC', '~> 3.0.0' #RAC
pod 'MJRefresh'
此外,还需要修改项目的 Building Settings,将 Enable Bitcode 设置为 NO,将 Allow Non-modular … 设置为 YES。
设置完后,就可以在你的项目中使用 MCCSframework 了。
使用 MCCS 架构编写 APP
安装完成后,就可以在项目中使用 MCCS 架构的方式来编写 APP 了。注意,MCCS 和 MVC 是完全无缝兼容的,它不需要你破坏原来的项目结构和代码编写方式。你完全可以在项目中同时使用 MVC 和 MCCS。将一些简单的界面仍然使用 MVC 方式构建,而复杂界面则使用 MCCS 构建。
当然,为了演示,我们可以用一些不是那么“复杂”的界面来开始。
假设我们要实现这样一个界面:
依据前面提过的原则,我们可以将它划分几种不同的 cell,如下图所示:
其中:
- 在这个 UI 中,有两个列表,上面的横向列表表示肉类下面的二级分类列表:猪肉、牛肉……等等。下面的列表则表示当用户选中某个二级分类时的商品列表,如果用户未选定,则显示整个一级的商品列表。
- 这两个列表都有各自的标题,这两个标题,我们可以同一种 cell 绘制,所以都命名为 cell1。
- 其中,第一个列表是用的 cell 归成一类,即 cell2。这个列表实际上只有一个 cell,这个 cell 上回包含一个允许横向滚动的 UICollectionView,二级分类实际上是这个 UICollectionView 的 cell,即 cell3。
- 第二个列表使用的 cell 属于 cell 4。
这样在这个 UI 中将会涉及到 4 个 UICollectionViewCell 类。划分完 cell,接下来我们来看看这些 cell 需要由几个子控制器来进行管理。
因为子控制器实际上负责替 ViewController 管理屏幕,每个子控制器分别控制器屏幕的一片区域,从视图可知,整个屏幕被我们从上到下划分成了 4 段,因此我们需要 4 个子控制器。
在这里需要注意:
- 一个子控制器只能管理一段屏幕,注意,是“段”,而不是“片”,因为这个区域只能横向划分,不能纵向划分,更不能是不规则形状划分。因此只能是矩形,同时,矩形的宽度必须占据屏幕完整宽度。
- 划分的范围只能是屏幕的滚动区域。固定的区域,比如头部导航栏和底部 TabBar 不能算在内——这些是 ViewController 负责管理范围,子控制器不能逾越。
- 两个列表的标题其实都是非常简单的文字显示,所以两个子控制器可以用同一个子控制器类来展示,使用一个类的两个实例即可。
因此,整个界面需要用到 4 个 Cell 类、3 个子控制器类。
绘制 cell
-
显示列表标题的 cell 我们可以使用框架提供的 OneLabelCell 类。
这个 cell 只包含了一个 UILabel 的属性 lbTitle。
-
二级分类的 cell 可以用框架提供的 EmbedCollectionViewCell 类。这个 cell 中嵌套了一个 UICollectionView。
-
二级分类的 cell 中嵌套的 UICollectionView 需要使用一种 cell,我们需要创建 Collection View 自身所用到的 cell。
新建一个 UICollectionViewCell,名为 SecondCategoryCell,勾选 Alson create XIB File。
打开 SecondCategoryCell,拖入一个 UIImageView 和一个 UILabel:创建两个 IBOutlet:
#import <MCCSframework/NibCollectionViewCell.h> @interface SecondCategoryCell : NibCollectionViewCell @property (weak, nonatomic) IBOutlet UIImageView *ivImage; @property (weak, nonatomic) IBOutlet UILabel *lbTitle; @end
注意,这里继承了框架提供的 NibCollectionViewCell。NibCollectionViewCell 提供了从 xib 文件中实例化一个 UICollectionViewCell 的能力。
-
商品列表所用 cell 为 OptimumGoodsCell。OptimumGoodsCell.xib 的内容如下,包含了一个 UIImageView 和 3 个 Label:
OptimumGoodsCell 中创建的 IBOutlet 如下:
#import <MCCSframework/Configurable.h> @interface OptimumGoodsCell : NibCollectionViewCell<Configurable> @property (weak, nonatomic) IBOutlet UILabel *lbPurchaser; @property (weak, nonatomic) IBOutlet UILabel *lbPrice; @property (weak, nonatomic) IBOutlet UIView *shadowView; @property (weak, nonatomic) IBOutlet UILabel *lbName; @property (weak, nonatomic) IBOutlet UIImageView *ivImage; @end
注意,这里实现了框架提供的 Configurable 接口。该接口需要实现一个 configWithObject:(id)obj 方法。OptimumGoodsCell 实现了这个方法:
#import "OptimumGoodsCell.h"
#import "Goods.h"
#import <SDWebImage/UIImageView+WebCache.h>
#import <MCCSframework/Utils.h>
#import <MCCSframework/NSString+Add.h>
@implementation OptimumGoodsCell
-(void)configWithObject:(id)obj{
if([obj isKindOfClass:Goods.class]){
Goods* goods = (Goods*)obj;
// 价格
self.lbPrice.text = f2s(goods.price,2);
// 销量
self.lbPurchaser.text = [NSString stringWithFormat:@"销量 %ld",goods.salesVolume];
// 商品名称
self.lbName.text = goods.productName;
// 商品图片
[self.ivImage sd_setImageWithURL:remoteImgAddr(goods.mainImgId) placeholderImage:[UIImage imageNamed:@"商品"]
];
}
}
cell 就创建好了,接下来是子控制器。
实现子控制器
前面讨论到的 3 个子控制器类分别是:
- OneLabelSC:用来显示视图中两个列表的标题,所管理的 cell 包括 OnceLabelCell。
- SecondCategorySC:用来显示二级分类列表,所管理的 cell 包括 EmbedCollectionViewCell(以及其中嵌套的 SecondCategoryCell。
- OptimumGoodsSC:用来显示商品列表,所管理的 cell 包括 OptimumGoodsCell。
OneLabelSC 子控制器
OneLabelSC.h:
#import <MCCSframework/SubController.h>
@interface OneLabelSC : SubController
@property (strong, nonatomic) NSString* title;
@property (strong, nonatomic) UIFont* font;
@property (strong, nonatomic) UIColor* color;
@property(assign, nonatomic) CGFloat height;
@property(assign, nonatomic) CGFloat leading;
@end
- 子控制器必须继承框架的 SubController。
- 这个子控制器仅仅显示一行占据一定高度的标题文本。所以需要有 title、font、color、height 等属性。分别用于表示文本的内容、字体大小、字体的颜色、行的高度。
- leading 属性用于控制文本缩进距离。
OneLabelSC.m:
```
#import <MCCSframework/dimensions.h>
#import <MCCSframework/OneLabelCell.h>
#import <MCCSframework/UIColor+Hex.h>
@implementation OneLabelSC
// 1
-(instancetype)init{
if(self = [super init]){
self.font = [UIFont boldSystemFontOfSize:15];
self.color = hex_color(0x333333);
self.height = 50;
self.leading = 20;
}
return self;
}
// 2
- (NSInteger)numberOfItems{
return 1;
}
// 3
- (CGSize)sizeForItemAtIndex:(NSInteger)index{
return CGSizeMake(SCREEN_WIDTH, _height);
}
// 4
-(UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index{
OneLabelCell* cell = [self.collectionContext dequeueReusableCellOfClass:[OneLabelCell class] forSectionController:self atIndex:index];// Crash!!! [MultilineTextInputCell new];
cell.lbTitle.text = _title;
cell.lbTitle.textColor = _color;
cell.lbTitle.font = _font;
cell.lbTitleLeading.constant = 20;
return cell;
}
@end
```
继承 SubController,需要覆盖 SubController 的 5 个方法。这 5 个方法中,只有 3 个方法必须实现。这些方法在上一篇文章中已经介绍过。这里我们只覆盖了其中 3 个。
- 初始化方法中,我们设置了子控制器的几个属性默认值。
- numberOfItems 方法返回一个整型,用于决定这个子控制器中将包含几个 cell。对于标题来说,返回 1 即可。
- sizeForItemAtIndex 方法指定每个 cell 的大小。因为这个 cell 实际上占据了整个屏幕宽度,因此我们使用了 SCREEN_WIDTH 宏作为 cell 宽度,这个宏是包含在 <MCCSframework/dimensions.h> 头文件中的。cell 的高度即标题的 height 属性所指定。
- 在 cellForItemAtIndex 方法中,我们需要提供该 SubController 所管理的 cell 对象。同时配置这些 cell。这里我们配置了 OneLabelCell 的标签文本、字体即颜色等。
SecondCategorySC 子控制器
SecondCategorySC.h:
#import "GoodsType.h"
#import <MCCSframework/SubController.h>
@interface SecondCategorySC : SubController
@property (strong, nonatomic) GoodsType* goodsType; // 1
@property (strong, nonatomic) void(^subclassSelected)(TypeNode* subclass);// 2
@end
- 这个子控制器有一个属性 goodsType,这实际上就是子控制器要渲染的数据模型。该模型是一个实体类 GoodsType,用于表示商品分类,我们会在“模型”一节进行介绍。
- 此外还有一个 block 属性,用于处理二级分类被用户选中时的事件。
SecondCategorySC.m:
#import <MCCSframework/dimensions.h>
#import <ReactiveObjC.h>
#import "SecondCategoryCell.h"
#import <MCCSframework/EmbedCollectionViewCell.h>
#import <MCCSframework/UIImage+Extension.h>
#import <MCCSframework/UIColor+Hex.h>
#import <MCCSframework/Utils.h>
#import <MCCSframework/NSString+Add.h>
#import "UIViewController+Navigation.h"
#import <SDWebImage/UIImageView+WebCache.h>
@interface SecondCategorySC()<UICollectionViewDelegate,UICollectionViewDataSource>
@end
@implementation SecondCategorySC
- (NSInteger)numberOfItems{
return 1;
}
- (CGSize)sizeForItemAtIndex:(NSInteger)index{
return CGSizeMake(SCREEN_WIDTH, 110);
}
-(UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index{
EmbedCollectionViewCell* cell = [self.collectionContext dequeueReusableCellOfClass:EmbedCollectionViewCell.class forSectionController:self atIndex:index];
[cell.collectionView registerNib:[UINib nibWithNibName:@"SecondCategoryCell" bundle:nil] forCellWithReuseIdentifier:@"SecondCategoryCell"];
cell.collectionView.delegate = self;
cell.collectionView.dataSource = self;
[cell.collectionView reloadData];
cell.collectionViewLeading.constant = 16;
cell.collectionViewTrail.constant = 16;
cell.collectionView.layer.cornerRadius = 8;
return cell;
}
#pragma mark -----UICollectionViewDataSource-----
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
SecondCategoryCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"SecondCategoryCell" forIndexPath:indexPath];
TypeNode* node = _goodsType.children[indexPath.row];
if(!stringIsEmpty(node.icon)){
[cell.ivImage sd_setImageWithURL:remoteImgAddr(node.icon) placeholderImage:[UIImage imageNamed:@"goodstype_placeholder"]];
}
cell.lbTitle.text = _goodsType.children[indexPath.row].categoryName;
return cell;
}
//每一组有多少个cell
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return _goodsType.children.count;
}
#pragma mark -----UICollectionViewDelegate-----
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
TypeNode* subclass = _goodsType.children[indexPath.item];
if(_subclassSelected){
_subclassSelected(subclass);
}
}
//定义每一个cell的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(114,110);
}
@end
这个子控制器的代码要比之前的多一些,因为这个子控制器除了要实现 SubController 的 3 个必须方法外,还额外实现了UICollectionViewDelegate 协议和 UICollectionViewDataSource 协议。
在 SubController 必须覆盖的 3 个方法中,前两个方法都简单,需要注意的仅仅是 cellForItemAtIndex 方法。在这个方法中:
- 我们创建了一个 EmbedCollectionViewCell 实例。这个类是框架提供的,它是一种特殊的 cell,仅仅是嵌入了一个 UICollectionView,没有任何其它的 UI。
- 创建好 EmbedCollectionViewCell 实例后,我们需要为它的 UICollectionView 注册 一个 cell,设置它的 delegate 和 datasource —— 这里我们将两者都设置为 self。这样我们就可以用子控制器充当 CollectionView 的数据源和委托了。
所以接下来我们要实现UICollectionViewDelegate 协议和 UICollectionViewDataSource 协议。这部分内容和我们在使用 UIKit 中的做法没有什么两样,你应该很熟悉了。
- 在 numberOfItemsInSection 方法中,我们根据数据模型中包含的二级分类的个数,来决定要显示几个 cell。
- 在 cellForItemAtIndexPath 方法中,我们创建 SecondCategoryCell 对象,并根据数据模型来渲染 cell。
- 在 didSelectItemAtIndexPath 方法中,我们通过 block 调用的方式,将用户选择的二级分类数据传递给外界。
OptimumGoodsSC 子控制器
OptimumGoodsSC 负责管理显示商品列表的 cell。类的声明如下:
#import "Goods.h"
#import <MCCSframework/SubController.h>
@interface OptimumGoodsSC : SubController
@property (strong, nonatomic) NSMutableArray<Goods*>* goodsArray;
@end
goodsArray 是一个模型数组,它的类型是 Goods 类型,用于表示商品的具体数据。这个类在“模型”一节介绍。
类的实现如下:
#import "OptimumGoodsSC.h"
#import "OptimumGoodsCell.h"
#import <MCCSframework/dimensions.h>
@implementation OptimumGoodsSC
- (instancetype)init{
self = [super init];
if (self) {
self.inset=UIEdgeInsetsMake(0, 16, 0, 0);
self.minimumLineSpacing=5;
self.minimumInteritemSpacing=2;
}
return self;
}
-(NSMutableArray<Goods*>*)goodsArray{
if(!_goodsArray){
_goodsArray = [NSMutableArray new];
}
return _goodsArray;
}
- (NSInteger)numberOfItems{
return self.goodsArray ? self.goodsArray.count: 0;
}
- (CGSize)sizeForItemAtIndex:(NSInteger)index{
return CGSizeMake((SCREEN_WIDTH-32)/2,265);
}
-(UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index{
OptimumGoodsCell* cell = [self.collectionContext dequeueReusableCellOfClass:OptimumGoodsCell.class forSectionController:self atIndex:index];
[cell configWithObject:_goodsArray[_noSectionTitle ? index : index-1]];
return cell;
}
@end
大部分代码都是和之前的子控制器一样的套路,不同的在于:
- init 方法中,我们设置了一个 inset 属性。这个属性顾名思义,是用于设置子控制器管理的区域边距的。
- cellForItemAtIndex 方法中,创建了 cell 实例。但是我们并没有逐一配置 cell 的每个属性——因为那样太繁琐,而是调用了 cell 的 configWithObject: 方法,将模型数据传递给 cell,由 cell 自己配置自己。configWithObject: 方法是 Configurable 协议中的方法,而
OptimumGoodsCell 类实现了这个方法(参考“绘制 cell”一节)。
模型
在子控制器中,我们用到了2个模型类 Goods 和 GoodsType,这些模型和正常 APP 中使用的模型——相对于 MVC 中的模型——没有任何区别。
GoodsType 模型
其实这个模型包含了 2 个类:
#import <MCCSframework/JKModel.h>
@interface TypeNode : JKModel
@property (strong, nonatomic) NSString* categoryId;
@property (strong, nonatomic) NSString* categoryName;
@property (strong, nonatomic) NSString* description;
@property (strong, nonatomic) NSString* icon;
@end
@interface GoodsType : JKModel
@property (nonatomic,copy) TypeNode *parent;
@property(nonatomic,strong) NSArray<TypeNode*> *children;
@end
JKModel 是框架提供的,用于解析 JSON 格式的数据(使用了 JSONModel)。
Goods 模型
这个模型也包含了 2 个类:
#import <MCCSframework/JKModel.h>
#import "PageModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface Goods : JKModel
@property (strong, nonatomic) NSString* id;
@property (strong, nonatomic) NSString* productId;// 查询商品详情使用 productId 不要用 id
@property (strong, nonatomic) NSString* categoryId;
@property (strong, nonatomic) NSString* belongsShopId;
@property (strong, nonatomic) NSString* productName;
@property (strong, nonatomic) NSString* introduction;// 文字介绍
@property (strong, nonatomic) NSString* mainImgId;// 封面图片
@property (strong, nonatomic) NSString* imgId;// 展示图片 Id 列表
@property(assign, nonatomic) CGFloat price;// 价格
@property(assign, nonatomic) CGFloat discountPrice;// 打折价
@property (strong, nonatomic) NSString* priceUnit;// 单位
@property(assign, nonatomic) NSInteger inventory;// 库存
@property(assign, nonatomic) NSInteger salesVolume;// 销量
@property (strong, nonatomic) NSString* shipMethod;// 配送方式 1 线下自提
@property (strong, nonatomic) NSString* remarks;//
//@property (strong, nonatomic) NSString* amount;// 查我的订单时会有该字段
// 非实体映射属性
@property(assign, nonatomic) NSInteger amount;
@property(assign, nonatomic) BOOL checked;
@end
@interface GoodsPage : PageModel
@property (strong, nonatomic) NSArray<Goods*>* records;
@end
经常使用 JSONModel 的人应该非常熟悉这些代码了,基本上是用工具生成的代码。其中 GoodsPage 是接口返回的分页数据,它主要包含了一个 Goods 数组,此外还包含了从父类 PageModel 继承来的一些属性,比如页码、页大小等。
实现控制器
当 cell、子控制器、模型这些原材料准备好之后,实现控制器是一件非常简单的过程。
控制器的类声明如下:
#import <MCCSframework/NavBarVC.h>
#import "GoodsType.h"
@interface SecondCategoryVC : NavBarVC // 1
@property (strong, nonatomic) GoodsType* goodsType; // 2
@end
- 继承框架提供的 NavBarVC,这样我们就有一个现成的导航栏可用。
- 一个 goodsType 属性,由外界传入,这个实际上是从一级分类选择页面传递过来的参数,表示用户当前所选择的商品一级分类。
在 SecondCategoryVC.m 中,我们首先声明两个属性:
@interface SecondCategoryVC()
@property (strong, nonatomic) SecondCategorySC* subclassSC;// 二级分类列表
@property (strong, nonatomic) OptimumGoodsSC* optimumSC;// 商品列表
@end
分别对应二级分类子控制器和商品列表子控制器。然后以懒加载的方式初始化两个子控制器:
// MARK: - Lazy load
-(OptimumGoodsSC*)optimumSC{
if(!_optimumSC){
_optimumSC = [OptimumGoodsSC new];
}
return _optimumSC;
}
-(SecondCategorySC*)subclassSC{
if(!_subclassSC){
_subclassSC = [SecondCategorySC new];
_subclassSC.goodsType = _goodsType;
@weakify(self)
_subclassSC.subclassSelected = ^(TypeNode * _Nonnull subclass) {
@strongify(self)
NSLog(@"用户选择了二级分类:%@",subclass.categoryName);
};
}
return _subclassSC;
}
注意,二级分类子控制器中,我们设置了 subclassSelected 块,这样当用户选中某个二级分类时,会打印这个二级分类的名称。
然后是 viewDidLoad 方法:
-(void)viewDidLoad{
[super viewDidLoad];
// 1
self.pageHeader.title = @"";
self.pageHeader.rightButtonHidden = YES;
// 2
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.pageHeader.mas_bottom);
make.left.mas_equalTo(0);
make.right.mas_equalTo(0);
make.bottom.mas_equalTo(0);
}];
// 下拉刷新
@weakify(self)
[self.collectionView addHeader:^{
@strongify(self)
[self.collectionView.mj_header endRefreshing];
[self loadFirstPage];
}footer:^{
@strongify(self)
[self.collectionView.mj_footer endRefreshing];
[self loadNextPage];
}];
// 3
[self addSC:[self labelSC:_goodsType.parent.categoryName]];
[self addSC:self.subclassSC];
[self addSC:[self labelSC:@"全部商品"]];
[self addSC:self.optimumSC];
[self.adapter reloadDataWithCompletion:nil];
// 4
[self loadFirstPage];
}
-
父类 NavBarVC 中有一个 pageHeader 属性,实际上就是一个导航条控件,我们需要设置它的标题,否则它会默认显示“安装进度”,隐藏它的 rightButton,否则默认会显示一颗“发布”按钮。
-
必须设置 CollectionView 的约束(使用 Masonry),否则 CollectionView 不会显示(默认 frame 为 0)。此外,我们还设置了 CollectionView 的上拉和下拉动作,以支持上拉加载和下拉刷新。这需要导入框架提供的 UIScrollView+addMJ.h 分类。
-
添加上面实现的 4 个子控制器。其中 labelSC: 方法会创建一个指定标题的单行标题文本(子控制器)。方法实现如下:
-(OneLabelSC*)labelSC:(NSString*)title{ OneLabelSC* _labelSC = [OneLabelSC new]; _labelSC.title = title; return _labelSC; }
当我们调用 addSC(添加子控制器)、rmSC(删除子控制器)、insSC(插入子控制器) 等方法后,一定不要忘记调用 adapter 的 reloadDataWithCompletion 方法。
-
调用 loadFirstPage 方法加载第一页数据。
在设置 CollectionView 的上拉和下拉 block 时,会用到这两个方法:
-(void)loadFirstPage{
self.collectionView.pageNum = 0;
[self loadNextPage];
}
-(void)loadNextPage{
if(self.collectionView.pageNum <=0){
[self.optimumSC.goodsArray removeAllObjects];
}
Goods* goods = [Goods new];
goods.belongsShopId =@"1";
goods.shipMethod=@"线下自提";
goods.id=@"84bd38cdb9b811e9acf6fa163e17a4af";
goods.salesVolume=0;
goods.productName=@"七彩洋芋";
goods.mainImgId=@"1565253738300";
goods.price=6.9000000000000004;
goods.discountPrice=5.1799999999999997;
goods.categoryId=@"2";
[self.optimumSC.goodsArray addObject:goods];
self.collectionView.pageNum++;
[self.optimumSC.collectionContext reloadSectionController:self.optimumSC];
}
分别用于下拉刷新和上拉加载。当然,现在都是 mock 的数据。后面我们会单独用一篇来介绍如何使用 MCCSframework 中的网络模块来加载真实的网络数据。作为演示,这里使用模拟数据就可以了。
还有一个工作,就是如何呈现 SecondCategoryVC。你可以用 presentViewController,也可以用 NavigationController PUSH。假设是后者,那么代码可能是这样的:
GoodsType* goodsType = [GoodsType new];
goodsType.parent = [TypeNode new];
goodsType.parent.categoryName = @"蔬菜";
goodsType.parent.icon = @"1564740275739";
TypeNode *child = [TypeNode new];
child.categoryName = @"根茎类";
child.icon = @"1564740355725";
goodsType.children =@[child];
[self pushSecondCagtegory:goodsType];
这里的 goodsType 仍然是 mock 模拟数据。运行程序,效果如下图所示:
上拉加载也没任何问题:
注意,你可能注意到,当你用自己的代码运行时,cell 中的所有图片(二级分类、商品图片)都不会显示。这并不是 MCCSframework 自身的问题。而是这些图片资源在源代码中并没有提供。如果你仅仅是想看一下效果,那么请搜索 sd_setImageWithURL 关键字,查到到这些地方:
[self.ivImage sd_setImageWithURL:remoteImgAddr(goods.mainImgId) placeholderImage:[UIImage imageNamed:@"商品"]
将 URL 参数从 remoteImgAddr 函数替换为任何你的 APP 能找到的图片资源。
结束
关于 MCCSframework 的第一篇介绍就到此结束了。相信你已经发现了,教程中所用的例子已“接近于”真实案例,许多问题在生产中也是同样存在的。同时框架也提供了大量的实用函数、工具类和分类,这样在真实项目中很多时候程序员只需要编写业务代码即可。