iOS开发造轮子 | 通用占位图
https://www.jianshu.com/p/beca3ac24031
实际运用场景:
没网时的提示view,tableView或collectionView没内容时的展示view,以及其它特殊情况时展示的特定view。如:

我的目标:
对以上几种情况的展示view做统一封装,将来做新APP时,我只需在这个轮子上稍加修改就可实现相应需求。
对自己的要求:
代码简洁规范,逻辑清晰,保证写出的代码将来任何接手的人可以轻松读懂。
对轮子的要求:
- 使用方便
- 易于维护修改
- 高内聚,低耦合(不要因为这个轮子的加入而影响之前的代码)
思路:
- 这是一个view,可以添加到tableView或collectionView上的view,当然,也可以添加到其他类型的view上。
- 一般说来,在一个项目中,从UI的角度来看,这样的view有几种,但是,从结构的角度来看,它们又一样,比如说:正中间一个imageView、imageView下方一个label、label下方一个button。
- 点击这个view或者这个view上的button,会执行相应回调方法。
先贴出代码,稍后细讲
.h文件
#import <UIKit/UIKit.h>
/** 占位图的类型 */
typedef NS_ENUM(NSInteger, CQPlaceholderViewType) {
/** 没网 */
CQPlaceholderViewTypeNoNetwork = 1,
/** 没订单 */
CQPlaceholderViewTypeNoOrder,
/** 没商品 */
CQPlaceholderViewTypeNoGoods,
/** 美丽的妹纸 */
CQPlaceholderViewTypeBeautifulGirl
};
#pragma mark - @protocol
@class CQPlaceholderView;
@protocol CQPlaceholderViewDelegate <NSObject>
/** 占位图的重新加载按钮点击时回调 */
- (void)placeholderView:(CQPlaceholderView *)placeholderView
reloadButtonDidClick:(UIButton *)sender;
@end
#pragma mark - @interface
@interface CQPlaceholderView : UIView
/** 占位图类型(只读) */
@property (nonatomic,assign,readonly) CQPlaceholderViewType type;
/** 占位图的代理方(只读) */
@property (nonatomic,weak,readonly) id <CQPlaceholderViewDelegate> delegate;
/**
构造方法
@param frame 占位图的frame
@param type 占位图的类型
@param delegate 占位图的代理方
@return 指定frame、类型和代理方的占位图
*/
- (instancetype)initWithFrame:(CGRect)frame
type:(CQPlaceholderViewType)type
delegate:(id)delegate;
@end
.m文件
#import "CQPlaceholderView.h"
@implementation CQPlaceholderView
#pragma mark - 构造方法
/**
构造方法
@param frame 占位图的frame
@param type 占位图的类型
@param delegate 占位图的代理方
@return 指定frame、类型和代理方的占位图
*/
- (instancetype)initWithFrame:(CGRect)frame type:(CQPlaceholderViewType)type delegate:(id)delegate{
if (self = [super initWithFrame:frame]) {
// 存值
_type = type;
_delegate = delegate;
// UI搭建
[self setUpUI];
}
return self;
}
#pragma mark - UI搭建
/** UI搭建 */
- (void)setUpUI{
self.backgroundColor = [UIColor whiteColor];
//------- 图片在正中间 -------//
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(self.frame.size.width / 2 - 50, self.frame.size.height / 2 - 50, 100, 100)];
[self addSubview:imageView];
//------- 说明label在图片下方 -------//
UILabel *descLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(imageView.frame) + 10, self.frame.size.width, 20)];
[self addSubview:descLabel];
descLabel.textAlignment = NSTextAlignmentCenter;
//------- 按钮在说明label下方 -------//
UIButton *reloadButton = [[UIButton alloc]initWithFrame:CGRectMake(self.frame.size.width / 2 - 60, CGRectGetMaxY(descLabel.frame) + 5, 120, 25)];
[self addSubview:reloadButton];
[reloadButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
reloadButton.layer.borderColor = [UIColor blackColor].CGColor;
reloadButton.layer.borderWidth = 1;
[reloadButton addTarget:self action:@selector(reloadButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
//------- 根据type创建不同样式的UI -------//
switch (_type) {
case CQPlaceholderViewTypeNoNetwork: // 没网
{
imageView.image = [UIImage imageNamed:@"网络异常"];
descLabel.text = @"没网,不约";
[reloadButton setTitle:@"点击重试" forState:UIControlStateNormal];
}
break;
case CQPlaceholderViewTypeNoOrder: // 没订单
{
imageView.image = [UIImage imageNamed:@"订单无数据"];
descLabel.text = @"暂无订单";
[reloadButton setTitle:@"没有拉到" forState:UIControlStateNormal];
}
break;
case CQPlaceholderViewTypeNoGoods: // 没商品
{
imageView.image = [UIImage imageNamed:@"没商品"];
descLabel.text = @"红旗连锁你的好邻居";
[reloadButton setTitle:@"buybuybuy" forState:UIControlStateNormal];
}
break;
case CQPlaceholderViewTypeBeautifulGirl: // 妹纸
{
imageView.image = [UIImage imageNamed:@"妹纸"];
descLabel.text = @"你会至少在此停留3秒钟";
[reloadButton setTitle:@"不爱妹纸" forState:UIControlStateNormal];
}
break;
default:
break;
}
}
#pragma mark - 重新加载按钮点击
/** 重新加载按钮点击 */
- (void)reloadButtonClicked:(UIButton *)sender{
// 代理方执行方法
if ([_delegate respondsToSelector:@selector(placeholderView:reloadButtonDidClick:)]) {
[_delegate placeholderView:self reloadButtonDidClick:sender];
}
// 从父视图上移除
[self removeFromSuperview];
}
@end
详细说明
1.关于构造方法的设计
/**
构造方法
@param frame 占位图的frame
@param type 占位图的类型
@param delegate 占位图的代理方
@return 指定frame、类型和代理方的占位图
*/
- (instancetype)initWithFrame:(CGRect)frame
type:(CQPlaceholderViewType)type
delegate:(id)delegate;
三个参数说明:
- frame:决定占位图的大小和位置。之所以需要这个参数是因为:这个占位图可能和你的tableView一样大,也可能是全屏的。
- type:占位图的类型。这个参数决定占位图的展示样式。
- delegate:代理方。处理事件。
2.为什么type和delegate要设置为只读?
/** 占位图类型(只读) */
@property (nonatomic,assign,readonly) CQPlaceholderViewType type;
/** 占位图的代理方(只读) */
@property (nonatomic,weak,readonly) id <CQPlaceholderViewDelegate> delegate;
因为这两个属性需要暴露出去,但是我又不希望它们在外部被修改,我希望构造方法是决定这个占位图属性的唯一方法。还有就是:让在外部能直接修改的越少,意外也就越少。
3.为什么我的这代理方法命名如此冗长?
/** 占位图的重新加载按钮点击时回调 */
- (void)placeholderView:(CQPlaceholderView *)placeholderView
reloadButtonDidClick:(UIButton *)sender;
可能有人会说,你这个代理方法不就是想要执行重新加载数据方法嘛,直接命名为reloadData不是多好的?反正曾经懵懂的我看别人的代码时是这样想当然的认为的,直到有一天我看了官方文档以及回想了一下系统给代理方法的命名。
其实代理方法的命名是一个非常讲究的东西,我之所以这样命名是完全参照官方文档的命名规范的,建议有疑问的瞅两眼delegate 命名。你也可以这样理解:代理方法,它描述的是某一个事件,而不是事件要执行的某个方法。
4.为什么点击按钮后就直接将占位图移除?
#pragma mark - 重新加载按钮点击
/** 重新加载按钮点击 */
- (void)reloadButtonClicked:(UIButton *)sender{
// 代理方执行方法
if ([_delegate respondsToSelector:@selector(placeholderView:reloadButtonDidClick:)]) {
[_delegate placeholderView:self reloadButtonDidClick:sender];
}
// 从父视图上移除
[self removeFromSuperview];
}
我之前的做法是写一个移除占位图的方法,然后将这个方法暴露出去,需要移除的时候就调用这个方法。后面想了想,这样做完全是自找麻烦:我们点击UIAlertView的按钮的时候,这个alertView不就自动移除了吗?而这个占位图,不也可以看做是一个弹窗吗?

使用方法
作为标准delegate传值的view,使用方法类似于系统的UIAlertView。
1. 引入delegate
@interface ViewController ()<CQPlaceholderViewDelegate>
2. 初始化
CQPlaceholderView *placeholderView = [[CQPlaceholderView alloc]initWithFrame:tableView.bounds type:CQPlaceholderViewTypeNoOrder delegate:self];
[tableView addSubview:placeholderView];
看到没有,我想在哪add就在哪add,比那什么只能在tableView或collectionView上展示的强大多了。正是这个frame和完全开放的被add性决定了这个通用占位图的高度灵活性。

3. 处理回调
#pragma mark - Delegate - 占位图
/** 占位图的重新加载按钮点击时回调 */
- (void)placeholderView:(CQPlaceholderView *)placeholderView reloadButtonDidClick:(UIButton *)sender{
switch (placeholderView.type) {
case CQPlaceholderViewTypeNoGoods: // 没商品
{
[self.view makeToast:@"买个球啊"];
}
break;
case CQPlaceholderViewTypeNoOrder: // 没有订单
{
[self.view makeToast:@"拉到就拉到"];
}
break;
case CQPlaceholderViewTypeNoNetwork: // 没网
{
[self.view makeToast:@"没网适合打排位"];
}
break;
case CQPlaceholderViewTypeBeautifulGirl: // 妹纸
{
[self.view makeToast:@"哦,那你很棒棒哦"];
}
break;
default:
break;
}
}
对比DZNEmptyDataSet
DZNEmptyDataSet
这是当前很受欢迎的一个库:
A drop-in UITableView/UICollectionView superclass category for showing empty datasets whenever the view has no content to display
它通过一系列代理方法来决定占位图的显示样式及事件回调。平心而论,很优秀,但是我还是决定用我自己的。
我的看法是(Im my opinion):
DZNEmptyDataSet很强大,它之所以如此强大是为了让大部分人可以直接拿来即可用,它的目标是满足大部分开发者的一般常规需求,所以,它或许适合你,但一定不是最适合你,最适合你的,一定是自己亲手打造的。还有,它很强大,以至于有些功能你都不需要。
窃以为:知道其实现原理,然后自己封装真正适合自己当前项目的框架才是王道。

demo
分享的人不少,认真总结分享的人不多,请不要吝惜你的star。
github demo
作者:无夜之星辰
链接:https://www.jianshu.com/p/beca3ac24031
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
iOS开发造轮子 | 通用占位图的更多相关文章
- iOS开发代码规范(通用)
1. 关于命名 1> 统一要求 含义清楚,尽量做到不需要注释也能了解其作用,若做不到,就加注释 使用全称,不适用缩写 2> 类的命名 大驼峰式命名:每个单词的首字母都采用大写字母 例子:M ...
- iOS开发——无网占位图的实现
https://www.jianshu.com/p/d537393fe247 https://github.com/wyzxc/CQPlaceholderViewhttps://github.com/ ...
- 除非你是BAT,前端开发中最好少造轮子
站在前人的肩膀上 HTML.CSS.JavaScript是前端的根基,这是无可否认的事实.正如一辆车当然都是由一堆钢板和螺钉组成的,但是现在还有人拎着个锤子敲敲打打的造车吗?李书福说过,“汽车不过是四 ...
- 避免重复造轮子的UI自动化测试框架开发
一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...
- GitHub Android 最火开源项目Top20 GitHub 上的开源项目不胜枚举,越来越多的开源项目正在迁移到GitHub平台上。基于不要重复造轮子的原则,了解当下比较流行的Android与iOS开源项目很是必要。利用这些项目,有时能够让你达到事半功倍的效果。
1. ActionBarSherlock(推荐) ActionBarSherlock应该算得上是GitHub上最火的Android开源项目了,它是一个独立的库,通过一个API和主题,开发者就可以很方便 ...
- Light libraries是一组通用的C基础库,目标是为减少重复造轮子而写(全部用POSIX C实现)
Light libraries是一组通用的C基础库,目标是为减少重复造轮子而写实现了日志.原子操作.哈希字典.红黑树.动态库加载.线程.锁操作.配置文件.os适配层.事件驱动.工作队列.RPC.IPC ...
- 拒绝造轮子!如何移植并使用Linux内核的通用链表(附完整代码实现)
在实际的工作中,我们可能会经常使用链表结构来存储数据,特别是嵌入式开发,经常会使用linux内核最经典的双向链表 list_head.本篇文章详细介绍了Linux内核的通用链表是如何实现的,对于经常使 ...
- 【疯狂造轮子-iOS】JSON转Model系列之二
[疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...
- 【疯狂造轮子-iOS】JSON转Model系列之一
[疯狂造轮子-iOS]JSON转Model系列之一 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗 ...
随机推荐
- django数据查询之F查询和Q查询
仅仅靠单一的关键字参数查询已经很难满足查询要求.此时Django为我们提供了F和Q查询: # F 使用查询条件的值,专门取对象中某列值的操作 # from django.db.models impor ...
- Lua不显示小数点0的部分
我的环境:Unity3D 5.3.7p4 XLua版本v2.1.6 基于Lua5.3 (https://github.com/Tencent/xLua) 在Lua中数字不区分整型或浮点型,所有都是nu ...
- CentOS 7.0下解决ifconfig: command not found的方法
在CentOS7.0中输入ifconfig命令会遇到-bash: ifconfig: command not found. 在CentOS最小安装时是没有附带ifconfig,我们进入sbin目录下可 ...
- Windows 版本说明,Enterprise、Ultimate、Home、Professional知多少
关于Windows 的安装光盘版本很多种,很多人不知道选择哪些. Ultimate 旗舰版,VISTA开始有了这个级别,是最全最高级的,一般程序开发的电脑,玩游戏的电脑,建议用它,不过对配置稍有一些要 ...
- LeetCode算法题-Move Zeroes(Java实现-三种解法)
这是悦乐书的第201次更新,第211篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第67题(顺位题号是283).给定一个数组nums,写一个函数将所有0移动到它的末尾,同 ...
- Git版本管理工具常用命令说明
Git常用命令 $ touch README.md 创建一个README.md文件 $ git init 创建本地仓库(repository),将会在文件夹下创建一个 .git 文件夹,.git 文 ...
- Window 中杀死指定端口 cmd 命令行 taskkill
https://www.cnblogs.com/xwer/p/7780571.html
- 为了下载和使用最新的git,只好安装了迅雷
一 安装迅雷时总感觉它的图标像是灰鸽子,让人不放心. 从https://git-scm.com/downloads上找到64-bit Git for Windows Setup的地址: https: ...
- Python 字典方法
访问字典的值 字典中的 键/值 实际上就是一种映射关系,只要知道了 “键”,就肯定知道 “值”. >>> my_dict = dict(name = 'zhangsan',other ...
- [tool] AI视频翻译 解决英文视频字幕问题(类似youtube自动生成字幕)
1.网易见外是网易人工智能事业部旗下的AI视频翻译产品. 字幕支持手工编辑和下载 不过网易见外 只支持WEB在线操作 并且只支持单个上传操作 目前没有客户端 2.人人译视界 (IOS 安卓 PC客户端 ...