MCCSframework 教程(一)介绍

MCCSframework 是什么

在上一篇介绍 MCCS:一种全新的 iOS APP 构建方式中,我们介绍了什么是 MCCS。MCCS 是一种设计模式,它的具体实现是 MCCSframework。

MCCSframework 当前只有 O-C 语言的版本,目前还没有 Swift 版本。
MCCSframework 的目前版本是 0.5.1,它的地址是: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,如下图所示:

MCCSframework 教程(一)介绍_第1张图片

其中:

  1. 在这个 UI 中,有两个列表,上面的横向列表表示肉类下面的二级分类列表:猪肉、牛肉……等等。下面的列表则表示当用户选中某个二级分类时的商品列表,如果用户未选定,则显示整个一级的商品列表。
  2. 这两个列表都有各自的标题,这两个标题,我们可以同一种 cell 绘制,所以都命名为 cell1。
  3. 其中,第一个列表是用的 cell 归成一类,即 cell2。这个列表实际上只有一个 cell,这个 cell 上回包含一个允许横向滚动的 UICollectionView,二级分类实际上是这个 UICollectionView 的 cell,即 cell3。
  4. 第二个列表使用的 cell 属于 cell 4。

这样在这个 UI 中将会涉及到 4 个 UICollectionViewCell 类。划分完 cell,接下来我们来看看这些 cell 需要由几个子控制器来进行管理。

因为子控制器实际上负责替 ViewController 管理屏幕,每个子控制器分别控制器屏幕的一片区域,从视图可知,整个屏幕被我们从上到下划分成了 4 段,因此我们需要 4 个子控制器。

在这里需要注意:

  1. 一个子控制器只能管理一段屏幕,注意,是“段”,而不是“片”,因为这个区域只能横向划分,不能纵向划分,更不能是不规则形状划分。因此只能是矩形,同时,矩形的宽度必须占据屏幕完整宽度。
  2. 划分的范围只能是屏幕的滚动区域。固定的区域,比如头部导航栏和底部 TabBar 不能算在内——这些是 ViewController 负责管理范围,子控制器不能逾越。
  3. 两个列表的标题其实都是非常简单的文字显示,所以两个子控制器可以用同一个子控制器类来展示,使用一个类的两个实例即可。

因此,整个界面需要用到 4 个 Cell 类、3 个子控制器类。

绘制 cell

  1. 显示列表标题的 cell 我们可以使用框架提供的 OneLabelCell 类。

    这个 cell 只包含了一个 UILabel 的属性 lbTitle。

  2. 二级分类的 cell 可以用框架提供的 EmbedCollectionViewCell 类。这个 cell 中嵌套了一个 UICollectionView。

  3. 二级分类的 cell 中嵌套的 UICollectionView 需要使用一种 cell,我们需要创建 Collection View 自身所用到的 cell。

    新建一个 UICollectionViewCell,名为 SecondCategoryCell,勾选 Alson create XIB File。
    打开 SecondCategoryCell,拖入一个 UIImageView 和一个 UILabel:

    MCCSframework 教程(一)介绍_第2张图片

    创建两个 IBOutlet:

    #import 
    
    @interface SecondCategoryCell : NibCollectionViewCell
    @property (weak, nonatomic) IBOutlet UIImageView *ivImage;
    @property (weak, nonatomic) IBOutlet UILabel *lbTitle;
    
    @end
    

    注意,这里继承了框架提供的 NibCollectionViewCell。NibCollectionViewCell 提供了从 xib 文件中实例化一个 UICollectionViewCell 的能力。

  4. 商品列表所用 cell 为 OptimumGoodsCell。OptimumGoodsCell.xib 的内容如下,包含了一个 UIImageView 和 3 个 Label:

    MCCSframework 教程(一)介绍_第3张图片

    OptimumGoodsCell 中创建的 IBOutlet 如下:

    #import 
    
    @interface OptimumGoodsCell : NibCollectionViewCell
    @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 
