图片 8

无网占位图的落到实处,通用占位图

By admin in 编程 on 2019年9月17日

图片 1T-ara—孝敏

图片 2灵魂歌手-泰妍

先来看一下实际效果:

图片 3效果.gif

实际运用场景:

没网时的提示view,tableView或collectionView没内容时的展示view,以及其它特殊情况时展示的特定view。如:

图片 4常见的几种情况

为什么要使用无网占位图?

为了更好地用户体验。难道没网的时候展示一个空白模板?当然,无网占位图也是一个APP最基本的功能只一。

我的目标:

对以上几种情况的展示view做统一封装,将来做新APP时,我只需在这个轮子上稍加修改就可实现相应需求。

原理简介:

viewWillAppear时判断是否有网,如果没网,展示无网占位图。点击无网占位图上的重新查看按钮,判断是否有网,如果没网,toast提示。如果有网,移除无网占位图,重新加载数据。

对自己的要求:

代码简洁规范,逻辑清晰,保证写出的代码将来任何接手的人可以轻松读懂。

详细讲解:

  • 首先按照设计师的UI图封装一个无网占位图view:CQNoNetworkView,在构造方法里完成

- (instancetype)initWithFrame:frame{ if (self = [super initWithFrame:frame]) { // UI搭建 [self setUpUI]; } return self;}

 /** UI搭建 */ - setUpUI{ self.backgroundColor = [UIColor whiteColor]; // 404图片放中间 UIImageView *noNetworkImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 125, 125)]; noNetworkImageView.center = CGPointMake(self.width / 2, self.height / 2); [self addSubview:noNetworkImageView]; noNetworkImageView.image = [UIImage imageNamed:@"404notfound"]; // 重新查看按钮 UIButton *checkButton = [[UIButton alloc]initWithFrame:CGRectMake(0, noNetworkImageView.maxY + 24, 115, 30)]; checkButton.centerX = self.width / 2; [self addSubview:checkButton]; [checkButton.titleLabel setFont:[UIFont systemFontOfSize:15]]; checkButton.backgroundColor = [UIColor colorWithRed:0.00 green:0.76 blue:0.66 alpha:1.00]; [checkButton setTitle:@"重新查看" forState:UIControlStateNormal]; [checkButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [checkButton addTarget:self action:@selector(checkNetworkButtonClicked) forControlEvents:UIControlEventTouchUpInside]; // 图片上面的两个label UILabel *label1 = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 20)]; label1.text = @"刷新一下返回店铺!"; label1.font = [UIFont systemFontOfSize:14]; label1.textColor = [UIColor colorWithRed:0.00 green:0.77 blue:0.68 alpha:1.00]; label1.backgroundColor = [UIColor clearColor]; label1.textAlignment = NSTextAlignmentCenter; [label1 sizeToFit]; label1.centerX = self.width / 2; label1.maxY = noNetworkImageView.y - 21; [self addSubview:label1]; UILabel *label2 = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 15)]; label2.text = @"您似乎迷路了"; label2.font = [UIFont systemFontOfSize:19]; label2.textColor = [UIColor colorWithRed:0.00 green:0.77 blue:0.67 alpha:1.00]; label2.backgroundColor = [UIColor clearColor]; label2.textAlignment = NSTextAlignmentCenter; [label2 sizeToFit]; label2.centerX = self.width / 2; label2.maxY = label1.y - 12; [self addSubview:label2];}
  • 处理“重新查看”按钮的逻辑,基本思路是:点击此按钮时,判断是否有网,如果仍旧没网,弹出toast提示用户;如果有网,移除此view,并且,让代理方执行相应代理方法,比如说,重新加载数据。

 /** 重新查看按钮点击 */ - checkNetworkButtonClicked{ if ([DateUtil isNetWorkRunning]) { // 如果有网,view消失,并且让代理方执行代理方法 for (CQNoNetworkView *view in [self getCurrentViewController].view.subviews) { if ([view isMemberOfClass:[CQNoNetworkView class]]) { [view removeFromSuperview]; } } // 重新加载数据 if ([self.delegate respondsToSelector:@selector(reloadData)]) { [self.delegate reloadData]; } }else{ // 如果没网,toast提示 [CQHud showToastWithMessage:@"请检查你的网络连接"]; }}
  • 确定代理方法(点击“重新加载”按钮时,代理方执行的方法),我的是重新加载数据。

@protocol CheckNetworkDelegate <NSObject>@optional /** 重新加载数据 */ - reloadData;@end /** 无网络时展示的view */@interface CQNoNetworkView : UIView@property (nonatomic,weak) id<CheckNetworkDelegate> delegate;@end
  • 确定代理方。代理方就是执行“重新加载数据”的对象。由于每个viewController都需要在无网时展示无网占位图,所以代理方可以设置为项目的BaseViewController(不要告诉我,你的项目里没有基类😅)

@interface BaseViewController ()<CheckNetworkDelegate>

在基类里实现重新加载数据,当然,需要由子类重写,不过如果你的项目架构的比较好,所有viewcontroller的获取数据的方法的方法名都一样,那么就不需要再由子类重写了:

/** 重新加载数据,由子类重写 */ reloadData{ }

同样在基类里实现弹出无网占位图的方法:

/** 显示无网络view */- showNoNetworkView{ // 将导航栏和tabbar留出来 CQNoNetworkView *noNetworkView = [[CQNoNetworkView alloc]initWithFrame:CGRectMake(0, 0, screenWidth, screenHeight - 49)]; noNetworkView.delegate = self; [self.view addSubview:noNetworkView];}
  • 在基类的viewWillAppear:里判断网络情况:

// 如果没网,加载无网占位图 if (![DateUtil isNetWorkRunning]) { // 无网加载无网站位图 [self showNoNetworkView]; }else{ // 有网移除所有无网展位图 for (CQNoNetworkView *view in self.view.subviews) { if ([view isMemberOfClass:[CQNoNetworkView class]]) { [view removeFromSuperview]; } } }
  • 最后,在你的子类里重写“重新加载数据”方法即可:

- reloadData{ [self getData];}

对轮子的要求:

  • 使用方便
  • 易于维护修改
  • 高内聚,低耦合(不要因为这个轮子的加入而影响之前的代码)

总结

此功能主要是代理和基类的运用,合理使用基类能让你少些很多代码。当然有些APP可能不仅有一种网络占位图,这个时候在子类里重写基类里的showNoNetworkView方法即可。以上是我对此功能的一些个人看法,只是简单的实现了这个功能,具体项目中还是要根据项目需求进行必要修改和优化的。但是,思路基本上不变。欢迎各位看官提出意见或建议。

思路:

  • 这是一个view,可以添加到tableView或collectionView上的view,当然,也可以添加到其他类型的view上。
  • 一般说来,在一个项目中,从UI的角度来看,这样的view有几种,但是,从结构的角度来看,它们又一样,比如说:正中间一个imageView、imageView下方一个label、label下方一个button。
  • 点击这个view或者这个view上的button,会执行相应回调方法。

补充两个更加优雅和强大的Demo

UITableView空数据占位图适用于任何场景的通用占位图

先贴出代码,稍后细讲

#import <UIKit/UIKit.h>/** 占位图的类型 */typedef NS_ENUM(NSInteger, CQPlaceholderViewType) { /** 没网 */ CQPlaceholderViewTypeNoNetwork = 1, /** 没订单 */ CQPlaceholderViewTypeNoOrder, /** 没商品 */ CQPlaceholderViewTypeNoGoods, /** 美丽的妹纸 */ CQPlaceholderViewTypeBeautifulGirl};#pragma mark - @protocol@class CQPlaceholderView;@protocol CQPlaceholderViewDelegate <NSObject>/** 占位图的重新加载按钮点击时回调 */- 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:frame type:(CQPlaceholderViewType)type delegate:delegate;@end

#import "CQPlaceholderView.h"@implementation CQPlaceholderView#pragma mark - 构造方法/** 构造方法 @param frame 占位图的frame @param type 占位图的类型 @param delegate 占位图的代理方 @return 指定frame、类型和代理方的占位图 */- (instancetype)initWithFrame:frame type:(CQPlaceholderViewType)type delegate:delegate{ if (self = [super initWithFrame:frame]) { // 存值 _type = type; _delegate = delegate; // UI搭建 [self setUpUI]; } return self;}#pragma mark - UI搭建/** UI搭建 */- 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  { 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 - 重新加载按钮点击/** 重新加载按钮点击 */- reloadButtonClicked:(UIButton *)sender{ // 代理方执行方法 if ([_delegate respondsToSelector:@selector(placeholderView:reloadButtonDidClick:)]) { [_delegate placeholderView:self reloadButtonDidClick:sender]; } // 从父视图上移除 [self removeFromSuperview];}@end

详细说明

/** 构造方法 @param frame 占位图的frame @param type 占位图的类型 @param delegate 占位图的代理方 @return 指定frame、类型和代理方的占位图 */- (instancetype)initWithFrame:frame type:(CQPlaceholderViewType)type delegate:delegate;

三个参数说明:

  • frame:决定占位图的大小和位置。之所以需要这个参数是因为:这个占位图可能和你的tableView一样大,也可能是全屏的。
  • type:占位图的类型。这个参数决定占位图的展示样式。
  • delegate:代理方。处理事件。

/** 占位图类型 */@property (nonatomic,assign,readonly) CQPlaceholderViewType type;/** 占位图的代理方 */@property (nonatomic,weak,readonly) id <CQPlaceholderViewDelegate> delegate;

因为这两个属性需要暴露出去,但是我又不希望它们在外部被修改,我希望构造方法是决定这个占位图属性的唯一方法。还有就是:让在外部能直接修改的越少,意外也就越少。

/** 占位图的重新加载按钮点击时回调 */- placeholderView:(CQPlaceholderView *)placeholderView reloadButtonDidClick:(UIButton *)sender;

可能有人会说,你这个代理方法不就是想要执行重新加载数据方法嘛,直接命名为reloadData不是多好的?反正曾经懵懂的我看别人的代码时是这样想当然的认为的,直到有一天我看了官方文档以及回想了一下系统给代理方法的命名。其实代理方法的命名是一个非常讲究的东西,我之所以这样命名是完全参照官方文档的命名规范的,建议有疑问的瞅两眼delegate
命名。你也可以这样理解:代理方法,它描述的是某一个事件,而不是事件要执行的某个方法

#pragma mark - 重新加载按钮点击/** 重新加载按钮点击 */- reloadButtonClicked:(UIButton *)sender{ // 代理方执行方法 if ([_delegate respondsToSelector:@selector(placeholderView:reloadButtonDidClick:)]) { [_delegate placeholderView:self reloadButtonDidClick:sender]; } // 从父视图上移除 [self removeFromSuperview];}

我之前的做法是写一个移除占位图的方法,然后将这个方法暴露出去,需要移除的时候就调用这个方法。后面想了想,这样做完全是自找麻烦:我们点击UIAlertView的按钮的时候,这个alertView不就自动移除了吗?而这个占位图,不也可以看做是一个弹窗吗?

图片 5为何这么6

使用方法

作为标准delegate传值的view,使用方法类似于系统的UIAlertView1.
引入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性决定了这个通用占位图的高度灵活性。

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

3. 处理回调

#pragma mark - Delegate - 占位图/** 占位图的重新加载按钮点击时回调 */- 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很强大,它之所以如此强大是为了让大部分人可以直接拿来即可用,它的目标是满足大部分开发者的一般常规需求,所以,它或许适合你,但一定不是最适合你,最适合你的,一定是自己亲手打造的。还有,它很强大,以至于有些功能你都不需要。窃以为:知道其实现原理,然后自己封装真正适合自己当前项目的框架才是王道

图片 7多了些细节

demo

分享的人不少,认真总结分享的人不多,请不要吝惜你的star。github demo

图片 8只有107个了

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 澳门新葡亰官网app 版权所有