TableView 無數據時展現佔位視圖

UITableView+NoDataView.masync

#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.matom

#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.hspa

#import <UIKit/UIKit.h>

extern NSString * const kNoDataViewObserveKeyPath;

@interface NoDataView : UIView

@end

NoDataView.mcode

#import "NoDataView.h"
NSString * const kNoDataViewObserveKeyPath = @"frame";
@implementation NoDataView

- (void)dealloc {
    NSLog(@"佔位視圖正常銷燬");
}

@end

 

調用orm

#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
相關文章
相關標籤/搜索