iOS 为UITableView或UICollectionView添加空值界面

背景:

1.平常码业务时,会有在列表没值时展示占位图的需求,有时候需要展示图片及文字,有时候我们还需要为占位图添加点击事件等。
2.在每次请求的时候手动调用代码去添加占位图太麻烦,也不现实,对代码质量也有影响。
3.git上面也有相关的三方库,但第三方库一般会考虑各种使用场景,代码量大,会对包体积造成影响。
4.此Demo已经满足正常需求,且使用非常方便,一句代码即可。

使用示例

只需要在初始化列表控件的时候,加入以下一句代码即可。

// image有值,title为nil时,只展示图片
// image为nil,title有值时,只展示文字
// block为nil时,则代表不需要点击事件
[_tableView setEmptyViewWithImage:[UIImage imageNamed:@"noresult"] title:@"某有数据啊,点击我再刷新一次看看?" eventBlock:^{
 NSLog(@"点击了,去刷新喽");
 }];

实现技术:

1.Runtime:类别关联属性,添加成员变量、方法交换
2.枚举(空值界面类型)、映射(对代理回调的监听)、Block(对点击事件的回调)

代码:

UIScrollView+Empty.h
//
//  UIScrollView+Empty.h
//
//  Created by ylh on 2019/11/13.
//  Copyright © 2019年 private. All rights reserved.
//

/*
 1、使用时只需调用此方法,即可在调用SEL: reloadData 时自动判断是否展示空值界面
 2、更多定制可根据此实现方案进行相对应拓展  一般会对emptyView 进行定制操作
 
 exp:
 
 [_tableView setEmptyViewWithImage:[UIImage imageNamed:@"noresult"] title:@"某有数据啊,点击我再刷新一次看看?" eventBlock:^{
 NSLog(@"点击了,去刷新喽");
 }];
 
 */

#import <UIKit/UIKit.h>


/**
 空值界面类型

 - EmptyImage: 只显示图片
 - EmptyTitle: 只显示文字
 - EmptyImageAndTitle: 显示图片和文字
 */
typedef NS_ENUM(NSInteger,EmptyType) {
    EmptyImage,
    EmptyTitle,
    EmptyImageAndTitle
};

/**
 点击事件回调
 */
typedef void(^EmptyEventBlock)(void);

NS_ASSUME_NONNULL_BEGIN

@interface UIScrollView (Empty)
@property (nonatomic,strong)UIView *emptyView;
@property (nonatomic,copy)EmptyEventBlock emptyEventBlock;
/**
 设置空值界面
 根据传入内容决定展示风格:  图片、文字、文字和图片、是否添加点击事件

 @param image 展示图片 可为空
 @param title 展示文字 可为空
 @param block 点击事件block 可为空  为空则没有点击事件
 */
- (void)setEmptyViewWithImage:(UIImage *_Nullable)image title:(NSString *_Nullable)title eventBlock:(EmptyEventBlock _Nullable)block;
@end


@interface UITableView (Empty)

@end

@interface UICollectionView (Empty)

@end
NS_ASSUME_NONNULL_END

UIScrollView+Empty.m
//
//  UIScrollView+Empty.m
//
//  Created by ylh on 2019/11/13.
//  Copyright © 2019年 private. All rights reserved.
//

#import "UIScrollView+Empty.h"
#import <objc/runtime.h>

static NSString *kEmptyViewKey = @"EmptyViewKey";
static NSString *kEmptyEventBlockKey = @"EmptyEventBlockKey";

@implementation UIScrollView (Empty)


