iOS 使用MVVM模式实现Cell的点击响应

2017-08-06iOS开发

卷首

最近新工作中用到的RAC+MVVM的开发模式,由于之前都是用MVC,从自己的菜鸡水平感觉这两种设计模式在思想上还是有些微区别的,然后自己也是看了挺多关于这两个模式异同与使用利弊的文章,但是说真的,代码这个东西光看看不出个花来,还是要写出来才能体会的更深,所以我不讲这两种模式的来龙去脉,我也讲不清 ^_^, 要是看过比较多理论上的东西,再结合一下代码理理思路还是极好滴。

目的介绍

上面已经说了,这是一个关于怎样用代码实现mvvm的记录,本来之前就想写一个极其简单的tableview的代码就行了来着,但正好在项目里面遇到一个比较弱鸡的问题,当时思路一下卡住了,就是一个基础知识,但是当时没有想通,这种问题吧,在网上搜都不知道怎么搜,在开发的时候还是比较影响开发效率的,所以回来也把那一点儿东西加上了,其实并没有几行代码,就是想给自己提个醒。

效果图如下


代码介绍

效果图就是上面的,功能比较简单,大牛们肯定看都不带看的那种,哈哈哈,我就是比价喜欢从这种简单功能上弄懂一些东西,简单的都不会,就更不指望去搞高深的了。话不多说,首先看一下主要的代码结构:

这里分了四个文件夹用来存放view/model/controller/viewModel 当然有些有些view也可以放在viewController里面,这个并没有什么严格要求。viewModel主要是用来处理数据逻辑,将model进行处理之后和view/controller进行交互,我理解的它是一个数据加工工厂,这样做的目的也就是避免在controller里面处理大量与界面业务逻辑无关的工作嘛,将数据处理专门用viewModel进行处理。。。(具体的还是参考详细的文章吧,我大概一说)分别说一下各个模块中的代码实现吧,很好理解。

Controller

控制器中就这几行代码,将自定义的mainView进行frame布局,初始化viewModel,是不是看着要比以前养眼了。

- (void)viewDidLoad {    [super viewDidLoad];    self.title = @"第一页";    self.view.

backgroundColor = [UIColor whiteColor];    self.viewModel =

[LGJMainViewModel new];    [self configMainView];}#pragma mark - configView- (void)configMainView {    self.mainView

= [[LGJMainView alloc] initWithViewModel:self.viewModel];

self.mainView.frame = CGRectMake(0, 0, self.view.frame.size.width,

self.view.frame.size.height);    [self.view addSubview:self.mainView];}

LGJMainView

就是我们看到的tableview一个自定义的view,用来“盛放”我们自定义的view,这里贴上来的代码就是将tableview单独放在这个view里面进行出来,当然这里的这些代理方法如果你想使这个view简化还是可以将他们封装出来的,我之前有写过一个对tableview代理方法优化的记录iOS实现UITableViewDataSource与Controller的分离可以参考这个进一步优化,这里面没有什么多说的,就有一点和之前不一样的,就是多了几个self.viewModel的方法,这个我们下面说

#pragma mark - tableView delegate&dataSource- (NSInteger)

numberOfSectionsInTableView:(UITableView *)tableView {

return [self.viewModel getSectionCount];}- (UIView *)tableView:(UITableView *)tableView viewForHeader

InSection:(NSInteger)section {    SectionModel *sectionModel = [self.viewModel getSectionModel

WithSection:section];    UILabel *headerLabel =

[self configHeader];    headerLabel.text = [NSString stringWithFormat:

@"我是第%@个section", sectionModel.sectionName];

return headerLabel;}- (CGFloat)tableView:(UITableView *)tableView heightForHeader

InSection:(NSInteger)section {

return 50;}- (NSInteger)tableView:(UITableView *)tableView numberOfRows

InSection:(NSInteger)section {

return [self.viewModel getCellCountWithIndexPath:section];}- (UITableViewCell *)tableView:(UITableView *)tableView cellFor

RowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *idStr = @"LGJCell";    LGJCell *cell = [tableView cellForRowAtIndexPath:indexPath];

if (!cell) {        cell = [[LGJCell alloc] initWithStyle:UITableView

CellStyleDefault reuseIdentifier:idStr];    }    cell.cellModel = [self.viewModel getRowModelWithIndexPath:

indexPath];

return cell;}- (CGFloat)tableView:(UITableView *)tableView heightForRowAt

IndexPath:(NSIndexPath *)indexPath {

return 50;}- (void)tableView:(UITableView *)tableView didSelectRowAt

IndexPath:(NSIndexPath *)indexPath {    [self.viewModel changeCellModelWithIndexPath:indexPath];    [tableView reloadData];}#pragma mark - coustom tableViewHeader - (UILabel *)configHeader

{    UILabel *headerLabel = [UILabel new];    headerLabel.backgroundColor = [UIColor whiteColor];    headerLabel.font = [UIFont systemFontOfSize:14];    headerLabel.textColor = [UIColor redColor];    headerLabel.textAlignment = NSTextAlignmentCenter;

return headerLabel;}

LGJViewModel

