UITableView完成动态加载页面,iOS之Cell工厂化解多

引:最近写个项目,自己写的模块有这样的需求:一个页面排列着不同的产品卡(考虑用UITableView写),这些产品卡的顺序、是否显示会根据后台请求的数据,或者当前用户登陆账号的权限来显示,重构了几次代码,最后得到下面的结果:

在开发过程中经常遇到tabView中包含多种样式的cell,这里介绍一种cell工厂模式

首先RAC和MVVM是两个东西,并不是一定要一起用,只不过RAC可以更优雅的实现MVVM的架构

mvc模式的缺点。
1.c层(viewController层),代码冗余,不方便阅读。一般包括以下几点:
1)界面构建。
2)网络数据的请求和后续处理。
3)响应逻辑。比如对点击事件的处理、很多delegate方法。
4)数据源方法。比如常用的tableView会有datasource方法,其中数cellForRowAtIndexpath最为典型,其中数据展示前的代码也是非常的多。
举个例子:在使用tableView时,section、row的判断很头疼,点击时间的if 、switch判断更加让人烦躁。如:

  • 所谓动态是已确定所有卡片的情况下动态
  • 用一个视图模型处理tableView的回调,数据请求逻辑,整个控制器级的逻辑,产品卡模型界面添加等
  • 用一个BaseModel抽象类处理tableView的section级的抽象接口和属性声明(实现在相应子类实现,一个产品卡对应一个子类)
  • 用一个BaseCell抽象类处理tableView的视图与数据关联

下面示例中含有示图的三种cell

MVVM

经历过一阵子的开发之后,当项目越写越大的时候,不难发现,传统的MVC架构显得和笨重,尤其处理复杂页面的时候,控制器里面可以分化出网络层,服务层,应用层,等等,根据每个项目的状况和每个人的理解程度不同,还可以有很多不同的方案,总之你会发现控制器越来越复杂。

再大量的码代码的阶段的过程中,解耦是永恒的问题,每个人对于继承,封装,协议,block等等的理解方式导致每个人的项目中或多或少都有一些倾向,无论是什么技术,设计出来的架构都是为了能让项目更明朗,更易用,易扩展。所以在项目架构设计的时候,无论是什么技术貌似都有利有弊,所以平衡就显得尤为重要,技术虽好,不要贪杯哦。没有最好的架构,只有最适合的架构。

图片 1

*第一步: 根据BaseCell为基类定制相应的产品卡,根据BaseModel为基类定制相应产品卡模型,每个产品卡必须对应一个模型

图片 2

定义MVVM

  • Model:MVVM定义的model倾向于瘦model,尽量让model只是提供数据——模型之间的转换问题
  • View:view层的定义跟MVC中没有什么差别,更加肯定的是,view的作用只是用来提供UI的罗列,并不存在什么有关的逻辑问题,这样也更容易复用。
  • ViewModel:通俗点理解他可以是控制器中的网络层,也可以是tableView的代理,无论他是什么,他都是view和model的桥梁。
  • Controller:这时候的控制器的作用只是用来调度,与其说是调度,不如说是bind。

c层的逻辑处理.png

  • 第二步:使用ViewModel的addSubstanceModelName:方法和addSubstanceCellName:方法将模型和产品卡视图添加入视图模型
  • 第三步:使用ViewModel的方法showOrders:调整产品卡的顺序,按照,自己想要的顺序显示
  • 第四步:请求数据(如果请求数据为空,会将model的一个属性设置为NO,不会对改产品卡进行显示)
  1. 创建model基类BaseModel   和子类 OneModel   TwoModel   ThreeModel

实现

需求

1.MVVM RAC实现列表数据,扩展上拉下拉到基类中,实现复用

2.彻底解耦,View只化UI,可以复用,model只提供数据类型,可以复用

具体实现
先来看看基类,首先是控制器,初始化方法就传入viewmodel,提供三个初始化后调用的接口,也可以根据具体需求修改。

#import "BaseVCProtocol.h"
@class BaseViewModel;
@interface BaseVC : UIViewController<BaseVCProtocol>
/**  初始化视图 */
- (instancetype)initWithViewModel:(BaseViewModel *)viewModel;

/**  初始化后调用的方法 */
- (void)bindInitialization;
- (void)bindNotification;
- (void)bindViewModel;
@end

#import "BaseVC.h"
#import "BaseViewModel.h"
@interface BaseVC ()

