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