#import 
#import 


@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 

	@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
  1. 子控制器必须继承框架的 SubController。
  2. 这个子控制器仅仅显示一行占据一定高度的标题文本。所以需要有 title、font、color、height 等属性。分别用于表示文本的内容、字体大小、字体的颜色、行的高度。
  3. leading 属性用于控制文本缩进距离。

OneLabelSC.m:

```
#import 
#import 
#import 

@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 个。

  1. 初始化方法中,我们设置了子控制器的几个属性默认值。
  2. numberOfItems 方法返回一个整型,用于决定这个子控制器中将包含几个 cell。对于标题来说,返回 1 即可。
  3. sizeForItemAtIndex 方法指定每个 cell 的大小。因为这个 cell 实际上占据了整个屏幕宽度,因此我们使用了 SCREEN_WIDTH 宏作为 cell 宽度,这个宏是包含在 头文件中的。cell 的高度即标题的 height 属性所指定。
  4. 在 cellForItemAtIndex 方法中,我们需要提供该 SubController 所管理的 cell 对象。同时配置这些 cell。这里我们配置了 OneLabelCell 的标签文本、字体即颜色等。

SecondCategorySC 子控制器

SecondCategorySC.h:

#import "GoodsType.h" 
#import 

@interface SecondCategorySC : SubController
@property (strong, nonatomic) GoodsType* goodsType;  // 1
@property (strong, nonatomic) void(^subclassSelected)(TypeNode* subclass);// 2

@end

  1. 这个子控制器有一个属性 goodsType,这实际上就是子控制器要渲染的数据模型。该模型是一个实体类 GoodsType,用于表示商品分类,我们会在“模型”一节进行介绍。
  2. 此外还有一个 block 属性,用于处理二级分类被用户选中时的事件。

SecondCategorySC.m:

#import 
#import 
#import "SecondCategoryCell.h"
#import 
#import 
#import 
#import 
#import 
#import "UIViewController+Navigation.h"
#import 

@interface SecondCategorySC()
@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 方法。在这个方法中:

  1. 我们创建了一个 EmbedCollectionViewCell 实例。这个类是框架提供的,它是一种特殊的 cell,仅仅是嵌入了一个 UICollectionView,没有任何其它的 UI。
  2. 创建好 EmbedCollectionViewCell 实例后,我们需要为它的 UICollectionView 注册 一个 cell,设置它的 delegate 和 datasource —— 这里我们将两者都设置为 self。这样我们就可以用子控制器充当 CollectionView 的数据源和委托了。

所以接下来我们要实现UICollectionViewDelegate 协议和 UICollectionViewDataSource 协议。这部分内容和我们在使用 UIKit 中的做法没有什么两样,你应该很熟悉了。

  1. 在 numberOfItemsInSection 方法中,我们根据数据模型中包含的二级分类的个数,来决定要显示几个 cell。
  2. 在 cellForItemAtIndexPath 方法中,我们创建 SecondCategoryCell 对象,并根据数据模型来渲染 cell。
  3. 在 didSelectItemAtIndexPath 方法中,我们通过 block 调用的方式,将用户选择的二级分类数据传递给外界。

OptimumGoodsSC 子控制器

OptimumGoodsSC 负责管理显示商品列表的 cell。类的声明如下:

#import "Goods.h"
#import 

@interface OptimumGoodsSC : SubController
@property (strong, nonatomic) NSMutableArray* goodsArray;
@end

goodsArray 是一个模型数组,它的类型是 Goods 类型,用于表示商品的具体数据。这个类在“模型”一节介绍。

类的实现如下:

#import "OptimumGoodsSC.h"
#import "OptimumGoodsCell.h"
#import 

@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*)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

大部分代码都是和之前的子控制器一样的套路,不同的在于:

  1. init 方法中,我们设置了一个 inset 属性。这个属性顾名思义,是用于设置子控制器管理的区域边距的。
  2. cellForItemAtIndex 方法中,创建了 cell 实例。但是我们并没有逐一配置 cell 的每个属性——因为那样太繁琐,而是调用了 cell 的 configWithObject: 方法,将模型数据传递给 cell,由 cell 自己配置自己。configWithObject: 方法是 Configurable 协议中的方法,而
    OptimumGoodsCell 类实现了这个方法(参考“绘制 cell”一节)。

模型

在子控制器中,我们用到了2个模型类 Goods 和 GoodsType,这些模型和正常 APP 中使用的模型——相对于 MVC 中的模型——没有任何区别。

GoodsType 模型

其实这个模型包含了 2 个类:


#import 

@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 *children;

@end

JKModel 是框架提供的,用于解析 JSON 格式的数据(使用了 JSONModel)。

Goods 模型

这个模型也包含了 2 个类:

#import 
#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* records;
@end

经常使用 JSONModel 的人应该非常熟悉这些代码了,基本上是用工具生成的代码。其中 GoodsPage 是接口返回的分页数据,它主要包含了一个 Goods 数组,此外还包含了从父类 PageModel 继承来的一些属性,比如页码、页大小等。

实现控制器

当 cell、子控制器、模型这些原材料准备好之后,实现控制器是一件非常简单的过程。

控制器的类声明如下:

#import 
#import "GoodsType.h"

@interface SecondCategoryVC : NavBarVC // 1
@property (strong, nonatomic) GoodsType* goodsType; // 2
@end
  1. 继承框架提供的 NavBarVC,这样我们就有一个现成的导航栏可用。
  2. 一个 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];
}

  1. 父类 NavBarVC 中有一个 pageHeader 属性,实际上就是一个导航条控件,我们需要设置它的标题,否则它会默认显示“安装进度”,隐藏它的 rightButton,否则默认会显示一颗“发布”按钮。

  2. 必须设置 CollectionView 的约束(使用 Masonry),否则 CollectionView 不会显示(默认 frame 为 0)。此外,我们还设置了 CollectionView 的上拉和下拉动作,以支持上拉加载和下拉刷新。这需要导入框架提供的 UIScrollView+addMJ.h 分类。

  3. 添加上面实现的 4 个子控制器。其中 labelSC: 方法会创建一个指定标题的单行标题文本(子控制器)。方法实现如下:

    -(OneLabelSC*)labelSC:(NSString*)title{
    OneLabelSC* _labelSC = [OneLabelSC new];
    _labelSC.title = title;
    return _labelSC;
    }
    

    当我们调用 addSC(添加子控制器)、rmSC(删除子控制器)、insSC(插入子控制器) 等方法后,一定不要忘记调用 adapter 的 reloadDataWithCompletion 方法。

  4. 调用 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 模拟数据。运行程序,效果如下图所示:

MCCSframework 教程(一)介绍_第4张图片

上拉加载也没任何问题:

注意,你可能注意到,当你用自己的代码运行时,cell 中的所有图片(二级分类、商品图片)都不会显示。这并不是 MCCSframework 自身的问题。而是这些图片资源在源代码中并没有提供。如果你仅仅是想看一下效果,那么请搜索 sd_setImageWithURL 关键字,查到到这些地方:

[self.ivImage sd_setImageWithURL:remoteImgAddr(goods.mainImgId) placeholderImage:[UIImage imageNamed:@"商品"]

将 URL 参数从 remoteImgAddr 函数替换为任何你的 APP 能找到的图片资源。

结束

关于 MCCSframework 的第一篇介绍就到此结束了。相信你已经发现了,教程中所用的例子已“接近于”真实案例,许多问题在生产中也是同样存在的。同时框架也提供了大量的实用函数、工具类和分类,这样在真实项目中很多时候程序员只需要编写业务代码即可。

你可能感兴趣的:(iPhone开发,MCCS)