/**  viewModel */
@property(nonatomic,strong) BaseViewModel * viewModel;

@end

@implementation BaseVC

#pragma mark - life cycle
  (instancetype)allocWithZone:(struct _NSZone *)zone {
    BaseVC *viewController = [super allocWithZone:zone];

    @weakify(viewController)
    [[viewController
      rac_signalForSelector:@selector(viewDidLoad)]
     subscribeNext:^(id x) {
         @strongify(viewController)
         [viewController bindInitialization];
         [viewController bindNotification];
         [viewController bindViewModel];
     }];

    return viewController;
}
- (instancetype)initWithViewModel:(BaseViewModel *)viewModel {
    self = [super init];
    if (self) {
        self.viewModel = viewModel;
    }
    return self;
}
- (void)viewDidLoad
{
    [super viewDidLoad];
}
- (void)bindInitialization {}
- (void)bindNotification {}
- (void)bindViewModel {}
@end

控制器基类还是比较简单,扩展性强,主要是做一些规范性的接口,接着是tableViewVC,包装一层tableView代理,提供网络请求状态接口方便在子类中处理异常的逻辑

tableViewVC中要依赖于里面的viewModel进行一些通用的设置,例如,基础的tableView代理配置,是否在在子类中选择是否有刷新,刷新信号的绑定,cell点击信号的绑定,数据更新时的刷新

#import "BaseVC.h"

@interface BaseTableViewVC : BaseVC<UITableViewDelegate,UITableViewDataSource>
/**  tableView */
@property(nonatomic,strong) UITableView * tableView;
/**  处理成功回调 */
- (void)dataRequestSuccess:(id)params;
/**  处理错误回调 */
- (void)dataRequestError:(NSError *)error;
/**  处理结束回调 */
- (void)dataRequestFinished;
@end

#import "BaseTableViewVC.h"
#import "BaseTableViewModel.h"
@interface BaseTableViewVC ()
/**  viewModel */
@property(nonatomic,strong) BaseTableViewModel * viewModel;
@end

@implementation BaseTableViewVC
#pragma mark - life cycle
- (instancetype)initWithViewModel:(BaseViewModel *)viewModel{
    self = [super initWithViewModel:viewModel];
    if (self) {
            @weakify(self);
            [[self rac_signalForSelector:@selector(bindViewModel)]
             subscribeNext:^(id x) {
                 @strongify(self);
                 //一开始就刷新
                 if (self.viewModel.isAllowLoadData &&self.viewModel.isLoadDataInitially) {
                     [self.tableView.mj_header beginRefreshing];
                 }
             }];
    }
    return self;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.tableView];
    if (self.viewModel.isAllowLoadData) {
        self.tableView.mj_header = [self setupHeader];
        self.tableView.mj_footer = [self setupFooter];
    }

}
- (void)bindInitialization
{
    [super bindInitialization];
}
- (void)bindNotification
{
    [super bindNotification];
}
- (void)bindViewModel
{
    [super bindViewModel];
    @weakify(self);
    //监听数据源,刷新列表  [RACObserve(self.viewModel,dataSource).distinctUntilChanged.deliverOnMainThread
     subscribeNext:^(id x) {
         @strongify(self);
         [self.tableView reloadData];
     }];
}
#pragma mark - custom
- (void)dataRequestSuccess:(id)params {}
- (void)dataRequestError:(NSError *)error {}
- (void)dataRequestFinished {}
- (MJRefreshNormalHeader *)setupHeader
{
    @weakify(self);
    MJRefreshNormalHeader * setupHeader = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        @strongify(self);
        self.viewModel.pageIndex = 0;
        [self requestRemote];
    }];
    return setupHeader;
}
- (MJRefreshBackNormalFooter *)setupFooter
{
    @weakify(self);
    MJRefreshBackNormalFooter * setupFooter = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
        @strongify(self);
        [self requestRemote];
    }];
    return setupFooter;
}
- (void)requestRemote
{
    @weakify(self);
    [[self.viewModel.requestRemoteDataCommand execute:@(self.viewModel.pageIndex   1)] subscribeNext:^(id  _Nullable x) {
        @strongify(self);
        if ([self respondsToSelector:@selector(dataRequestSuccess:)]) {
            [self dataRequestSuccess:x];
        }
        [self.tableView.mj_header endRefreshing];
        [self.tableView.mj_footer endRefreshing];
    } error:^(NSError * _Nullable error) {
        @strongify(self);
        if ([self respondsToSelector:@selector(dataRequestError:)]) {
            [self dataRequestError:error];
        }
        [self.tableView.mj_header endRefreshing];
        [self.tableView.mj_footer endRefreshing];
    } completed:^{
        @strongify(self);
        if ([self respondsToSelector:@selector(dataRequestFinished)]) {
            [self dataRequestFinished];
        }
        [self.tableView.mj_header endRefreshing];
        [self.tableView.mj_footer endRefreshing];
    }];
}
#pragma mark - UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.viewModel.dataSource.count;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 44.0f;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"%ld-----%ld",indexPath.section,indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    [self.viewModel.cellDidSelect execute:indexPath];
}
#pragma mark - lazy
- (UITableView *)tableView
{
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.backgroundColor = [UIColor whiteColor];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        [self.view addSubview:_tableView];
    }
    return _tableView;
}
- (BaseViewModel *)viewModel
{
    return [[BaseTableViewModel alloc] init];
}
@end

