UITableView编程之现阶段的问题
做ios开发的同学都知道使用UITableView开发的时候必须实现其dataSource、delegate两个协议,并且需要实现其相应的方法,感觉还算简单。但是随着开发的深入越来越发现几乎每个使用UITableView的页面都需要实现以上2哥协议实现其方法,而且发现每次实现的逻辑大同小异;感觉自己就是代码的搬运工。而且UITableViewCell和UITableView几乎是强绑定关系耦合度太高。由于需要在代理方法中通过indexPath来返回相应的内容因此对以后的业务扩展也不好,哪天产品说这个cell和上面的cell换个位置,这是好可能就需要修改好几处的delegate方法。
以下为传统使用UITableView的代码,真实情况会更加凌乱。
#import "ViewController.h"
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic,copy) NSArray *data;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
// 根据不同的view获取cell
UITableViewCell * cell;
if (indexPath.section == 0) {
if (indexPath.row == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:@""];
cell.textLabel.text = @"";
cell.imageView.image = [UIImage imageNamed:@""];
} else if(indexPath.row == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:@""];
cell.textLabel.text = @"";
cell.imageView.image = [UIImage imageNamed:@""];
} else if (indexPath.row == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:@""];
cell.textLabel.text = @"";
cell.imageView.image = [UIImage imageNamed:@""];
} else if(indexPath.row == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:@""];
cell.textLabel.text = @"";
cell.imageView.image = [UIImage imageNamed:@""];
}
}
return cell;
}
- (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
if (indexPath.row == 0) {
return 70;
} else if(indexPath.row == 1) {
return 80;
} else if (indexPath.row == 0) {
return 90;
} else if(indexPath.row == 1) {
return 100;
}
}
return 0;
}
@end
那么我们再想既然以上代码每个UITableView的页面都有并且逻辑差不多,无非就是根据IndexPath或者其他条件作为判断来返回结果。那么根据软件的设计思路,可不可以把和业务逻辑无关的内容封装起来呢?答案当然是肯定的。那么我们就想了,UITableView无非就是需要给Cell提供数据、高度、位置、处理cell点击等内容,而且这些和业务逻辑确实一点关系都没有。那么我们看看兄弟平台的Android是如何处理类似的问题的呢?如果了解Android的同学可能会很熟悉,Android的ListView,GridView等等类似控件都是使用Adapter提供数据给ListView、GridView使用以解决以上ios的问题。那么我们也可以参照Android的设计思路来设计ios开发。因此就开发了一款类似Android平台下的处理方式的框架,并且山寨其命名叫“CHGAdapter”。废话不多说,我们先看一下的使用。
使用CHGAdapter构建UITableView
ViewController中的写法 只需要给UITableView提供数据模型
#import <UIKit/UIKit.h>
@interface Test1ViewController : UIViewController
@end
#import "Test1ViewController.h"
#import "Test1Model.h"
@interface Test1ViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic,strong) NSArray *data;
@end
@implementation Test1ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.cellDatas = @[self.data];
}
///构造cell的数据,此处模拟 这些数据可以从网络获取
-(NSArray*)data {
if (!_data) {
_data = @[
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试1"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试2"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试3"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试4"]
];
}
return _data;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
Model写法 Test1Model类 Model类必须实现CHGTableViewCellModelProtocol,主要返回Model对应的Cell的一些参数,比如当前Model应该使用哪一个Cell类,当前Cell的高度应该是多少。另外还可以根据需求在Model中计算Cell的高度等操作。
#import <Foundation/Foundation.h>
#import "CHGAdapter.h"
@interface Test1Model : NSObject<CHGTableViewCellModelProtocol>
@property (nonatomic,strong) NSString *cellClassName;
@property (nonatomic,assign) CGFloat cellHeight;
//以下为业务参数
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *otherProperty2;
+(instancetype)initWithCellClassName:(NSString*)cellClassName cellHeight:(CGFloat)cellHeight name:(NSString*)name;
@end
#import "Test1Model.h"
@implementation Test1Model
+(instancetype)initWithCellClassName:(NSString*)cellClassName cellHeight:(CGFloat)cellHeight name:(NSString *)name{
Test1Model * t1 = [Test1Model new];
t1.cellHeight = cellHeight;
t1.cellClassName = cellClassName;
t1.name = name;
return t1;
}
- (NSString *)cellClassNameInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
//如果一个model只对应一个cell 这里可以写死,就不需要外部传入。也可以根据model的某一个字段来判断使用哪一个cell
return self.cellClassName;
}
- (CGFloat)cellHeighInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
return self.cellHeight;
}
@end
UITableViewCell的写法 Test1TableViewCell类 必须继承CHGTableViewCell类
#import "CHGTableViewCell.h"
@interface Test1TableViewCell : CHGTableViewCell
@property (weak, nonatomic) IBOutlet UILabel *name;
@end
#import "Test1TableViewCell.h"
#import "Test1Model.h"
@implementation Test1TableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)cellForRowAtIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView withData:(id)data {
[super cellForRowAtIndexPath:indexPath tableView:tableView withData:data];
Test1Model * t1 = data;
self.name.text = t1.name;
}
@end
运行效果图如下
我们发现TableView的上下有一块空白区域,那个空白区域是tableView的HeaderView和FooterView,框架中默认设置HeaderView和FooterView的高度为30,因此我们可以设置其默认高度,代码如下:
#import "Test1ViewController.h"
#import "Test1Model.h"
@interface Test1ViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic,strong) NSArray *data;
@end
@implementation Test1ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.cellDatas = @[self.data];
//设置默认高度
self.tableView.tableViewAdapter.headerHeight = 0.01;
self.tableView.tableViewAdapter.cellHeight = 50;
self.tableView.tableViewAdapter.footerHeight = 0.01;
}
///构造cell的数据,此处模拟 这些数据可以从网络获取
-(NSArray*)data {
if (!_data) {
_data = @[
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试1"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试2"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试3"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试4"]
];
}
return _data;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
以上代码设置tableView的header、cell、footer的高度:
self.tableView.tableViewAdapter.headerHeight = 0.01;//设置headerView的默认高度如果在model的协议中有设置则优先使用model中返回的
self.tableView.tableViewAdapter.cellHeight = 50;//设置cell的默认高度,如果在model的协议中有设置则优先使用model中返回的
self.tableView.tableViewAdapter.footerHeight = 0.01;//设置footerView的默认高度如果在model的协议中有设置则优先使用model中返回的
设置后运行效果如下:
我们看到只有cell,那么如何添加headerView和footerView呢?请往下看
首先创建header的Model 并且实现CHGTableViewHeaderFooterModelProtocol协议并且实现其required方法
#import <Foundation/Foundation.h>
#import "CHGAdapter.h"
@interface Test1HeaderFooterViewModel : NSObject<CHGTableViewHeaderFooterModelProtocol>
@property (nonatomic,copy) NSString *name;
+(instancetype)initWithName:(NSString*)name;
@end
#import "Test1HeaderFooterViewModel.h"
@implementation Test1HeaderFooterViewModel
+(instancetype)initWithName:(NSString*)name {
Test1HeaderFooterViewModel * t1hf = [Test1HeaderFooterViewModel new];
t1hf.name = name;
return t1hf;
}
- (NSString *)headerFooterClassInTableViw:(UITableView *)tableView section:(NSInteger)section type:(CHGTableViewHeaderFooterViewType)type {
return @"Test1HeaderFooterView";
}
- (CGFloat)headerFooterHeighInTableViw:(UITableView *)tableView section:(NSInteger)section type:(CHGTableViewHeaderFooterViewType)type {
return 30;
}
@end
然后添加TableView的headerView Test1HeaderFooterView类 必须继承CHGTableViewHeaderFooterView类
#import "CHGTableViewHeaderFooterView.h"
#import "CHGAdapter.h"
@interface Test1HeaderFooterView : CHGTableViewHeaderFooterView
@property (nonatomic,weak) IBOutlet UILabel *name;
@end
#import "Test1HeaderFooterView.h"
#import "Test1HeaderFooterViewModel.h"
@implementation Test1HeaderFooterView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (void)headerFooterForSection:(NSInteger)section inTableView:(UITableView *)tableView withData:(id)data type:(CHGTableViewHeaderFooterViewType)type {
[super headerFooterForSection:section inTableView:tableView withData:data type:type];
Test1HeaderFooterViewModel * model = data;
self.name.text = model.name;
}
@end
这里偷懒使用xib。但是需要注意使用xib来关联headerFooterView必须要做如下设置,否则会失败
然后是ViewController中的设置
#import "Test1ViewController.h"
#import "Test1Model.h"
#import "Test1HeaderFooterViewModel.h"
@interface Test1ViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic,strong) NSArray *data;
@property (nonatomic,strong) NSArray *headerData;
@property (nonatomic,strong) NSArray *footerData;
@end
@implementation Test1ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.cellDatas = @[self.data];
self.tableView.headerDatas = self.headerData;
self.tableView.footerDatas = self.footerData;//header和footer的所有元素都可以通用包括model和UI
//设置默认高度
self.tableView.tableViewAdapter.headerHeight = 0.01;
self.tableView.tableViewAdapter.cellHeight = 50;
self.tableView.tableViewAdapter.footerHeight = 0.01;
}
-(NSArray*)headerData {
if (!_headerData) {
_headerData = @[
[Test1HeaderFooterViewModel initWithName:@"header 标题"]
];
}
return _headerData;
}
-(NSArray*)footerData {
if (!_footerData) {
_footerData = @[
[Test1HeaderFooterViewModel initWithName:@"footer 标题"]
];
}
return _footerData;
}
///构造cell的数据,此处模拟 这些数据可以从网络获取
-(NSArray*)data {
if (!_data) {
_data = @[
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试1"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试2"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试3"],
[Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"测试4"]
];
}
return _data;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
运行效果图
上面demo只有一种cell,接下来我们再创建一种类型的Cell。首先创建一个新model明明Test2Model及cell
Test2Model类
#import <Foundation/Foundation.h>
#import "CHGAdapter.h"
@interface Test2Model : NSObject<CHGTableViewCellModelProtocol>
@property (nonatomic,copy) NSString *placeholder;//输入框占位符
@property (nonatomic,copy) NSString *inputText;//输入的内容
+(instancetype)initWithPlaceholder:(NSString*)placeholder inputText:(NSString*)inputText;
@end
#import "Test2Model.h"
@implementation Test2Model
+(instancetype)initWithPlaceholder:(NSString*)placeholder inputText:(NSString*)inputText {
Test2Model * t2 = [Test2Model new];
t2.placeholder = placeholder;
t2.inputText = inputText;
return t2;
}
- (NSString *)cellClassNameInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
//如果一个model只对应一个cell 这里可以写死,就不需要外部传入。也可以根据model的某一个字段来判断使用哪一个cell
return @"Test2TableViewCell";
}
- (CGFloat)cellHeighInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
return 80;
}
@end
Test2TableViewCell类
#import "CHGTableViewCell.h"
@interface Test2TableViewCell : CHGTableViewCell
@property (nonatomic,weak) IBOutlet UITextField *textField;
@end
#import "Test2TableViewCell.h"
#import "Test2Model.h"
@implementation Test2TableViewCell
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)cellForRowAtIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView withData:(id)data {
[super cellForRowAtIndexPath:indexPath tableView:tableView withData:data];
Test2Model * model = data;
self.textField.placeholder = model.placeholder;
self.textField.text = model.inputText;
}
@end
运行效果
以上是运行效果,是不是在想如何把输入框的内容实时的在UIViewController中显示?由于篇幅问题,讲在下一篇文章中详细介绍。
GitHub地址:
码云地址: