zoukankan      html  css  js  c++  java
  • 使用Runtime的hook技术为tableView实现一个空白缺省页

    一、介绍

    UITableView和UICollectionView是iOS开发最常用的控件,也是必不可少的控件,这两个控件基本能实现各种各样的界面样式。

    它们都是通过代理模式监测数据源的有无对数据进行UI的展示或隐藏。

    如果没有数据时,UITableView和UICollectionView可能会展示了一个空白的页面,没有任何提示,逻辑上是没有问题的,但是对于用户而言,显得不够友好。

    此时,最好做一个优化,也即没有数据时,刷新列表后提供一个缺省页。

    给UITableView和UICollectionView添加一个缺省页,实现的方式有很多种,在这里,我首推采用运行时的hook技术来实现,第三方框架DZNEmptyDataSet就是用这个技术。

    本文以UITableView为例,UICollectionView实现原理相同。

    二、思路

    (1)自定义一个缺省页视图NoDataEmptyView,添加图片视图UIImageView和提示文字标签label;

    (2)给UITableView创建一个分类UITableView (ReloadData);

    (3)在UITableView (ReloadData)分类中的load方法中使用hook技术用自定义的xyq_reloadData方法交换系统的reloadData方法;

    (4)在UITableView (ReloadData)分类中使用关联对象的技术关联缺省页视图NoDataEmptyView对象,也即作为属性;

    (5)在UITableView (ReloadData)分类中的xyq_reloadData方法中添加缺省页显示或隐藏的逻辑;

    (6)在ViewController中刷新数据时正常调用reloadData方法即可达到实现。

    三、代码

    NoDataEmptyView

    //  NoDataEmptyView.h
    //  运行时
    //
    //  Created by 夏远全 on 2019/10/11.
    //  Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
    //
    
    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface NoDataEmptyView : UIView
    
    @end
    
    NS_ASSUME_NONNULL_END
    //
    //  NoDataEmptyView.m
    //  运行时
    //
    //  Created by 夏远全 on 2019/10/11.
    //  Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
    //
    
    #import "NoDataEmptyView.h"
    
    @interface NoDataEmptyView ()
    @property (nonatomic, strong) UIImageView *imageView;
    @property (nonatomic, strong) UILabel     *label;
    @end
    
    @implementation NoDataEmptyView
    
    -(instancetype)initWithFrame:(CGRect)frame
    {
        if (self = [super initWithFrame:frame]) {
            [self setup];
        }
        return self;
    }
    
    -(void)setup
    {
        self.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.6];
        self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
        self.imageView.image = [UIImage imageNamed:@"empty_body_kong"];
        self.imageView.center = self.center;
        
        self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 40)];
        self.label.text = @"暂无数据";
        self.label.textColor = [UIColor whiteColor];
        self.label.textAlignment = NSTextAlignmentCenter;
        self.label.center = CGPointMake(self.imageView.center.x, CGRectGetMaxY(self.imageView.frame)+40);
        
        [self addSubview:self.imageView];
        [self addSubview:self.label];
    }
    
    @end

    UITableView (ReloadData)

    //
    //  UITableView+ReloadData.h
    //  运行时
    //
    //  Created by 夏远全 on 2019/10/11.
    //  Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
    //
    
    #import <UIKit/UIKit.h>
    #import "NoDataEmptyView.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface UITableView (ReloadData)
    @property (nonatomic, strong) NoDataEmptyView *emptyView;
    @end
    
    NS_ASSUME_NONNULL_END
    //
    //  UITableView+ReloadData.m
    //  运行时
    //
    //  Created by 夏远全 on 2019/10/11.
    //  Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
    //
    
    #import "UITableView+ReloadData.h"
    #import <objc/message.h>
    
    static NSString *const NoDataEmptyViewKey = @"NoDataEmptyViewKey";
    
    @implementation UITableView (ReloadData)
    
    #pragma mark - 交换方法 hook
    +(void)load {
        Method originMethod = class_getInstanceMethod(self, @selector(reloadData));
        Method currentMethod = class_getInstanceMethod(self, @selector(xyq_reloadData));
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            method_exchangeImplementations(originMethod, currentMethod);
        });
    }
    
    #pragma mark - 刷新数据
    -(void)xyq_reloadData {
        [self xyq_reloadData];
        [self fillEmptyView];
    }
    
    #pragma mark - 填充空白页
    -(void)fillEmptyView {
        
        NSInteger sections = 1;
        NSInteger rows = 0;
        
        id <UITableViewDataSource> dataSource = self.dataSource;
        
        if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
            
            sections = [dataSource numberOfSectionsInTableView:self];
            
            if ([dataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) {
                for (int i=0; i<sections; i++) {
                    rows += [dataSource tableView:self numberOfRowsInSection:i];
                }
            }
        }
        
        if (rows == 0) {
            if (![self.subviews containsObject:self.emptyView]) {
                self.emptyView = [[NoDataEmptyView alloc] initWithFrame:self.bounds];
                [self addSubview:self.emptyView];
            }
        }
        else{
            [self.emptyView removeFromSuperview];
        }
    }
    
    
    #pragma mark - 关联对象
    -(void)setEmptyView:(NoDataEmptyView *)emptyView {
        objc_setAssociatedObject(self, &NoDataEmptyViewKey, emptyView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    -(NoDataEmptyView *)emptyView {
        return objc_getAssociatedObject(self, &NoDataEmptyViewKey);
    }
    
    @end

    DataTableViewController

    //
    //  DataTableViewController.m
    //  运行时
    //
    //  Created by 夏远全 on 2019/10/11.
    //  Copyright © 2019 北京华樾教育科技有限公司. All rights reserved.
    //
    
    #import "DataTableViewController.h"
    #import "UITableView+ReloadData.h"
    
    @interface DataTableViewController ()<UITableViewDataSource>
    @property (nonatomic, strong) UITableView    *tableView;
    @property (nonatomic, strong) UIButton       *haveDataBtn;
    @property (nonatomic, strong) UIButton       *clearDataBtn;
    @property (nonatomic, strong) NSMutableArray *dataSource;
    @end
    
    @implementation DataTableViewController
    
    
    #pragma mark - life cycle
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        [self setupNavigation];
        [self setupSubviews];
    }
    
    -(void)setupNavigation
    {
        self.title = @"测试空白页";
        self.view.backgroundColor = [UIColor whiteColor];
    }
    
    -(void)setupSubviews
    {
        [self.view addSubview:self.haveDataBtn];
        [self.view addSubview:self.clearDataBtn];
        [self.view addSubview:self.tableView];
    }
    
    #pragma mark - dataSource methods
    -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return 1;
    }
    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return self.dataSource.count;
    }
    
    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        static NSString *reuserIdentifier = @"cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuserIdentifier];
        if (!cell) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuserIdentifier];
        }
        cell.textLabel.text = self.dataSource[indexPath.row];
        return cell;
    }
    
    #pragma mark - event
    -(void)haveDataBtnAction {
        self.dataSource = @[@"第1行数据",@"第2行数据",@"第3行数据",@"第4行数据",@"第5行数据",@"第6行数据"].mutableCopy;
        [self.tableView reloadData]; //内部调用自己的xyq_reloadData
    }
    
    -(void)clearDataBtnAction {
        [self.dataSource removeAllObjects];
        [self.tableView reloadData]; //内部调用自己的xyq_reloadData
    }
    
    
    #pragma mark - getters
    -(UITableView *)tableView {
        if (!_tableView) {
            CGFloat width = [UIScreen mainScreen].bounds.size.width;
            CGFloat height = [UIScreen mainScreen].bounds.size.height;
            _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64+50, width, height-64-50)];
            _tableView.backgroundColor = [UIColor whiteColor];
            _tableView.tableFooterView = [[UIView alloc] init];
            _tableView.dataSource = self;
        }
        return _tableView;
    }
    
    -(UIButton *)haveDataBtn {
        if (!_haveDataBtn) {
            CGFloat width = [UIScreen mainScreen].bounds.size.width;
            _haveDataBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, 10+64, width/2-20, 30)];
            _haveDataBtn.backgroundColor = [UIColor redColor];
            [_haveDataBtn setTitle:@"显示数据" forState:UIControlStateNormal];
            [_haveDataBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [_haveDataBtn addTarget:self action:@selector(haveDataBtnAction) forControlEvents:UIControlEventTouchUpInside];
        }
        return _haveDataBtn;
    }
    
    -(UIButton *)clearDataBtn {
        if (!_clearDataBtn) {
            CGFloat width = [UIScreen mainScreen].bounds.size.width;
            _clearDataBtn = [[UIButton alloc] initWithFrame:CGRectMake(width/2+10, 10+64, width/2-20, 30)];
            _clearDataBtn.backgroundColor = [UIColor purpleColor];
            [_clearDataBtn setTitle:@"清空数据" forState:UIControlStateNormal];
            [_clearDataBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [_clearDataBtn addTarget:self action:@selector(clearDataBtnAction) forControlEvents:UIControlEventTouchUpInside];
        }
        return _clearDataBtn;
    }
    
    @end 

    四、演示

  • 相关阅读:
    禁用Firefox浏览器中的CSS、Flash及Image加载
    禁用Chrome浏览器中的Image加载
    启用Firefox的同时打开Firebug
    禁用IE的保护模式
    禁用Chrome浏览器的PDF和Flash插件
    屏蔽Chrome的--ignore-certificate-errors提示及禁用扩展插件并实现窗口最大化
    技术文档
    操作工具
    接口自动化
    nmon
  • 原文地址:https://www.cnblogs.com/XYQ-208910/p/11661232.html
Copyright © 2011-2022 走看看