基本的viewModel还是提供最基本的接口供子类重写,并提供一个数据的信号,可以在子类封装出使用的网络请求

#import "BaseViewModelProtocol.h"

@interface BaseViewModel : NSObject<BaseViewModelProtocol>

/**  初始化后调用的方法 */
- (void)viewModelDidLoad;
- (void)viewModelLoadNotifications;
/**  获取数据方法 */
- (RACSignal *)requestRemoteDataSignal;

@end

#import "BaseViewModel.h"

@interface BaseViewModel ()

@end

@implementation BaseViewModel
  (instancetype)allocWithZone:(struct _NSZone *)zone {
    BaseViewModel *viewModel = [super allocWithZone:zone];

    @weakify(viewModel)
    [[viewModel
      rac_signalForSelector:@selector(init)]
     subscribeNext:^(id x) {
         @strongify(viewModel)
         [viewModel viewModelDidLoad];
         [viewModel viewModelLoadNotifications];
     }];

    return viewModel;
}

- (void)viewModelDidLoad {}
- (void)viewModelLoadNotifications {}
#pragma mark - custom
- (RACSignal *)requestRemoteDataSignal
{
    return [RACSignal empty];
}
#pragma mark - lazy
@end

然后为tableView提供一个专门的viewModel,来配置刷新状态,操作刷新的逻辑处理,网络请求,点击事件和刷新的信号发送

#import "BaseViewModel.h"

//默认每次请求显示10条数据
static const NSInteger pageSize = 10;

@interface BaseTableViewModel : BaseViewModel
/**  是否一开始就需要刷新 */
@property (nonatomic,assign) BOOL isLoadDataInitially;
/**  是否允许刷新 */
@property (nonatomic,assign) BOOL isAllowLoadData;
/**  是否允许加载更多 */
@property (nonatomic,assign) BOOL isAllowLoadAdditionalData;
/**  点击cell指令 */
@property (nonatomic,strong) RACCommand * cellDidSelect;
/**  获取更多数据的指令 */
@property (nonatomic,strong) RACCommand *requestRemoteDataCommand;
/**  数据源 */
@property(nonatomic,strong) NSArray * dataSource;
/**  pageIndex */
@property(nonatomic,assign) NSInteger pageIndex;
/**  获取数据方法(重写) */
- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page;
/**  默认处理数据回调的方法(上拉下拉处理数据) */
- (void)dataSourceHandler:(NSArray *)arr;
@end

#import "BaseTableViewModel.h"


@implementation BaseTableViewModel
#pragma mark - life cycle
- (void)viewModelDidLoad {
    [super viewModelDidLoad];

    self.pageIndex = 0;
    self.dataSource = [NSMutableArray array];

    @weakify(self);
    self.requestRemoteDataCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSNumber *page) {
        @strongify(self);
        return [self requestRemoteDataSignalWithPage:page.unsignedIntegerValue];
    }];
    self.cellDidSelect = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSIndexPath *  _Nullable indexPath) {
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [subscriber sendNext:indexPath];
            [subscriber sendCompleted];
            return nil;
        }];
    }];
}
- (void)viewModelLoadNotifications
{
    [super viewModelLoadNotifications];
}
#pragma mark - network
- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page
{
    return [RACSignal empty];
}
#pragma mark - custom
- (void)dataSourceHandler:(NSArray *)arr {
    if (arr && arr.count > 0) {
        if (self.pageIndex == 0) {
            self.dataSource = arr;
        } else {
            self.dataSource = [self.dataSource arrayByAddingObjectsFromArray:arr];
        }
        self.pageIndex   ;
    } else {
        if (self.pageIndex == 0) {
            self.dataSource = @[];
        }
    }
}
#pragma mark - lazy

