• TableView 无数据时展示占位视图


    UITableView+NoDataView.m

    #import "UITableView+NoDataView.h"
    #import "NoDataView.h"
    #import <objc/runtime.h>
    
    @protocol TableViewDelegate <NSObject>
    @optional
    - (UIView *)noDataView;
    - (UIImage *)noDataViewImage;
    - (NSString *)noDataViewMessage;
    - (UIColor *)noDataViewMessageColor;
    - (NSNumber *)noDataViewCenterYOffset;
    
    @end
    
    @implementation UITableView (NoDataView)
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method reloadData = class_getInstanceMethod(self, @selector(reloadData));
            Method replace_reloadData = class_getInstanceMethod(self, @selector(replace_reloadData));
            method_exchangeImplementations(reloadData, replace_reloadData);
            
            Method dealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
            Method replace_dealloc = class_getInstanceMethod(self, @selector(replace_dealloc));
            method_exchangeImplementations(dealloc, replace_dealloc);
        });
    }
    
    - (void)replace_reloadData {
        [self replace_reloadData];
        
        //  忽略第一次加载
        if (![self isInitFinish]) {
            [self havingData:YES];
            [self setIsInitFinish:YES];
            return ;
        }
        
        //  刷新完成之后检测数据量
        dispatch_async(dispatch_get_main_queue(), ^{
            
            NSInteger numberOfSections = [self numberOfSections];
            BOOL havingData = NO;
            for (NSInteger i = 0; i < numberOfSections; i++) {
                if ([self numberOfRowsInSection:i] > 0) {
                    havingData = YES;
                    break;
                }
            }
            
            [self havingData:havingData];
        });
    }
    
    
    /**
     展示占位图
     */
    - (void)havingData:(BOOL)havingData {
        
        //  不需要显示占位图
        if (havingData) {
            [self freeNoDataViewIfNeeded];
            self.backgroundView = nil;
            return ;
        }
        
        //  不需要重复创建
        if (self.backgroundView) {
            return ;
        }
        
        //  自定义了占位图
        if ([self.delegate respondsToSelector:@selector(noDataView)]) {
            self.backgroundView = [self.delegate performSelector:@selector(noDataView)];
            return ;
        }
        
        //  使用自带的
        UIImage  * img   = nil;
        NSString * msg   = @"暂无数据";
        UIColor  * color = [UIColor lightGrayColor];
        CGFloat  offset  = 0;
        
        //  获取图片
        if ([self.delegate    respondsToSelector:@selector(noDataViewImage)]) {
            img = [self.delegate performSelector:@selector(noDataViewImage)];
        }
        //  获取文字
        if ([self.delegate    respondsToSelector:@selector(noDataViewMessage)]) {
            msg = [self.delegate performSelector:@selector(noDataViewMessage)];
        }
        //  获取颜色
        if ([self.delegate      respondsToSelector:@selector(noDataViewMessageColor)]) {
            color = [self.delegate performSelector:@selector(noDataViewMessageColor)];
        }
        //  获取偏移量
        if ([self.delegate        respondsToSelector:@selector(noDataViewCenterYOffset)]) {
            offset = [[self.delegate performSelector:@selector(noDataViewCenterYOffset)] floatValue];
        }
        
        //  创建占位图
        self.backgroundView = [self defaultNoDataViewWithImage  :img message:msg color:color offsetY:offset];
    }
    
    /**
     默认的占位图
     */
    - (UIView *)defaultNoDataViewWithImage:(UIImage *)image message:(NSString *)message color:(UIColor *)color offsetY:(CGFloat)offset {
        
        //  计算位置, 垂直居中, 图片默认中心偏上.
        CGFloat sW = self.bounds.size.width;
        CGFloat cX = sW / 2;
        CGFloat cY = self.bounds.size.height * (1 - 0.618) + offset;
        CGFloat iW = image.size.width;
        CGFloat iH = image.size.height;
        
        //  图片
        UIImageView *imgView = [[UIImageView alloc] init];
        imgView.frame        = CGRectMake(cX - iW / 2, cY - iH / 2, iW, iH);
        imgView.image        = image;
        
        //  文字
        UILabel *label       = [[UILabel alloc] init];
        label.font           = [UIFont systemFontOfSize:17];
        label.textColor      = color;
        label.text           = message;
        label.textAlignment  = NSTextAlignmentCenter;
        label.frame          = CGRectMake(0, CGRectGetMaxY(imgView.frame) + 24, sW, label.font.lineHeight);
        
        //  视图
        NoDataView *view   = [[NoDataView alloc] init];
        [view addSubview:imgView];
        [view addSubview:label];
        
        //  实现跟随 TableView 滚动
        [view addObserver:self forKeyPath:kNoDataViewObserveKeyPath options:NSKeyValueObservingOptionNew context:nil];
        return view;
    }
    
    
    /**
     监听
     */
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:kNoDataViewObserveKeyPath]) {
            
            /**
             在 TableView 滚动 ContentOffset 改变时, 会同步改变 backgroundView 的 frame.origin.y
             可以实现, backgroundView 位置相对于 TableView 不动, 但是我们希望
             backgroundView 跟随 TableView 的滚动而滚动, 只能强制设置 frame.origin.y 永远为 0
             兼容 MJRefresh
             */
            CGRect frame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue];
            if (frame.origin.y != 0) {
                frame.origin.y  = 0;
                self.backgroundView.frame = frame;
            }
        }
    }
    
    
    
    #pragma mark - 属性
    
    // 加载完数据的标记属性名
    static NSString * const kTableViewPropertyInitFinish = @"kTableViewPropertyInitFinish";
    
    /**
     设置已经加载完成数据了
     */
    - (void)setIsInitFinish:(BOOL)finish {
        objc_setAssociatedObject(self, &kTableViewPropertyInitFinish, @(finish), OBJC_ASSOCIATION_ASSIGN);
    }
    
    /**
     是否已经加载完成数据
     */
    - (BOOL)isInitFinish {
        id obj = objc_getAssociatedObject(self, &kTableViewPropertyInitFinish);
        return [obj boolValue];
    }
    
    /**
     移除 KVO 监听
     */
    - (void)freeNoDataViewIfNeeded {
        
        if ([self.backgroundView isKindOfClass:[NoDataView class]]) {
            [self.backgroundView removeObserver:self forKeyPath:kNoDataViewObserveKeyPath context:nil];
        }
    }
    
    - (void)replace_dealloc {
        [self freeNoDataViewIfNeeded];
        [self replace_dealloc];
        NSLog(@"TableView 视图正常销毁");
    }
    
    
    @end

    UICollectionView+NoDataView.m

    #import "UICollectionView+NoDataView.h"
    #import <objc/runtime.h>
    #import "NoDataView.h"
    
    /**
     消除警告
     */
    @protocol CollectionViewDelegate <NSObject>
    @optional
    - (UIView   *)noDataView;
    - (UIImage  *)noDataViewImage;
    - (NSString *)noDataViewMessage;
    - (UIColor  *)noDataViewMessageColor;
    - (NSNumber *)noDataViewCenterYOffset;
    @end
    
    @implementation UICollectionView (NoDataView)
    /**
     加载时, 交换方法
     */
    + (void)load {
        //  只交换一次
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            Method reloadData    = class_getInstanceMethod(self, @selector(reloadData));
            Method replace_reloadData = class_getInstanceMethod(self, @selector(replace_reloadData));
            method_exchangeImplementations(reloadData, replace_reloadData);
            
            Method dealloc       = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
            Method replace_dealloc    = class_getInstanceMethod(self, @selector(replace_dealloc));
            method_exchangeImplementations(dealloc, replace_dealloc);
        });
    }
    
    /**
     在 ReloadData 的时候检查数据
     */
    - (void)replace_reloadData {
        
        [self replace_reloadData];
        
        //  忽略第一次加载
        if (![self isInitFinish]) {
            [self havingData:YES];
            [self setIsInitFinish:YES];
            return ;
        }
        //  刷新完成之后检测数据量
        dispatch_async(dispatch_get_main_queue(), ^{
            
            NSInteger numberOfSections = [self numberOfSections];
            BOOL havingData = NO;
            for (NSInteger i = 0; i < numberOfSections; i++) {
                if ([self numberOfItemsInSection:i] > 0) {
                    havingData = YES;
                    break;
                }
            }
            
            [self havingData:havingData];
        });
    }
    
    /**
     展示占位图
     */
    - (void)havingData:(BOOL)havingData {
        
        //  不需要显示占位图
        if (havingData) {
            [self freeNoDataViewIfNeeded];
            self.backgroundView = nil;
            return ;
        }
        
        //  不需要重复创建
        if (self.backgroundView) {
            return ;
        }
        
        //  自定义了占位图
        if ([self.delegate respondsToSelector:@selector(noDataView)]) {
            self.backgroundView = [self.delegate performSelector:@selector(noDataView)];
            return ;
        }
        
        //  使用自带的
        UIImage  *img   = nil;
        NSString *msg   = @"暂无数据";
        UIColor  *color = [UIColor lightGrayColor];
        CGFloat  offset = 0;
        
        //  获取图片
        if ([self.delegate    respondsToSelector:@selector(noDataViewImage)]) {
            img = [self.delegate performSelector:@selector(noDataViewImage)];
        }
        //  获取文字
        if ([self.delegate    respondsToSelector:@selector(noDataViewMessage)]) {
            msg = [self.delegate performSelector:@selector(noDataViewMessage)];
        }
        //  获取颜色
        if ([self.delegate      respondsToSelector:@selector(noDataViewMessageColor)]) {
            color = [self.delegate performSelector:@selector(noDataViewMessageColor)];
        }
        //  获取偏移量
        if ([self.delegate        respondsToSelector:@selector(noDataViewCenterYOffset)]) {
            offset = [[self.delegate performSelector:@selector(noDataViewCenterYOffset)] floatValue];
        }
        
        //  创建占位图
        self.backgroundView = [self defaultNoDataViewWithImage  :img message:msg color:color offsetY:offset];
    }
    
    /**
     默认的占位图
     */
    - (UIView *)defaultNoDataViewWithImage:(UIImage *)image message:(NSString *)message color:(UIColor *)color offsetY:(CGFloat)offset {
        
        //  计算位置, 垂直居中, 图片默认中心偏上.
        CGFloat sW = self.bounds.size.width;
        CGFloat cX = sW / 2;
        CGFloat cY = self.bounds.size.height * (1 - 0.618) + offset;
        CGFloat iW = image.size.width;
        CGFloat iH = image.size.height;
        
        //  图片
        UIImageView *imgView = [[UIImageView alloc] init];
        imgView.frame        = CGRectMake(cX - iW / 2, cY - iH / 2, iW, iH);
        imgView.image        = image;
        
        //  文字
        UILabel *label       = [[UILabel alloc] init];
        label.font           = [UIFont systemFontOfSize:17];
        label.textColor      = color;
        label.text           = message;
        label.textAlignment  = NSTextAlignmentCenter;
        label.frame          = CGRectMake(0, CGRectGetMaxY(imgView.frame) + 24, sW, label.font.lineHeight);
        
        //  视图
        NoDataView * view   = [[NoDataView alloc] init];
        [view addSubview:imgView];
        [view addSubview:label];
        
        //  实现跟随 collectionView 滚动
        [view addObserver:self forKeyPath:kNoDataViewObserveKeyPath options:NSKeyValueObservingOptionNew context:nil];
        return view;
    }
    
    
    /**
     监听
     */
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:kNoDataViewObserveKeyPath]) {
            
            /**
             在 collectionView 滚动 ContentOffset 改变时, 会同步改变 backgroundView 的 frame.origin.y
             可以实现, backgroundView 位置相对于 collectionView 不动, 但是我们希望
             backgroundView 跟随 collectionView 的滚动而滚动, 只能强制设置 frame.origin.y 永远为 0
             兼容 MJRefresh
             */
            CGRect frame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue];
            if (frame.origin.y != 0) {
                frame.origin.y  = 0;
                self.backgroundView.frame = frame;
            }
        }
    }
    
    #pragma mark - 属性
    
    /// 加载完数据的标记属性名
    static NSString * const kCollectionViewPropertyInitFinish = @"kCollectionViewPropertyInitFinish";
    
    /**
     设置已经加载完成数据了
     */
    - (void)setIsInitFinish:(BOOL)finish {
        objc_setAssociatedObject(self, &kCollectionViewPropertyInitFinish, @(finish), OBJC_ASSOCIATION_ASSIGN);
    }
    
    /**
     是否已经加载完成数据
     */
    - (BOOL)isInitFinish {
        id obj = objc_getAssociatedObject(self, &kCollectionViewPropertyInitFinish);
        return [obj boolValue];
    }
    
    /**
     移除 KVO 监听
     */
    - (void)freeNoDataViewIfNeeded {
        
        if ([self.backgroundView isKindOfClass:[NoDataView class]]) {
            [self.backgroundView removeObserver:self forKeyPath:kNoDataViewObserveKeyPath context:nil];
        }
    }
    
    - (void)replace_dealloc {
        [self freeNoDataViewIfNeeded];
        [self replace_dealloc];
        NSLog(@"CollectionView 视图正常销毁");
    }
    @end

    NoDataView.h

    #import <UIKit/UIKit.h>
    
    extern NSString * const kNoDataViewObserveKeyPath;
    
    @interface NoDataView : UIView
    
    @end

    NoDataView.m

    #import "NoDataView.h"
    NSString * const kNoDataViewObserveKeyPath = @"frame";
    @implementation NoDataView
    
    - (void)dealloc {
        NSLog(@"占位视图正常销毁");
    }
    
    @end

    调用

    #import "ViewController.h"
    #import "MJRefresh.h"
    
    @interface ViewController () <UITableViewDelegate, UITableViewDataSource>
    @property (nonatomic, strong) UITableView * tableView;
    @property (nonatomic, strong) NSMutableArray * dataArr;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        self.tableView.delegate = self;
        self.tableView.dataSource = self;
        [self.view addSubview:self.tableView];
        self.tableView.tableFooterView = [UIView new];
        
        
        __weak typeof(self) weakSelf = self;
        self.tableView.mj_header  = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
            [weakSelf loadData];
        }];
    }
    
    - (void)loadData {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.tableView.mj_header endRefreshing];
            [self.tableView reloadData];
        });
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return 0;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        return [UITableViewCell new];
    }
    
    
    #pragma mark - TableView 占位图
    
    - (UIImage *)noDataViewImage {
        return [UIImage imageNamed:@"note_list_no_data"];
    }
    
    - (NSString *)noDataViewMessage {
        return @"都用起来吧, 起飞~";
    }
    
    - (UIColor *)noDataViewMessageColor {
        return [UIColor blackColor];
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    
    @end
  • 相关阅读:
    黑马程序员__OC三大特性
    黑马程序员___OC类和对象
    黑马程序员___预处理指令
    黑马程序员___数据类型总结
    黑马程序员__指针
    黑马程序员__C语言__函数__static和extern
    黑马程序员__C语言__流程控制__选择结构
    黑马程序员__C语言__循环结构
    入园随笔
    Fiddler中抓取不到Jmeter模拟的请求包。
  • 原文地址:https://www.cnblogs.com/fengmin/p/8177353.html
Copyright © 2020-2023  润新知