![](https://img.haomeiwen.com/i1692043/55350f5afeb2361d.jpg)
什么是无数据占位图?
无数据占位图,就是当后台返回的数据源为空时需要展示的页面,比如下面这三张:
![](https://img.haomeiwen.com/i1692043/526f6f420f47c674.png)
为什么我们需要无数据占位图?
一般来说,tableView获取到的数据源为空时,直接展示一个空的tableView显得比较突兀,所以设计师往往会针对这种情况给出相应的UI,用来替代空tableView的展示。
思路:
无数据占位图是一个view,一个覆盖在tableView上的自定义view,这个view可以响应单击手势。
实现方法
要实现这个功能,比较简单,但是,我们的目标是:没有蛀牙(没有蛀牙的意思就是不仅完美实现功能,并且使用非常方便,而且易于修改和维护🙄)。
![](https://img.haomeiwen.com/i1692043/28528a6164fe6ec7.jpg)
-
第一步:封装无数据占位图
这是一个自定义view,根据设计图搭建UI即可,需要注意的是:在一个项目中,无数据占位图往往不止一种(无订单、无收藏的商品、无收获地址。。。),但是它们的布局往往很相似,所以可以通过传入不同的值创建不同的UI。
自定义无数据占位图NoContentView
:
.h文件:
#import <UIKit/UIKit.h>
// 无数据占位图的类型
typedef NS_ENUM(NSInteger, NoContentType) {
/** 无网络 */
NoContentTypeNetwork = 0,
/** 无订单 */
NoContentTypeOrder = 1
};
@interface NoContentView : UIView
/** 无数据占位图的类型 */
@property (nonatomic,assign) NSInteger type;
@end
.m文件
#import "NoContentView.h"
#import "UIColor+Util.h"
#import "Masonry.h"
@interface NoContentView ()<UIScrollViewDelegate>
@property (nonatomic,strong) UIImageView *imageView;
@property (nonatomic,strong) UILabel *topLabel;
@property (nonatomic,strong) UILabel *bottomLabel;
@end
@implementation NoContentView
#pragma mark - 构造方法
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
// UI搭建
[self setUpUI];
}
return self;
}
#pragma mark - UI搭建
/** UI搭建 */
- (void)setUpUI{
self.backgroundColor = [UIColor whiteColor];
//------- 图片 -------//
self.imageView = [[UIImageView alloc]init];
[self addSubview:self.imageView];
//------- 内容描述 -------//
self.topLabel = [[UILabel alloc]init];
[self addSubview:self.topLabel];
self.topLabel.textAlignment = NSTextAlignmentCenter;
self.topLabel.font = [UIFont systemFontOfSize:15];
self.topLabel.textColor = [UIColor colorWithHexString:@"484848"];
//------- 提示点击重新加载 -------//
self.bottomLabel = [[UILabel alloc]init];
[self addSubview:self.bottomLabel];
self.bottomLabel.textAlignment = NSTextAlignmentCenter;
self.bottomLabel.font = [UIFont systemFontOfSize:15];
self.bottomLabel.textColor = [UIColor colorWithHexString:@"484848"];
//------- 建立约束 -------//
[self.imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self);
make.centerY.mas_offset(-100);
make.size.mas_equalTo(CGSizeMake(100, 100));
}];
[self.topLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.imageView.mas_bottom).mas_offset(10);
make.left.right.mas_offset(0);
make.height.mas_equalTo(20);
}];
[self.bottomLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.topLabel.mas_bottom).mas_offset(5);
make.left.right.mas_offset(0);
make.height.mas_equalTo(20);
}];
}
#pragma mark - 根据传入的值创建相应的UI
/** 根据传入的值创建相应的UI */
- (void)setType:(NSInteger)type{
switch (type) {
case NoContentTypeNetwork: // 没网
{
[self setImage:@"网络异常" topLabelText:@"貌似没有网络" bottomLabelText:@"点击重试"];
}
break;
case NoContentTypeOrder:
{
[self setImage:@"订单无数据" topLabelText:@"暂时没有订单" bottomLabelText:@"重新�加载"];
}
break;
default:
break;
}
}
#pragma mark - 设置图片和文字
/** 设置图片和文字 */
- (void)setImage:(NSString *)imageName topLabelText:(NSString *)topLabelText bottomLabelText:(NSString *)bottomLabelText{
self.imageView.image = [UIImage imageNamed:imageName];
self.topLabel.text = topLabelText;
self.bottomLabel.text = bottomLabelText;
}
-
第二步:封装BaseTableView
BaseTableView作为基类,需要时可以根据实际情况展示不同样式的无数据占位图,也可以随时将无数据占位图移除。当然,点击这个占位图也会回调相应方法。
.h文件
#import <UIKit/UIKit.h>
@interface BaseTableView : UITableView
// 无数据占位图点击的回调函数
@property (copy,nonatomic) void(^noContentViewTapedBlock)();
/**
展示无数据占位图
@param emptyViewType 无数据占位图的类型
*/
- (void)showEmptyViewWithType:(NSInteger)emptyViewType;
/* 移除无数据占位图 */
- (void)removeEmptyView;
@end
.m文件
#import "BaseTableView.h"
#import "NoContentView.h"
@interface BaseTableView (){
NoContentView *_noContentView;
}
@end
@implementation BaseTableView
/**
展示无数据占位图
@param emptyViewType 无数据占位图的类型
*/
- (void)showEmptyViewWithType:(NSInteger)emptyViewType{
// 如果已经展示无数据占位图,先移除
if (_noContentView) {
[_noContentView removeFromSuperview];
_noContentView = nil;
}
//------- 再创建 -------//
_noContentView = [[NoContentView alloc]initWithFrame:self.bounds];
[self addSubview:_noContentView];
_noContentView.type = emptyViewType;
//------- 添加单击手势 -------//
[_noContentView addGestureRecognizer:[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(noContentViewDidTaped:)]];
}
/* 移除无数据占位图 */
- (void)removeEmptyView{
[_noContentView removeFromSuperview];
_noContentView = nil;
}
// 无数据占位图点击
- (void)noContentViewDidTaped:(NoContentView *)noContentView{
if (self.noContentViewTapedBlock)
{
self.noContentViewTapedBlock(); // 调用回调函数
}
}
@end
-
使用:
tableView的数据源数组为空时展示,不为空时移除。
// 展示无数据占位图
[self.tableView showEmptyViewWithType:NoContentTypeNetwork];
// 无数据占位图点击的回调
self.tableView.noContentViewTapedBlock = ^{
[SVProgressHUD showSuccessWithStatus:@"没网"];
};
// 移除无数据占位图
[self.tableView removeEmptyView];
细节:
- 根据苹果官方文档的推荐,你的方法名必须直观。
- 合理使用枚举,看起舒心、写起放心。
- 为什么是移除无数据占位图而不是隐藏?因为你已经不需要它了,所以也不需要它隐藏。不需要时移除,需要时再建,不要说什么再建消耗内存,你一直隐藏在那里不占内存?这种情况下,随时移除释放资源才是最好的做法。
- delegate和block的取舍:当两种方式都可以实现相同功能,并且代码可读性不相上下时,采用最终使用时能让使用者敲更少代码的那种方式(我这里用block,使用时就只敲一句代码,如果用delegate,可读性不见得更高,还要多敲两行代码,所以用block)。
demo
![](https://img.haomeiwen.com/i1692043/ddd5909bc6ccf998.gif)
此文章对应的demo
这个demo更加通用和强大
注:看demo直接从
ViewController.m
看起
2017年9月26日更新
后面我发现这个轮子不是很优雅,然后重新设计了一个:
iOS开发造轮子 | UIView及其子类的占位图
网友评论
/** 无数据占位图的类型 */
@property (nonatomic,assign) NSInteger type;