基于这些基础,扩展起来就很容易,代码也很少。view只需要去布局就好了,model只写数据结构,中间通过一个协议进行赋值,高度如果手动算的话就实现协议

@protocol BaseTableViewCellProtocol <NSObject>
@optional
/**  绑定ViewModel以及赋值 */
- (void)bindWithViewModel:(BaseViewModel *)viewModel fetchDataSource:(NSObject *)dataSource forIndexPath:(NSIndexPath *)indexPath;
/**  计算高度 */
  (CGFloat)cellHeightWithModel:(NSObject *)model;
@end

子类的viewModel只需要重写网络请求信号,并解析

#import "SectionMVVM_ViewModel.h"
#import "SectionMVVM_Model.h"

@implementation SectionMVVM_ViewModel
#pragma mark - life cycle
- (void)viewModelDidLoad
{
    [super viewModelDidLoad];
    self.isAllowLoadData = YES;
}

- (void)viewModelLoadNotifications
{
    [super viewModelLoadNotifications];
}
#pragma mark - network
- (NSArray *)networkHandle
{
    NSDictionary * dic = @{@"image":@"http://pic6.huitu.com/res/20130116/84481_20130116142820494200_1.jpg",
                           @"title":@"百度图片",
                           @"content":@"百度图片使用世界前沿的人工智能技术,为用户甄选海量的高清美图,用更流畅、更快捷、更精准的搜索体验,带你去发现多彩的世界。"};
    NSMutableArray *array = [NSMutableArray array];
    for (NSInteger i = 0; i < 20; i  ) {
        [array addObject:dic];
    }
    return array;
}
/**  获取数据方法(重写) */
- (RACSignal *)requestRemoteDataSignalWithPage:(NSUInteger)page
{
    @weakify(self)
    return [[[[[ZZRequestManager sectionMVVMRequestWithPage:page] map:^id _Nullable(NSArray *  _Nullable listArray) {
        return [NSArray yy_modelArrayWithClass:[SectionMVVM_Model class] json:listArray];
    }] doNext:^(id  _Nullable x) {
        @strongify(self)
        [self dataSourceHandler:x];
    }] doError:^(NSError * _Nonnull error) {
        @strongify(self)
        NSArray * array = [NSArray yy_modelArrayWithClass:[SectionMVVM_Model class] json:[self networkHandle]];

        [self dataSourceHandler:array];
    }] doCompleted:^{

    }];
}
#pragma mark - custom

#pragma mark - lazy
@end

子类控制器如果想自定义什么就重写什么方法,初始化的时候绑定上viewModel的网络请求和点击事件

#import "SectionMVVM_VC.h"
#import "SectionMVVM_Cell.h"
#import "SectionMVVM_ViewModel.h"

@interface SectionMVVM_VC ()
/**  viewModel */
@property(nonatomic,strong) SectionMVVM_ViewModel * viewModel;
@end

@implementation SectionMVVM_VC
#pragma mark - life cycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView registerClass:[SectionMVVM_Cell class] forCellReuseIdentifier:NSStringFromClass([SectionMVVM_Cell class])];
}

- (void)bindViewModel
{
    [super bindViewModel];
    [self.viewModel.requestRemoteDataCommand execute:@(1)];

    [self.viewModel.cellDidSelect.executionSignals.switchToLatest subscribeNext:^(NSIndexPath *  _Nullable indexPath) {
        NSLog(@"%ld--%ld",indexPath.section,indexPath.row);
    }];
}


#pragma mark - customMethod


#pragma mark - reset
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    SectionMVVM_Cell * cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([SectionMVVM_Cell class])];
    [cell bindWithViewModel:self.viewModel fetchDataSource:self.viewModel.dataSource[indexPath.row] forIndexPath:indexPath];
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [tableView fd_heightForCellWithIdentifier:NSStringFromClass([SectionMVVM_Cell class]) cacheByIndexPath:indexPath configuration:^(SectionMVVM_Cell * cell) {
            [cell bindWithViewModel:self.viewModel fetchDataSource:self.viewModel.dataSource[indexPath.row] forIndexPath:indexPath];
    }];
}
#pragma mark - lazy or setter or getter