- (void)setEmptyView:(UIView *)emptyView {
    objc_setAssociatedObject(self, &kEmptyViewKey, emptyView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)emptyView {
    return objc_getAssociatedObject(self, &kEmptyViewKey);
}

- (void)setEmptyEventBlock:(EmptyEventBlock)emptyEventBlock {
    objc_setAssociatedObject(self, &kEmptyEventBlockKey, emptyEventBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (EmptyEventBlock)emptyEventBlock {
    return objc_getAssociatedObject(self, &kEmptyEventBlockKey);
}

- (void)setEmptyViewWithImage:(UIImage *)image title:(NSString *)title eventBlock:(EmptyEventBlock)block {
    if (!self.emptyView) {
        
        EmptyType type;
        if (image && !title) {
            type = EmptyImage;
        }else if (!image && title) {
            type = EmptyTitle;
        }else if (image && title) {
            type = EmptyImageAndTitle;
        }else{
            NSLog(@"参数错误,不能使用此功能");
            return;
        }
        
        UIButton *emptyBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        block ? (emptyBtn.enabled = YES) : (emptyBtn.enabled = NO);
        (emptyBtn.enabled = YES) ? [emptyBtn addTarget:self action:@selector(emptyClick:) forControlEvents:UIControlEventTouchUpInside]:nil;
        (emptyBtn.enabled = YES) ? (self.emptyEventBlock = block) : nil;
        
        switch (type) {
            case EmptyImage:
            {
                emptyBtn.frame = CGRectMake((self.frame.size.width-image.size.width)/2, (self.frame.size.height-image.size.height)/2, image.size.width, image.size.height);
                [emptyBtn setImage:image forState:UIControlStateNormal];
            }
                break;
            case EmptyTitle:
            {
                CGRect titleRect = [title boundingRectWithSize:CGSizeMake(self.frame.size.width, 20) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont systemFontOfSize:16.0f],NSFontAttributeName, nil] context:nil];
                emptyBtn.frame = CGRectMake((self.frame.size.width-titleRect.size.width)/2, (self.frame.size.height-titleRect.size.height)/2, titleRect.size.width, titleRect.size.height);
                [emptyBtn setTitle:title forState:UIControlStateNormal];
                [emptyBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            }
                break;
            case EmptyImageAndTitle:
            {
                CGFloat btnWidth = 0;
                CGFloat btnHeight = 0;
                UIFont *titleFont = [UIFont systemFontOfSize:16.0f];
                CGRect titleRect = [title boundingRectWithSize:CGSizeMake(self.frame.size.width, 20) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:[NSDictionary dictionaryWithObjectsAndKeys:titleFont,NSFontAttributeName, nil] context:nil];
                titleRect.size.width <= image.size.width ? (btnWidth = image.size.width) : (btnWidth = titleRect.size.width);
                btnHeight = image.size.height + titleRect.size.height + 10;
                emptyBtn.frame = CGRectMake((self.frame.size.width-btnWidth)/2, (self.frame.size.height-btnHeight)/2, btnWidth, btnHeight);
                [emptyBtn setImage:image forState:UIControlStateNormal];
                [emptyBtn setTitle:title forState:UIControlStateNormal];
                [emptyBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
                [emptyBtn setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 30, -titleRect.size.width)];
                [emptyBtn setTitleEdgeInsets:UIEdgeInsetsMake(btnHeight-20, -image.size.width, 0, 0)];
                emptyBtn.titleLabel.font = titleFont;
            }
                break;
            default:
                break;
        }
        self.emptyView = emptyBtn;
        [self addSubview:self.emptyView];
    }
    [self bringSubviewToFront:self.emptyView];
}

/// 更新界面,是否展示空值视图
- (void)refreshEmptyView {
    NSInteger section = 1;
    NSInteger rows = 0;
    if ([self isKindOfClass:[UITableView class]]) {
        
        UITableView *tableView = (UITableView *)self;
        if (tableView.dataSource != nil && [tableView.dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
            
            if ([tableView.dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
                section = [tableView.dataSource numberOfSectionsInTableView:tableView];
            }
            for (int i = 0; i < section; i++) {
                rows += [tableView.dataSource tableView:tableView numberOfRowsInSection:i];
            }
        }
    }else if ([self isKindOfClass:[UICollectionView class]]) {
        
        UICollectionView *collectionView = (UICollectionView *)self;
        if (collectionView.dataSource != nil && [collectionView.dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]) {
            
            if ([collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
                section = [collectionView.dataSource numberOfSectionsInCollectionView:collectionView];
            }
            for (int i = 0; i < section; i++) {
                rows += [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:i];
            }
        }
    }
    
    if (rows == 0) {
        self.emptyView.hidden = NO;
        [self bringSubviewToFront:self.emptyView];
    }else{
        self.emptyView.hidden = YES;
        return;
    }
}

#pragma mark -- action

- (void)emptyClick:(UIButton *)sender {
    if (self.emptyEventBlock) {
        self.emptyEventBlock();
    }
}

#pragma mark -- swizzing
+ (void)hookClass:(Class)classObject originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Class class = classObject;
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    if (didAddMethod) {
        originalMethod = class_getInstanceMethod(class, originalSelector);
    }
    method_exchangeImplementations(swizzledMethod, originalMethod);
}

@end


@implementation UITableView (Empty)

+ (void)load {
    [super load];
    [self hookClass:[self class] originalSelector:@selector(reloadData) swizzledSelector:@selector(customReloadData)];
}
- (void)customReloadData {
    [self customReloadData];
    [self refreshEmptyView];
}

@end


@implementation UICollectionView (Empty)

+ (void)load {
    [super load];
    [self hookClass:[self class] originalSelector:@selector(reloadData) swizzledSelector:@selector(customReloadData)];
}

- (void)customReloadData {
    [self customReloadData];
    [self refreshEmptyView];
}

@end

如果对你帮助或者思路上的启发,帮忙点个赞呗!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353