前言:需要构建如下图的UI呈现出来给用户、逻辑业务(1.点击头部需要判断用户等级进行不同的操作2.点击cell可以携带数据进入详情页面3.注销用户时需要请求后台验证信息、以及判断余额是否为0、是否有优惠卷、最后请求后台注销)。
一、网络请求、解析数据提取出来。采用block直接回调model
1.新建PersonalCenterService类,专门处理网络请求以及model回调。model解析使用第三方YYModel。数据结构为两个部分,头部返回一个字典、第二部分为一个数组。备注:数据是自己构建的、此过程只是模拟网络请求过程。
#import "PersonalCenterService.h"
#import "PersonalCenterModel.h"
@implementation PersonalCenterService
+ (void)getPersonalCenterData:(void(^)(PersonalCenterModel *,NSArray *))successBlock
{
//模拟网络请求-后台返回数据
NSDictionary *dictionary = @{
@"head":@{
@"name":@"怪咖君",
@"autograph":@"人生得意须尽欢,莫使金樽空对月",
@"grade":@"青铜",
@"money":@"100",
@"blind":@"2",
@"cardCoupon":@"2",
},
@"rowInfomation":@[
@{@"title":@"支付"},
@{@"title":@"收藏"},
@{@"title":@"卡劵"},
@{@"title":@"用户反馈"},
@{@"title":@"系统设置"},
]
};
//数据解析
NSDictionary *head = [dictionary objectForKey:@"head"];
//头部model
PersonalCenterModel*model = [PersonalCenterModel yy_modelWithDictionary:head];
//section->model利用YYModel解析装入数组、便于操作
NSArray *rowInfomation = [dictionary objectForKey:@"rowInfomation"];
NSArray *dataArray = [NSArray yy_modelArrayWithClass:[personalSecontionModel class] json:rowInfomation];
successBlock(model,dataArray);
}
@end
复制代码
2.controller中、直接回调block、刷新数据
[PersonalCenterService getPersonalCenterData:^(PersonalCenterModel * headModel,NSArray *sectionDataArray) {
//接收数据
self.headModel = headModel;
self.sectionArray = sectionDataArray;
//刷新数据
[self.tableView reloadData];
}];
复制代码
二、采用继承、封装、类目的方式单元化
1.cell子类化
自定义TableViewCell、子视图布局全放到cell中,cellforRow简化代码、不做任何创建子视图以及添加target事件。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = @"TableViewCell";
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[TableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identifier];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.model = self.dataArray[indexPath.section];
return cell;
}
复制代码
这样看起来还是有点多、我们自定义BaseCell、cell创建代码放到BaseCell中。cell相关属性也放到子cell中。之后就变成如下代码、简洁明了。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewCell *cell = [TableViewCell cellWithTableView:tableView];
cell.model = self.dataArray[indexPath.section];
return cell;
}
复制代码
BaseCell大家可以封装如下
/**
* 创建单元格
*/
+ (instancetype)cellWithTableView:(UITableView *)tableView
{
NSString *className = NSStringFromClass([self class]);
NSString *identifier = [NSString stringWithFormat:@"%@",className];
BaseTableViewCell *baseCell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(baseCell == nil){
baseCell = [[self alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
return baseCell;
}
复制代码
2.HeadView子类化
HeadViev我们可以封装成一个UIView子类,当作tableViewHeadView处理就好了。点击事件大家可以用target-action或者代理以及block等处理,这里我用了block、简洁明了。
- (UIView*)headView
{
PersonalCenterView *centerView = [[PersonalCenterView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, 100) clickBlock:^{
//业务操作-根据model判断等级 进行不同的的处理
if ([self.headModel.grade isEqualToString:@"青铜"])
{
[ASRequestHUD showErrorWithStatus:@"您还不具备修改资格,努力升级吧"];
}else if ([self.headModel.grade isEqualToString:@"黄金"])
{
//逻辑处理
}else
{
//钻石
}
}];
centerView.model = self.headModel;
return centerView;
}
复制代码
3.数据展示放到view中
数据展示、复写model的set方法就好(大家常用的方法,apple的MVC架构中model和View是不能通信的)
//headView
- (void)setModel:(PersonalCenterModel *)model
{
self.nameLabel.text = model.name;
self.autographLabel.text = model.autograph;
}
//cell
- (void)setModel:(personalSecontionModel *)model
{
self.titleLabel.text = model.title;
}
复制代码
4.封装工具类
对于字符串判空、还有提示信息可以封装起来。一句话代码调用就好。如下是注销模拟过程。ASRequestHUD以及字符串判空、获取当前控制器等全部封装成工具类。备注:这里用注销过程示例、也是为了体现Controller处理逻辑业务会占用太多代码
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"验证信息" message:nil preferredStyle:UIAlertControllerStyleAlert];
// 添加文本框
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
textField.placeholder = @"请输入姓名";
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
textField.placeholder = @"请输入身份证号码";
}];
UIAlertAction * firstAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action){
UITextField *name= alertController.textFields.firstObject;
UITextField *idCard= alertController.textFields.lastObject;
if ([NSString isEmptString:name.text]&&[NSString isEmptString:idCard.text]) {
[ASRequestHUD showErrorWithStatus:@"信息输入不完整~"];
return ;
}
//网络请求1-假设请求成功 处理业务
//网络请求2-假设请求成功 处理业务
if ([NSString isEmptString:model.money]) {
[ASRequestHUD showErrorWithStatus:@"有余额未体现,请提现后注销"];
return;
}else if ([NSString isEmptString:model.blind])
{
[ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您还有%@张卡未解除,请前往解除绑定",model.blind]];
return;
}else if ([NSString isEmptString:model.cardCoupon])
{
[ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您还有%@张优惠卷未使用,请前往删除或使用",model.cardCoupon]];
return;
}else
{
//网络请求3-注销-假设请求成功
//假设需要刷新数据
[self.tableView reloadUI];
}];
UIAlertAction *secondAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:firstAction];
[alertController addAction:secondAction];
复制代码
三、从Controller中提取UITableView的UITableViewDataSource和UITableViewDelegate
以上通过提取网络请求等一系列方法、Controller是瘦身一些。但由于UITableView协议内容太多、仍然显得臃肿。现在需要将tableView协议剥离出来,控制器就变得干净起来。实现协议最核心的还是数据源问题,所以我们只传入数据源、问题就解决了。
1.定义TableViewData类
#import "TableViewDataSource.h"
#import "TableViewCell.h"
@implementation TableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.dataArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewCell *cell = [TableViewCell cellWithTableView:tableView];
cell.model = self.dataArray[indexPath.section];
return cell;
}
@end
复制代码
2.定义TableViewDelegate类
@interface TableViewDelegate()
@property(nonatomic ,strong)PresenterPesonalCenter *present;
@end
@implementation TableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//逻辑业务处理-进入详情页面
personalSecontionModel *model = self.dataArray[indexPath.section];
PersonalCenterDeatilController *controller = [PersonalCenterDeatilController new];
controller.title = model.title;
[[self jsd_getCurrentViewController].navigationController pushViewController:controller animated:YES];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 50;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 10;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return 0;
}
- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return nil;
}
- (UIView*)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
return nil;
}
@end
复制代码
3.Controller中只要如下代码就好
//初始化协议
tableDataSource = [[TableViewDataSource alloc]init];
tableDelegate = [[TableViewDelegate alloc]init];
//请求数据
[PersonalCenterService getPersonalCenterData:^(PersonalCenterModel * headModel,NSArray *sectionDataArray) {
self.headModel = headModel;
tableDataSource.dataArray = sectionDataArray;
tableDelegate.dataArray = sectionDataArray;
[self.tableView reloadData];
}];
复制代码
四、定义协议、抽取业务逻辑到Prsenter类、controller以及view只负责UI布局以及展示。
经过以上三个步骤之后,Controller已经达到了瘦身的效果。但如果实际开发中业务逻辑太多、Controller仍然还是很胖的。这种情况我们需要把Controller中的业务逻辑提取出到Prsenter类中、Controller专门负责UI展示、布局,不再关心业务操作。这里采用的思想是在MVC基础上扩展的MVP,我们定义专门协议类,让Prsenter类去实现,Controller只要跟Presenter交流就好
1.协议部分
#import <Foundation/Foundation.h>
#import "PersonalCenterModel.h"
NS_ASSUME_NONNULL_BEGIN
@protocol PersonalCenterProtocol <NSObject>
@optional
/***
定义协议、所有逻辑业务都让Present类去处理、controller和view只负责UI布局和数据显示
**/
//注销-prsenter实现
- (void)cancellation:(PersonalCenterModel*)model;
//刷新UI->tableView----->让controller去实现(比较方便-为了清晰、明了也可以单独给controller定义协议类)
- (void)reloadUI;
//点击头部-进入编辑页面-prsenter实现
- (void)clickHead:(PersonalCenterModel*)model;
//cell点击事件-进入详情页面并携带参数-prsenter实现
- (void)enterDetailPage:(personalSecontionModel*)model;
@end
NS_ASSUME_NONNULL_END
复制代码
2.Prsenter处理
实现协议方法、处理业务逻辑。Prsenter就像个中间人,从model获取数据,通知view更新数据。model和view互相独立。
#import "PresenterPesonalCenter.h"
#import "PersonalCenterViewController.h"
#import "PersonalCenterDeatilController.h"
@implementation PresenterPesonalCenter
//实现协议方法 注销账号&&点击头部进入想象
- (void)cancellation:(PersonalCenterModel*)model
{
PersonalCenterViewController *controller = (PersonalCenterViewController*)[self jsd_getCurrentViewController];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"验证信息" message:nil preferredStyle:UIAlertControllerStyleAlert];
// 添加文本框
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
textField.placeholder = @"请输入姓名";
}];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField){
textField.placeholder = @"请输入身份证号码";
}];
UIAlertAction * firstAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action){
UITextField *name= alertController.textFields.firstObject;
UITextField *idCard= alertController.textFields.lastObject;
if ([NSString isEmptString:name.text]&&[NSString isEmptString:idCard.text]) {
[ASRequestHUD showErrorWithStatus:@"信息输入不完整~"];
return ;
}
//网络请求1-假设请求成功 处理业务
//网络请求2-假设请求成功 处理业务
if ([NSString isEmptString:model.money]) {
[ASRequestHUD showErrorWithStatus:@"有余额未体现,请提现后注销"];
return;
}else if ([NSString isEmptString:model.blind])
{
[ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您还有%@张卡未解除,请前往解除绑定",model.blind]];
return;
}else if ([NSString isEmptString:model.cardCoupon])
{
[ASRequestHUD showErrorWithStatus:[NSString stringWithFormat:@"您还有%@张优惠卷未使用,请前往删除或使用",model.cardCoupon]];
return;
}else
{
//网络请求3-注销-假设请求成功
//假设需要刷新数据
if (controller &&[controller respondsToSelector:@selector(reloadUI)]) {
[controller reloadUI];
}
}
}];
UIAlertAction *secondAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:firstAction];
[alertController addAction:secondAction];
[controller presentViewController:alertController animated:YES completion:nil];
}
- (void)clickHead:(PersonalCenterModel*)model
{
if ([model.grade isEqualToString:@"青铜"])
{
[ASRequestHUD showErrorWithStatus:@"您还不具备修改资格,努力升级吧"];
}else if ([model.grade isEqualToString:@"黄金"])
{
//逻辑处理
}else
{
//钻石
}
}
- (void)enterDetailPage:(personalSecontionModel *)model
{
//进入详情页面
PersonalCenterDeatilController *controller = [PersonalCenterDeatilController new];
controller.title = model.title;
[[self jsd_getCurrentViewController].navigationController pushViewController:controller animated:YES];
}
复制代码
3.controller处理
点击事件中、指定让presenter去实现就好
//头部点击事件
if (weakSelf.presenter&&[weakSelf.presenter respondsToSelector:@selector(clickHead:)]) {
[weakSelf.presenter clickHead:weakSelf.headModel];
}
//注销点击
if (self.presenter&&[self.presenter respondsToSelector:@selector(cancellation:)]) {
[self.presenter cancellation:self.headModel];
}
cell中点击进入详情
self.present = [[PresenterPesonalCenter alloc]init];
personalSecontionModel *model = self.dataArray[indexPath.section];
if (self.present&&[self.present respondsToSelector:@selector(enterDetailPage:)]) {
[self.present enterDetailPage:model];
}
复制代码
五、总结
1.通过以上处理、控制器温柔一笑,自言自语道“我终于瘦下来了”,我要去照照镜子了。
#import "PersonalCenterViewController.h"
#import "PersonalCenterView.h"
#import "PersonalCenterService.h"
#import "TableViewDataSource.h"
#import "TableViewDelegate.h"
#import "PresenterPesonalCenter.h"
@interface PersonalCenterViewController ()
{
TableViewDataSource *tableDataSource;
TableViewDelegate *tableDelegate;
}
@property(strong ,nonatomic)PresenterPesonalCenter *presenter;
@property(strong ,nonatomic)UITableView *tableView;
@property(strong ,nonatomic)PersonalCenterModel *headModel;
@property(strong,nonatomic)UIButton *footButton;
@end
@implementation PersonalCenterViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"个人中心";
//初始化协议
tableDataSource = [[TableViewDataSource alloc]init];
tableDelegate = [[TableViewDelegate alloc]init];
//请求数据
[PersonalCenterService getPersonalCenterData:^(PersonalCenterModel * headModel,NSArray *sectionDataArray) {
self.headModel = headModel;
tableDataSource.dataArray = sectionDataArray;
tableDelegate.dataArray = sectionDataArray;
[self.tableView reloadData];
}];
//tableView
[self.view addSubview:self.tableView];
self.presenter = [[PresenterPesonalCenter alloc]init];
}
- (UITableView*)tableView
{
if (!_tableView) {
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, KNavBarHeight, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-KNavBarHeight) style:(UITableViewStyleGrouped)];
_tableView.dataSource = tableDataSource;
_tableView.delegate = tableDelegate;
_tableView.tableHeaderView = [self headView];
_tableView.tableFooterView = [self footView];
}
return _tableView;
}
- (UIView*)headView
{
WeakSelf;
PersonalCenterView *centerView = [[PersonalCenterView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, 100) clickBlock:^{
//操作
if (weakSelf.presenter&&[weakSelf.presenter respondsToSelector:@selector(clickHead:)]) {
[weakSelf.presenter clickHead:weakSelf.headModel];
}
}];
centerView.model = self.headModel;
return centerView;
}
- (UIView*)footView
{
UIButton*(^creatButton)(NSString*,CGRect) = ^(NSString *title,CGRect frame)
{
UIButton *button = [[UIButton alloc] initWithFrame:frame];
button.backgroundColor = K_WhiteColor;
[button setTitle:title forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(cancellation) forControlEvents:UIControlEventTouchUpInside];
return button;
};
UIView *footView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, 100)];
footView.backgroundColor = [UIColor groupTableViewBackgroundColor];
UIButton *footbutton = creatButton(@"注销",CGRectMake(0, 0, kScreenWidth, 50));
footbutton.center = footView.center;
[footView addSubview:footbutton];
return footView;
}
//注销方法
- (void)cancellation
{
if (self.presenter&&[self.presenter respondsToSelector:@selector(cancellation:)]) {
[self.presenter cancellation:self.headModel];
}
}
#pragma mark-实现协议方法
- (void)reloadUI {
//假设需求需要清空数据-更新UI
tableDataSource.dataArray = @[];
[self.tableView reloadData];
}
- (void)dealloc
{
NSLog(@"控制器释放了!!!!!!!!");
}
@end
复制代码
2.Prsenter回之一笑,真是好女不过百也。方天下之英雄,为使君与吾也。Controller发现不再是自己一个人的天下了。
3.MVVM通过双向绑定,也可以使Controller达到瘦身效果。学习成本较前两位高,也存在着缺点。个人认为需要根据实际业务场景、不特别复杂的页面,能用MVC就用MVC、代码简单、明了就好。最后附上结构图。
转载于:https://juejin.im/post/5d004eafe51d4510bf1d665f