总结

由于手中现在的项目做的越来越复杂,控制器的代码逐渐增加,从最开始200行到现在400行,实在是无法忍受了(其实看别人的项目,控制器400行我肯定看不下去),再寻找合适框架的时候,顺便学了一下RAC,写个demo巩固一下所学的知识,最近时间比较紧张,可能还有很多地方可以优化,大神勿喷。

demo地址:https://github.com/754340156/RAC-MVVM

不加注释的行为简直受不了!
其次,代码不够灵活,后期难以维护。想要增加、删除的功能时,不仅要修改数据源(self.storeArray)中的数据,还要修改tableView中返回的section,row的数量,还需要修改cellForRow中,view的赋值,和对cell点击事件的处理,感觉无从下手。
最后,c层代码太多,仅仅一个界面就达到了1,2千行左右的代码,寻找很不方便。(尤其是不加注释!)
那么接下来,我们主要介绍下传说的MVVM模式,即Model View ViewModel。其实相比于MVC,重点在于ViewModel,我的理解就是,将我们对传统的Model进行继承和重用。下面通过一个例子来说说。

在BaseModel 中 创建对应model

图片 3

  • 每个model和cell在ViewModel里面根据NSNumber进行定位的,因此在调用showOrders:方法时,需要类似这样传入:[self.viewModel showOrders:@[@,@]];
  • 每个model和cell根据基类进行个性定制,model和cell里面的内容不确定,需要根据需求确定,有必要需要修改ViewModel里面的代码

(instancetype)initWithDictionary:(NSDictionary*)dictionary;

界面.png

根据字典内提供的数据分别创建出其对应的model来获取数据

首先,我们可以将界面根据cell类型,去分几大类。如,该界面中,第一类"地址"cell,第二类"药店"cell,第三类''其他''cell(都的左侧、右侧都是文本型)。
这样我们就可以就只需要3个model,来分别储存在3中cell的数据。但是这3中model也有很多通用的数据,如cell的种类,cell的高度,cell是否要显示,cell是否能点击等等。这就需要一个基类,来存放这些通用的属性。上述的3个model来继承这个基类,来实现model的重用。
如:我们通过建立SubmitOrderBaseModel为基类

(instancetype)initWithDictionary:(NSDictionary*)dictionary {

@interfaceSubmitOrderBaseModel :NSObject
/**
cell高度
*/
@property(nonatomic,assign)CGFloatcellHeight;
/**
cell类型
*/
@property(nonatomic,copy)NSString* type;
/**
model名称
*/
@property(nonatomic,copy)NSString* title;
/**
是否显示
*/
@property(nonatomic,assign)BOOLisShow;
/**
是否能点击
*/
@property(nonatomic,assign)BOOLisSelected;
/**
子model
*/
@property(nonatomic,strong)NSMutableArray*models;
@end

先使用当前类(父类)创建出model对象

一定要加注释!
"地址"cell,SubmitOrderAddressModel,继承SubmitOrderBaseModel

BaseModel*model =nil;

@interface SubmitOrderAddressModel : SubmitOrderBaseModel

@end

根据字典中key对应的数据初始化不同的子类对象并将其返回给我们的父类

"药店"cell,SubmitOrderShopModel,继承SubmitOrderBaseModel

if([dictionary[@"tag"]isEqualToString:@"Top News"]) {

@interface SubmitOrderShopModel : SubmitOrderBaseModel

@end

model = [[OneModelalloc]init];

其他cell,SubMitOrderNormalModel,继承SubmitOrderBaseModel

}elseif([dictionary[@"tag"]isEqualToString:@"imgextra"]) {

@interface SubMitOrderNormalModel :SubmitOrderBaseModel

/**
 右侧文字输入方向
 */
@property(nonatomic,strong)NSString *rightTextAlignment;

/**
 右侧字体颜色
 */
@property(nonatomic,strong)UIColor *rightColor;
@end

model = [[TwoModelalloc]init];

''其他''cell中又有写微小的区别,比如右侧文本的颜色和输入方向,这是''其他''cell中的独有属性。
好,现在我们可以在ViewController中,根据界面的需求来处理数据。在上面的''界面''中,我们分析了需要3种model来储存数据,但是他根据业务逻辑分了6个(其实是大于6个,一个药店一个section)section。这样的话,可以根据section的个数来处理数据。创建个一个大数组(orderTypeArray),其中储存了我们所要展示的所有数据,他的个数就是section的个数。
在处理数据时,我遇到的问题是,“其他”cell,type都是一种属性,你要在models属性中添加要展示的cell数据,封装成一种数据格式,比如我封装成字典以"NAME"为key,方便赋值时使用。
整理后的数组大概是这个样子的:

}elseif([dictionary[@"tag"]isEqualToString:@"music"]) {

图片 4

model = [[ThreeModelalloc]init];

处理后的数据.png

}

现在拿到数组后,就可以去赋值了。首先我们把tableView的delegate拿出来,独立成一个类,然后在tableView设置代理和数据源时,绑定在个类。

[modelsetValuesForKeysWithDictionary:dictionary];

    _delegate = [[SubmitOrderTableViewDelegate alloc] init]; 
    _mainTableView.delegate =_delegate;
    _mainTableView.dataSource =_delegate;

returnmodel;

要记得在SubmitOrderTableViewDelegate.h中遵守UITableViewDataSource,UITableViewDelegate。
然后我们就可以在SubmitOrderTableViewDelegate.m中实现要展示的数据了。
1) 在numberOfSectionsInTableView方法中,返回当然是数组(orderTypeArray)中model的个数了。

}

    return _orderTypeArray.count;

