BaseTableView的简单使用
现在来说说另一个我封装比较重的UI组件。这个组件要实现什么效果呢?
当你使用继承了BaseTableView的子类tableView后,只需要在使用这个类的控制器中传递给tableView一个数据数组,并实现下拉刷新或者上拉加载的代理方法就可以了。你就自动实现了下拉刷新,上拉加载,以及刷新数据为空等的处理。
这样说可能比较笼统,我说说我的实现思路可能会便于理解。
一般来说使用tableView的地方我们都是加载一些数据,常用的就是下拉刷新和上拉加载。稍稍复杂些我们可能需要处理下比如上拉加载完成后的提示,以及下拉没有数据时的处理。下拉没有数据时展示的地方如果没有数据空白的一片可能体验不太好,所以我们可以添加一个图片或者提示告知用户没有数据。
#import <UIKit/UIKit.h>
@class BaseTableView;
/**
* 下拉,上拉,选择的代理
*/
@protocol UITableViewEventDelegate <NSObject>
@optional
/**
* 下拉刷新
*
* @param tableView 传入的tableView
*/
- (void)pullDown:(BaseTableView *)tableView;
/**
* 上拉加载
*
* @param tableView 传入的tableView
*/
- (void)pullUp:(BaseTableView *)tableView;
/**
* 选中cell的代理
*
* @param tableView 传入的tableView
* @param indexPath 选中的坐标位置
*/
- (void)tableView:(BaseTableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
@end
@interface BaseTableView : UITableView<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic, assign)BOOL isDiyRefresh; // 是否自定义
@property (nonatomic,assign)BOOL isRefreshHeader;//是否需要下拉效果
@property (nonatomic,strong)NSArray *data;// 为tableView提供数据
@property (nonatomic,assign)id<UITableViewEventDelegate> eventDelegate;// 下拉,上拉事件代理
@property (nonatomic,assign)BOOL isMoreInfo;// 是否有更多数据,用来控制是否需要上拉加载功能
@property (nonatomic,strong)UILabel *footLabel;// 上拉加载时底部显示的标签提示
@property (nonatomic,assign)BOOL isNOData;// 没有数据
/**
* 自定义的上拉
*
*
*/
- (id)initWithFrame:(CGRect)frame and:(BOOL)isDiyMoreRresh;
/**
* 停止下拉刷新动画
*/
- (void)doneLoadingTableViewData;
/**
* 滚动栏滚动到最底部
*
* @param animated 是否开启动画
*/
- (void)scrollToBottomWithAnimated:(BOOL)animated;
#import "BaseTableView.h"
#import "MJRefresh.h"
#import "DiyRefreshHeader.h"
@interface BaseTableView()
{
UILabel *_noDataLab;// 没有数据的提示
UIImageView *_noDataImageV;// 没有数据的图片
MJRefreshHeader *header;//下拉刷新的头文件
}
@end
@implementation BaseTableView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.dataSource = self;
self.delegate = self;
[self initView];
}
return self;
}
- (id)initWithFrame:(CGRect)frame and:(BOOL)isDiyMoreRresh
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
_isDiyRefresh = isDiyMoreRresh;
self.dataSource = self;
self.delegate = self;
[self initView];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
self.dataSource = self;
self.delegate = self;
[self initView];
}
#pragma mark 懒加载一下data
- (NSArray *)data
{
if (!_data) {
_data = [NSArray array];
}
return _data;
}
- (void)initView
{
// 设置回调(一旦进入刷新状态,就调用target的action,也就是调用self的loadNewData方法)
header = [DiyRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(pullDown)];
//-----------------------上拉加载-----------------
self.mj_footer = [MJRefreshAutoGifFooter footerWithRefreshingBlock:^{
// 进入刷新状态后会自动调用这个block
[self.eventDelegate pullUp:self];
}];
if (_isDiyRefresh) {
MJRefreshAutoGifFooter *footer = [MJRefreshAutoGifFooter footerWithRefreshingTarget:self refreshingAction:@selector(pullUp)];
footer.isDiyRefresh = _isDiyRefresh;
NSMutableArray *imageArr = [NSMutableArray array];
for (int i = 1; i<=24; i++) {
UIImage *iamge = [UIImage imageNamed:[NSString stringWithFormat:@"%d",i]];
[imageArr addObject:iamge];
}
[footer setImages:imageArr duration:1 forState:MJRefreshStateRefreshing];
footer.refreshingTitleHidden = YES;
self.mj_footer = footer;
}else
{
MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(pullUp)];
// 设置字体
footer.isDiyRefresh = _isDiyRefresh;
footer.stateLabel.font = [UIFont systemFontOfSize:14];
// 设置颜色
footer.stateLabel.textColor = [UIColor hexColor:@"F4F1F2"];
footer.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite;
footer.refreshingTitleHidden = NO;
self.mj_footer = footer;
}
//默认关闭上拉加载以及开启上拉刷新
self.isMoreInfo = NO;
self.refreshHeader = YES;
self.separatorStyle = UITableViewCellSeparatorStyleNone;
}
#pragma mark 是否开启下拉刷新功能
- (void)setRefreshHeader:(BOOL)refreshHeader
{
_isRefreshHeader = refreshHeader;
if (_isRefreshHeader) {
self.mj_header = header;
}else{
self.mj_header = nil;
}
}
#pragma mark 隐藏或显示没有数据的提示图片
- (void)setIsNOData:(BOOL)isNOData
{
_isNOData = isNOData;
_isMoreInfo = NO;
if (_isNOData) {
[self initNoDataImage];
_noDataImageV.hidden = NO;
_noDataLab.hidden = NO;
//将tableView上的数据清除并刷新
self.data = @[];
[self reloadData];
}else
{
_noDataImageV.hidden = YES;
_noDataLab.hidden = YES;
}
}
#pragma mark 是否开启上拉加载动画
- (void)setIsMoreInfo:(BOOL)isMoreInfo
{
_isMoreInfo = isMoreInfo;
if (_isMoreInfo) {
//显示上拉加载动画
self.mj_footer.hidden = NO;
}else
{
// 隐藏上拉加载动画
self.mj_footer.hidden = YES;
}
}
#pragma mark 关闭下拉加载
- (void)doneLoadingTableViewData{
// 拿到当前的下拉刷新控件,结束刷新状态
[self.mj_header endRefreshing];
[self.mj_footer endRefreshing];
}
#pragma mark ----------------------UITableViewDelegate---------------------
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
#pragma mark - 初始化没有数据的图片
- (void)initNoDataImage
{
if (_noDataImageV == nil || _noDataLab == nil)
{
_noDataImageV = [[UIImageView alloc] initWithFrame:CGRectZero];
_noDataImageV.image = [UIImage imageNamed:@"空哒"];
_noDataImageV.frame = CGRectMake((self.viewController.view.width-_noDataImageV.image.size.width)/2.0, (self.viewController.view.height-_noDataImageV.image.size.height)/2.0-48, _noDataImageV.image.size.width, _noDataImageV.image.size.height);
_noDataImageV.hidden = YES;
[self addSubview:_noDataImageV];
_noDataLab = [[UILabel alloc]initWithFrame:CGRectMake((self.viewController.view.width-200)/2.0, _noDataImageV.bottom-30, 200, 100)];
_noDataLab.text = @"空哒";
_noDataLab.textAlignment = NSTextAlignmentCenter;
_noDataLab.textColor = [UIColor colorWithHexString:@"bab4b8"];
_noDataLab.font = [UIFont systemFontOfSize:13];
_noDataLab.hidden = YES;
[self addSubview:_noDataLab];
}
}
#pragma mark 下拉刷新
- (void)pullDown
{
// 2.模拟0.1秒后刷新表格UI(真实开发中,可以移除这段gcd代码),防止请求过快,看不到等待动画
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//停止加载,弹回下拉
[self.eventDelegate pullDown:self];
});
}
#pragma mark 上拉加载
- (void)pullUp
{
// 2.模拟0.1秒后刷新表格UI(真实开发中,可以移除这段gcd代码),防止请求过快,看不到等待动画
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//停止加载,弹回下拉
[self.eventDelegate pullUp:self];
});
}
#pragma mark - 滚动栏滚动到最底部
- (void)scrollToBottomWithAnimated:(BOOL)animated
{
if ([self numberOfSections] > 0) {
NSInteger lastSectionIndex = [self numberOfSections] - 1;
NSInteger lastRowIndex = [self numberOfRowsInSection:lastSectionIndex] - 1;
if (lastRowIndex > 0) {
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
[self scrollToRowAtIndexPath:lastIndexPath atScrollPosition: UITableViewScrollPositionTop animated:animated];
}
}
}
#pragma mark - 滚动到顶部
- (void)scrollToTopWithAnimated:(BOOL)animated
{
if ([self numberOfSections] > 0 && [self numberOfRowsInSection:0] > 0) {
[self scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:animated];
}
}
代码篇幅不多,也比较好理解,代码可在项目中的BaseTableView中查阅,我现在来说明下使用方法,看过后便大致知道方便的地方在哪了。
1. BaseTableView的引入
tableView基本页面中都会使用到,所以我便直接在BaseViewController中进行实现。使用继承自BaseViewController的控制器时同时也相当于实现了tableView。
在BaseViewController中实现协议UITableViewEventDelegate
@interface BaseViewController : UIViewController<RequestDataDelegate,UITableViewEventDelegate>
在BaseViewController定义一个baseTableView对象作为使用
@interface BaseViewController ()
{
MBProgressHUD *hud; //!< hud第三方动画
BaseTableView *_baseTableView;//!< 当子控制器用到baseTableView时,用此变量控制上拉下拉动画的结束
BOOL _isPullDown;//!< 是否是下拉刷新
// BaseScrollView *_baseScrollView;//!< 当子控制器用到baseTableView时,用此变量控制上拉下拉动画的结束
NSLayoutConstraint *hudBottomVToTopConstraint;//!<加载hud层距离顶部的距离
NSLayoutConstraint *requestFailVToTopConstraint;//!<网络请求失败的view距离顶部的距离
BOOL isCanReducePageNum;//是否可以减少pageNum在上拉失败时的页数
}
正如我在BaseViewController中介绍的,我这添加了上拉加载的计数器,所以每个方法中做了是上拉加载还是下拉刷新的判断。不论是上拉加载还是下拉刷新,我们都将代理传过来的tableView引用给ViewController中我们定义的baseTableView对象,方便我们在ViewController中网络请求成功或失败后关闭刷新动画。
这几个UITableViewEventDelegate协议的实现都只是简单做好网络前的准备,具体的实现我们需要对应不懂的子类ViewController中具体实现,现在可以操作第二步了。
2. 为tableView生成对应的cell和model
在homeViewController中进行实现,对应的文件夹下我们生产一个对应继承自baseTableView的HomeTable.
每个tableView展示的内容都是不一样的,所以我们需要与之对应的cell界面以及对应在cell中展示的数据model,Mode的详解后面说明,先参照项目代码完成tableView的数据展示
我们需要在HomeTable中实现的就是将获取到的数据赋予我们对应的cell,cell进行数据的展示,如果需要实现点击对应的栏跳跃到下一个界面,我么也可以直接在此实现tableView中的didSelectRowAtIndexPath方法。
最终生成结果如下
3. 模拟下拉刷新和上拉加载
因为暂时还没说到数据mod以及网络请求的封装,所以我们这暂时不做真实的网络请求,只简单的模拟下网络请求开始,失败以及结束的不同情况实现。
在homeViewController的控制器中拖动一个homeTableView类型的组件
代码内实现参见项目代码
#import "HomeViewController.h"
#import "HomeTable.h"
#import "HomeModel.h"
@interface HomeViewController ()
@property (weak, nonatomic) IBOutlet HomeTable *tableView;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = @"首页";
//1.实现requestDataDelegate网络请求的几个个基本协议requestData,requestDataAgain
//2.实现UITableViewEventDelegate的协议,将tableView传递给父类
//3.在下拉刷新里面调用网络请求requestData
[self requestData];
}
#pragma mark -------------------------requestDataDelegate-------------------------
#pragma mark 请求网路
- (void)requestData
{
[super requestData];
NSMutableArray *muArray = [NSMutableArray array];
//做一个假的数据数组
for(int i = 0; i<20; i++){
HomeModel *model = [[HomeModel alloc]init];
model.name = [NSString stringWithFormat:@"王%d",i];
model.school = [NSString stringWithFormat:@"第%d中学",i];
[muArray addObject:model];
}
//延迟3s模拟网络请求过程
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//将请求获取到的值赋予tableView并刷新,并设置此类为代理
self.tableView.data = muArray;
self.tableView.eventDelegate = self;
[self.tableView reloadData];
//如果需要实现上拉加载,则只需要
//self.tableView.isMoreInfo = true;
//当我们判断服务器还有数据可以加载时便可开启此开关
//调用父类写好的网络请求成功方法requestSuccess
//如果需要自定义请求成功后的处理则可在此类中覆盖requestSuccess,暂时不模拟网络失败的情况
[self requestSuccess];
});
}
#pragma mark 重新请求网络
- (void)requestDataAgain
{
[super requestDataAgain];
[self requestData];
}
#pragma mark--------------------------------UITableViewEventDelegate
#pragma mark 下拉刷新
- (void)pullDown:(BaseTableView *)tableView
{
[super pullDown:self.tableView];
[self requestData];
}
//#pragma mark 上拉加载 根据情况是否需要有上拉加载
//- (void)pullUp:(BaseTableView *)tableView
//{
//
// [super pullUp:self.tableView];
//}
这次数据的展示基本可以看出如何实现mvc的分层了。
- 我们在ViewController中获取到数据并转化为model,如果需要处理数据我们在model中处理即可
- 将model值传递给tableView
- tableView自动将model给对应的cell,直接在cell中引用model值
- 关于一些tableView上的操作如跳转,删除等我们在tableView中进行实现
以上也可以看出ViewController成了中转站,在这里有model,有view,他的作用便只需要将对应的model给对应的view就行了,本身不再对数据和界面进行操作,要修改ui只需要进对应的storyboard中的控制器或者对应的cell的xib文件修改就行了。
操作上的处理也单独抽出来,和table有关的在tableView中处理,和cell有关的在cell中处理。
基本所有展示数据的界面都是这个套路,所以团队在实现时引用这种套路会非常的方便与统一。成员写的代码一看即懂,和自己写的都是一个模板样。