看一下.h文件中,主要就是一些外部需要调用的方法,比如在这里我们使用比较多的就是和tableview代理方法相关的很多方法比较多,如果是在MVC中,那么我们这些数据操作很有可能会写在controller里面,controller里面的内容也就不像我们刚看见的那样简洁了,还有一个就是我在.h文件中声明了一个block,其实用RAC+MVVM开发的话,RAC框架有很多自身封装好的的block也就是Signal供我们使用,也就减少了我们比较容易头疼也比较容易忽略的block的循环引用和内存泄漏,等我再熟悉熟悉RAC再专门去说它吧,这里我们就用block先这样处理,这个不是不可能的。(在这里这个block没有用到,一开始想用来处理一些东西来着,后来没有用,之所以没有删,是想说一下,在mvvm中如果我们没有用RAC框架,我们可以用block来进行一些回调操作)

.h

typedef void(^UpdateCellBlock)(NSIndexPath *indexPath);

@interface LGJMainViewModel : NSObject

@property (nonatomic, copy) UpdateCellBlock updateCellBlock;

- (void)changeCellModelWithIndexPath:(NSIndexPath *)indexPath;

- (RowModel *)getRowModelWithIndexPath:(NSIndexPath *)indexPath;

- (NSInteger)getCellCountWithIndexPath:(NSInteger)section;- (NSInteger)getSectionCount;

- (SectionModel *)getSectionModelWithSection:(NSInteger)section;

在.m文件中我做了一个假数据,用来模拟section和cell中的数据,这个会有用的,就在下面我要说的那个坑。

.m

@interface LGJMainViewModel ()

@property (nonatomic, strong)NSMutableArray *listArr;

//盛放所有model的数组

@end

@implementation LGJMainViewModel

- (instancetype)init {

if (self = [super init]) {        [self configModelArr];    }    return self;}- (void)configModelArr {    self.listArr = [NSMutableArray array];

for (int i = 0; i < 10; i++) {        SectionModel *model = [SectionModel new];        model.sectionName = [NSString stringWithFormat:@"%d", i];

NSMutableArray *mutArr = [NSMutableArray array];

for (int j = 0; j < 20; j++) {            RowModel *rowModel = [RowModel new];            rowModel.name = [NSString stringWithFormat:@"第%d行", j];            rowModel.detail = [NSString stringWithFormat:

@"我是第%d行, 多多指教", j];            [mutArr addObject:rowModel];        }        model.rowModelArr = mutArr;        [self.listArr addObject:model];    }}

这里就是我们在.h文件中看见的那些方法的实现了,在viewModel中对请求的数据或者本地的数据处理之后,返回给外部使用(这里说的不专业了,明白这个道理就好&—— &)

#pragma mark - get CellModel- (RowModel *)getRowModelWithIndexPath:

(NSIndexPath *)indexPath {    SectionModel *secModel = [self.listArr objectAtIndex:

indexPath.section];    NSArray *rowArr = secModel.rowModelArr;    RowModel *rModel = [rowArr objectAtIndex:indexPath.row];

return rModel;}#pragma mark - cell/section Count- (NSInteger)getSectionCount {

return self.listArr.count;}- (NSInteger)getCellCountWithIndexPath:(NSInteger)section {    SectionModel *secModel = [self.listArr objectAtIndex:section];

return secModel.rowModelArr.count;}#pragma mark - get SectionModel- (SectionModel *)getSectionModel

WithSection:(NSInteger)section {    SectionModel *sModel = [self.listArr objectAtIndex:section];

return sModel;}

这个坑就是在这儿,想实现的效果是,当我点击cell的时候,我替换这个cell对应的model数据,一开始是用的被注释掉的方法,这个稍微有些经验的都能想到这个不行,可是我就是那个掉坑的,本来想着我找到对应的section的model,在sectionModel中找到对应的RowModel然后将新model替换掉旧的。perfect。。。运行之后发现是行不通的,然后用下面在数组中遍历查找的方法进行替换解决的。关于这问题我的理解是, 数组中存放的是model的指针,我用newModel替换oldModel,替换的只是model的指针,但是数组中储存model的指针没有改变,所以数组并不会改变它保存的对应位置的指针,所以说数组中对应位置储存的还是oldModel的指针。 这个是我的理解,如果有不对麻烦告知了。

#pragma mark - change Cell Model- (void)changeCellModelWithIndexPath:

(NSIndexPath *)indexPath {//    RowModel *rm = [RowModel new];

//    rm.name = @"新替换的name";

//    rm.detail = @"新替换的detail";//

//    SectionModel *sModel = [SectionModel new];

//    sModel = [self.listArr objectAtIndex:indexPath.section];

//    NSArray *tempCellArr = sModel.rowModelArr;

//    RowModel *rModel = [tempCellArr objectAtIndex:indexPath.row];

//    //    rModel = rm;//    //

if (self.updateCellBlock) {//

self.updateCellBlock(indexPath);//    }    for (int i = 0; i < self.listArr.count; i++) {        SectionModel *sModel = [SectionModel new];

if (i == indexPath.section) {            sModel = [self.listArr objectAtIndex:i];

for (int j = 0; j < sModel.rowModelArr.count; j++) {                RowModel *rModel = [RowModel new];

if (j == indexPath.row) {                    rModel = [sModel.rowModelArr objectAtIndex:j];                    rModel.name = @"替换***";                    rModel.detail = @"我是被替换的新cell";                }            }        }    }}

关于model还是我们以前用的

@interface SectionModel : NSObject

@property (nonatomic, copy) NSString *sectionName;

@property (nonatomic, strong) NSArray *rowModelArr;

@end

总结

写这篇主要是记录一个最基本的mvvm思想的实现还有一个以后绝对不能再犯的错误,遇到问题多想想,别浮躁。晚安,明天还要上班,还有bug等我拯救。。。

demo:https://github.com/irembeu/LGJ_MVVM_TestDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容