//保护方法

2)在numberOfRowsInSection方法中,返回的是每个model中models中的对象个数。

-(void)setValue:(id)value forUndefinedKey:(NSString*)key {

    SubmitOrderBaseModel *model =_orderTypeArray[section];

    if ([model.type isEqualToString:@"OrderAddressTableViewCell"] || [model.type isEqualToString:@"GoodsShopTableViewCell"] )//地址、药品只返回1
    {
        return 1;
    }
    return model.models.count;

}

3)在heightForRowAtIndexPath方法中,返回的是我们model.cellHeight参数。

  1. 在viewDidLoad获取数据
    SubmitOrderBaseModel *model =_orderTypeArray[indexPath.section];

    CGFloat height =0;

    if (!model.cellHeight)
    {
        SubmitOrderBaseModel *subModel =   model.models[indexPath.row];

        if (subModel.isShow)                //是否显示
        {
            height = subModel.cellHeight;
        }
        else
        {
            height = 0;
        }
    }
    else
    {
        height = model.cellHeight;          //地址、药品cell
    }
    return height;

self.dataArr= [[NSMutableArrayalloc]init];

这里需要注意的是"是否显示cell"这个属性。
现在来到关键的cellForRowAtIndexPath方法。

//根据文件路径获取数据

    NSString *cellType =@"";
    SubmitOrderBaseModel *data = _orderTypeArray[indexPath.section];
    SubmitOrderBaseModel *subData = data.models[indexPath.row];
    if (!data.type) //地址、药品Type
    {
        cellType = subData.type;
    }
    else
    {
        cellType = data.type;
    }

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellType];
    if (cell ==nil)
    {
        cell = [[NSClassFromString(cellType) alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellType];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

NSArray*array = [NSArrayarrayWithContentsOfFile:[[NSBundlemainBundle]pathForResource:@"data"ofType:@"plist"]];

首先根据cellType来创建cell。然后根据cell的类型(也就是cellType的类型)来为指定的cell赋值。
比如,当前cell是''地址''cell

//使用字典遍历数组内的所有数据

     if ([cell isKindOfClass:[OrderAddressTableViewCell class]])
     {
       ((OrderAddressTableViewCell *)cell).accessoryType = UITableViewCellAccessoryDisclosureIndicator;

        ((OrderAddressTableViewCell *)cell).nameLabel.text = ((NSDictionary*)subData)[@"RECEIVER"];
        ((OrderAddressTableViewCell *)cell).phoneLabel.text = ((NSDictionary*)subData)[@"CONTACTPHONE"];
        ((OrderAddressTableViewCell *)cell).addressLabel.text = ((NSDictionary*)subData)[@"ADDRESS"];
    }

for(NSDictionary*dictinarray) {

这些都是简单的为控件赋值。
在比如,“其他”cell

BaseModel*model = [BaseModelinitWithDictionary:dict];

     if ([cell isKindOfClass:[ActionCell class]])
      {
        if (((SubMitOrderNormalModel*)subData).isShow)
        {
            ((ActionCell *)cell).hidden = NO;
        }
        else
        {
            ((ActionCell *)cell).hidden = YES;
        }
        if (((SubMitOrderNormalModel*)subData).isSelected)
        {
            ((ActionCell *)cell).accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        }
        else
        {
            ((ActionCell *)cell).accessoryType = UITableViewCellAccessoryNone;
        }

        ((ActionCell *)cell).titleLab.text = ((SubMitOrderNormalModel*)subData).title;          //标题

        NSDictionary *infoDic =((SubMitOrderNormalModel*)subData).models[0];                    //详情信息
        ((ActionCell *)cell).actionLab.text = infoDic[@"NAME"];

        if (((SubMitOrderNormalModel*)subData).rightColor)                                      //详情字体颜色
        {
            ((ActionCell *)cell).actionLab.textColor= ((SubMitOrderNormalModel*)subData).rightColor;
        }
        else
        {
            ((ActionCell *)cell).actionLab.textColor= [UIColor blackColor];
        }

        if ([((SubMitOrderNormalModel*)subData).rightTextAlignment isEqualToString:@"left"])    //详情信息输入方向
        {
            ((ActionCell *)cell).actionLab.textAlignment = NSTextAlignmentLeft;
        }
        else
        {
            ((ActionCell *)cell).actionLab.textAlignment = NSTextAlignmentRight;
        }

//将不同子类创建出的model对象添加到我们的数组当中

到这里可以看出,我们并没有通过 section和row的顺序来为控件赋值,而是通过我们传入的数据来映射到特定的cell。
然后是点击事件,这里我们需要把点击的indexPath,和model传出去进行处理就可以了。

[self.dataArraddObject:model];

到这里,大家可能会对MVVM有了一定的认识,这种通过只对数据源处理,就可以展示我们界面的内容。极大的增加了代码的灵活性,在以后维护时,我们要想改变cell的展示顺序,或者新增、删除某个cell,我们都可以通过对数据源(即orderTypeArray)的存储顺序,增加、删除某条数据就可以了。

}

  1. cell基类BaseModelCell  和子类OneModelCell   TwoModelCell   ThreeModelCell  (注意cell的名字和model的关联,下面要使用)

根据不同类型的model创建出不同的cell

(instancetype)initWithModel:(BaseModel*)model;

(instancetype)initWithModel:(BaseModel*)model

{

//根据我们的OC函数获取我们的model类名并将其转化为OC字符串

NSString*modelName = [NSStringstringWithUTF8String:object_getClassName(model)];

//使用model的类名拼接一个"Cell"来获取到我们的Cell类名

NSString*cellName = [modelNamestringByAppendingString:@"Cell"];

//根据我们所提供的cellName来获取其对应的“cell子类”初始化一个cell对象返回给我们的父类对象

//唯一标识符可以使用我们所提供的model来给予不同cell所对应的标识来重用。

BaseTableViewCell*cell = [[NSClassFromString(cellName)alloc]initWithStyle:(UITableViewCellStyleDefault)reuseIdentifier:modelName];

returncell;

}

同时,在父类中声明出一个BaseModel对象,在其子类里重写set方法,在set方法内部去做赋值的操作  在子类完成对应视图布局

@property(nonatomic,strong)BaseModel*baseModel;

注意:

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {

//根据我们的indexPath.row获取我们对应的model

BaseModel*baseModel = [self.dataArrobjectAtIndex:indexPath.row];

//根据取出来的model获取其对应的类名

NSString*modelName = [NSStringstringWithUTF8String:object_getClassName(baseModel)];

//根据不同的唯一标示重用不同的cell

BaseTableViewCell*cell = [tableViewdequeueReusableCellWithIdentifier:modelName];

//如果我们的cell不存在

if(cell ==nil) {

//根据我们每行提供的model创建出对应的cell

//根据不同需求生产不同的产品

cell = [BaseTableViewCellinitWithModel:baseModel];

}

[cellsetBaseModel:baseModel];

returncell;

}

本文由星彩网app下载发布于计算机编程,转载请注明出处:UITableView完成动态加载页面,iOS之Cell工厂化解多

TAG标签: 星彩网app下载
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。