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 *)placeholderViewreloadButtonDidClick:(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不就自动移除了吗?而这个占位图,不也可以看做是一个弹窗吗?

为何这么6

 

使用方法

作为标准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性决定了这个通用占位图的高度灵活性。


推了一下我的300多度近视眼镜

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
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

转载于:https://www.cnblogs.com/sundaysgarden/p/9452914.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/570558.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 计算26个字母在一段文本中出现的频率(保留小数点后4位)

public class FrequencyCalculator {public static void main(String[] args){//定义需要计算字母出现频率的文本String text"some off#acebooksea3rl255 yinvestorssoldofftheirstockatthefirs" "tchancetheygotbutceomarkzuckerbergishangingontohissharesfor…

Maven_生产环境下开发对Maven的需求

目前技术开发时存在的问题&#xff1a; 一个项目就是一个工程 如果一个项目非常庞大&#xff0c;就不适合继续使用package来划分模块。最好是每一个模块对应一个工程&#xff0c;利于分工协作。 借助Maven就可以将一个项目拆分多个工程。 项目中需要的jar包必须手动复制粘贴到W…

string赋值-单引号和双引号的区别(php)

在赋予一个string值的时候&#xff0c;可以用单引号或者双引号。 1.单引号和双引号的区别&#xff1a; 单引号&#xff1a;不会翻译变量。 双引号&#xff1a;会翻译变量&#xff0c;会将变量替换为之前赋予变量的值。 例子&#xff1a; &#xff08;1&#xff09;单引号&a…

Web项目开发流程 PC端

Web项目开发流程 PC端 转自 https://www.cnblogs.com/mdengcc/p/6475733.html一直再做前端&#xff0c;突然想到如果有一天领导让自己独立承担一个web 项目的话是否有足够的能力去接这个任务&#xff0c;要学会自己去搭建一些基础的工具信息。所有的这一切在心里都要有个大致的…

Hibernate常用的Java数据类型映射到mysql和Oracle

研究了常用的Java基本数据类型在mysql和oracle数据库的映射类型。这里使用的是包装类型做研究&#xff0c;一般在hibernate声明的时候最好不要用基本类型&#xff0c;因为数据库中的null空数据有可能映射为基本类型的时候会报错&#xff0c;但是映射到包装类型的时候值为null&a…

Spring Cloud 之 Feign 使用HTTP请求远程服务

Feign是从Netflix中分离出来的轻量级项目&#xff0c;能够在类接口上添加注释&#xff0c;成为一个REST API 客户端&#xff0c;Feign默认集成了Ribbon。 Feign中对 Hystrix 有依赖关系。Feign只是一个便利的rest框架&#xff0c;简化调用&#xff0c;最后还是通过ribbon在注册…

UI控件之UISlider

一、创建 UISlider *slider [[UISlider alloc] initWithFrame:CGRectMake(0, 200, self.view.frame.size.width-100, 50)]; 二、设置最大最小值 slider.minimumValue 0; slider.maximumValue 1; 三、改变圆形前面和后面的颜色 slider.minimumTrackTintColor [UIColor orang…

Front End Accessibility Development Guide

Header Carefully write heading(h1,h2,h3) for screen reader. Alternative Image Provide alt text for both essential and functional images. Provide empty alt text (alt””) for decorative images. Don’t repeat the alt text of an image in the adjacent text. De…

详细的Windows下安装 binwalk

1. https://github.com/ReFirmLabs/binwalk到这里下载binwalk&#xff0c;下载后解压。 2. 找到下载后的文件夹&#xff0c; 在这里要进行安装步骤&#xff0c;一边按着shift&#xff0c;一边按着鼠标右键&#xff0c;点击在此处打开命令窗口。 输入python setup.py install 安…

各大浏览器清除缓存(cache)详细步骤

1. Firefox 1.1 选择“Options”&#xff1a; 1.2 选择“Advanced”->“Network”->“Cached Web Content”&#xff0c;点击“Clear Now”&#xff1a; 2. Chrome 2.1 选择“Settings”&#xff1a; 2.2 点击页面底部“Show advanced settings”&#xff1a; 2.3 选择…

python部分 + 数据库 + 网络编程

PS:附上我的博客地址&#xff0c;答案中略的部分我的博客都有&#xff0c;直接原标题搜索即可。https://www.cnblogs.com/Roc-Atlantis/第一部分 Python基础篇&#xff08;80题&#xff09;为什么学习Python&#xff1f;Omit通过什么途径学习的Python&#xff1f;OmitPython和J…

使用 angular directive 和 json 数据的 D3 带标签 donut chart示例

利用angular resource加载priorityData.json中的json数据&#xff0c;结合D3画出甜甜圈图。运行index.html结果如图所示&#xff1a; priorityData.json中json数据如下&#xff1a; { "priority":{ "Blocker":12,"Critical":18,"Major&qu…

第一个express app 详细步骤

1. 全局安装node.js&#xff08;请参考网上教程&#xff09;。 如何判断是否全局安装成功&#xff1f;打开命令行终端&#xff0c;进入任意文件夹&#xff0c;输入node&#xff0c;不报错。 2. 安装express 2.1 创建app文件夹。并在此文件夹下创建文件package.json如下&#…

【scarletthln 关于算法的一点总结】

1. 分解问题的角度: fix 某一维度&#xff0c;尝试另一维度上的所有可能 a. 可能是array的(i, j)pointers, b. 可能是矩形的长与宽, c. 可能是tree的每一个subtree, d. 可能是情景题的每一对pair...2. 求所有解的, 暴力上backtracking吧3. 如果问最短/最少的, 先想BFS、DP这对…

Angularjs Nodejs Grunt 一个例子

做了一个简单的示例&#xff0c;目的是记录环境配置以及这套框架的结构流程。 1.配置环境 默认nodejs已安装。 安装以下模块&#xff1a;express&#xff08;nodejs框架&#xff09;,grunt&#xff08;javascript task runner&#xff09;,grunt-contrib-watch&#xff08;g…

【C#/WPF】用Thumb做可拖拽的UI控件

【C#/WPF】用Thumb做可拖拽的UI控件 原文:【C#/WPF】用Thumb做可拖拽的UI控件需求&#xff1a;简单的可拖拽的图片 使用System.Windows.Controls.Primitives.Thumb类 前台&#xff1a; <Canvas x:Name"g"><Thumb Canvas.Left"10" Canvas.Top"…

PHP 常用设计模式 (转载)

1.单例模式 单例模式顾名思义&#xff0c;就是只有一个实例。作为对象的创建模式&#xff0c; 单例模式确保某一个类只有一个实例&#xff0c;而且自行实例化并向整个系统提供这个实例。 单例模式的要点有三个&#xff1a; 一是某个类只能有一个实例&#xff1b;二是它必须自行…

Angularjs切换网站配色模式简单示例1(切换css文件)

一个网站可以有多种配色方案&#xff0c;例如正常模式&#xff0c;夜间模式等。 简单示例一个通过点击按钮&#xff0c;更换css文件&#xff0c;达到切换配色模式的angularjs 小app。 主要文件有三个&#xff1a;index.html&#xff08;主文件&#xff09;&#xff0c;white.…

【转】在树莓派上实现人脸识别

教程参考地址&#xff1a;http://shumeipai.nxez.com/2018/08/12/facial-recognition-identification-on-raspberry-pi.html 转载于:https://www.cnblogs.com/little-kwy/p/9481259.html

Angularjs切换网站配色模式简单示例2(切换body元素的class)

一个网站可以有多种配色方案&#xff0c;例如正常模式&#xff0c;夜间模式等。 简单示例一个通过点击toggle 按钮&#xff0c;切换body元素的class&#xff0c;达到切换配色模式的angularjs小app。 1. Live范例可以在以下Codepen网址查看&#xff1a; http://codepen.io/Chris…