UITableView+XY.m 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. //
  2. // UITableView+XY.m
  3. // XYTableViewNoDataView
  4. //
  5. // Created by 韩元旭 on 2017/7/19.
  6. // Copyright © 2017年 iCourt. All rights reserved.
  7. //
  8. #import "UITableView+XY.h"
  9. #import "XYNoDataView.h"
  10. #import <objc/runtime.h>
  11. /**
  12. 消除警告
  13. */
  14. @protocol XYTableViewDelegate <NSObject>
  15. @optional
  16. - (UIView *)xy_noDataView; // 完全自定义占位图
  17. - (UIImage *)xy_noDataViewImage; // 使用默认占位图, 提供一张图片, 可不提供, 默认不显示
  18. - (NSString *)xy_noDataViewMessage; // 使用默认占位图, 提供显示文字, 可不提供, 默认为暂无数据
  19. - (UIColor *)xy_noDataViewMessageColor; // 使用默认占位图, 提供显示文字颜色, 可不提供, 默认为灰色
  20. - (NSNumber *)xy_noDataViewCenterYOffset; // 使用默认占位图, CenterY 向下的偏移量
  21. @end
  22. @implementation UITableView (XY)
  23. /**
  24. 加载时, 交换方法
  25. */
  26. + (void)load {
  27. // 只交换一次
  28. static dispatch_once_t onceToken;
  29. dispatch_once(&onceToken, ^{
  30. Method reloadData = class_getInstanceMethod(self, @selector(reloadData));
  31. Method xy_reloadData = class_getInstanceMethod(self, @selector(xy_reloadData));
  32. method_exchangeImplementations(reloadData, xy_reloadData);
  33. Method dealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
  34. Method xy_dealloc = class_getInstanceMethod(self, @selector(xy_dealloc));
  35. method_exchangeImplementations(dealloc, xy_dealloc);
  36. });
  37. }
  38. /**
  39. 在 ReloadData 的时候检查数据
  40. */
  41. - (void)xy_reloadData {
  42. [self xy_reloadData];
  43. // 忽略第一次加载
  44. if (![self isInitFinish]) {
  45. [self xy_havingData:YES];
  46. [self setIsInitFinish:YES];
  47. return ;
  48. }
  49. // 刷新完成之后检测数据量
  50. dispatch_async(dispatch_get_main_queue(), ^{
  51. NSInteger numberOfSections = [self numberOfSections];
  52. BOOL havingData = NO;
  53. for (NSInteger i = 0; i < numberOfSections; i++) {
  54. if ([self numberOfRowsInSection:i] > 0) {
  55. havingData = YES;
  56. break;
  57. }
  58. }
  59. [self xy_havingData:havingData];
  60. });
  61. }
  62. /**
  63. 展示占位图
  64. */
  65. - (void)xy_havingData:(BOOL)havingData {
  66. // 不需要显示占位图
  67. if (havingData || self.tag == 1107) {
  68. [self freeNoDataViewIfNeeded];
  69. self.backgroundView = nil;
  70. return ;
  71. }
  72. // 不需要重复创建
  73. if (self.backgroundView) {
  74. return ;
  75. }
  76. // 自定义了占位图
  77. if ([self.delegate respondsToSelector:@selector(xy_noDataView)]) {
  78. self.backgroundView = [self.delegate performSelector:@selector(xy_noDataView)];
  79. return ;
  80. }
  81. // 使用自带的
  82. UIImage *img = [UIImage imageNamed:@"暂无数据"];
  83. NSString *msg = @"亲~暂无任何内容";
  84. UIColor *color = [UIColor lightGrayColor];
  85. CGFloat offset = 0;
  86. // 获取图片
  87. if ([self.delegate respondsToSelector:@selector(xy_noDataViewImage)]) {
  88. img = [self.delegate performSelector:@selector(xy_noDataViewImage)];
  89. }
  90. // 获取文字
  91. if ([self.delegate respondsToSelector:@selector(xy_noDataViewMessage)]) {
  92. msg = [self.delegate performSelector:@selector(xy_noDataViewMessage)];
  93. }
  94. // 获取颜色
  95. if ([self.delegate respondsToSelector:@selector(xy_noDataViewMessageColor)]) {
  96. color = [self.delegate performSelector:@selector(xy_noDataViewMessageColor)];
  97. }
  98. // 获取偏移量
  99. if ([self.delegate respondsToSelector:@selector(xy_noDataViewCenterYOffset)]) {
  100. offset = [[self.delegate performSelector:@selector(xy_noDataViewCenterYOffset)] floatValue];
  101. }
  102. // 创建占位图
  103. self.backgroundView = [self xy_defaultNoDataViewWithImage :img message:msg color:color offsetY:offset];
  104. }
  105. /**
  106. 默认的占位图
  107. */
  108. - (UIView *)xy_defaultNoDataViewWithImage:(UIImage *)image message:(NSString *)message color:(UIColor *)color offsetY:(CGFloat)offset {
  109. // 计算位置, 垂直居中, 图片默认中心偏上.
  110. CGFloat sW = self.bounds.size.width;
  111. CGFloat cX = sW / 2;
  112. CGFloat cY = self.bounds.size.height * (1 - 0.618) + offset;
  113. CGFloat iW = image.size.width;
  114. CGFloat iH = image.size.height;
  115. // 图片
  116. UIImageView *imgView = [[UIImageView alloc] init];
  117. imgView.frame = CGRectMake(cX - iW / 2, cY - iH / 2, iW, iH);
  118. imgView.image = image;
  119. // 文字
  120. UILabel *label = [[UILabel alloc] init];
  121. label.font = [UIFont systemFontOfSize:17];
  122. label.textColor = color;
  123. label.text = message;
  124. label.textAlignment = NSTextAlignmentCenter;
  125. label.frame = CGRectMake(0, CGRectGetMaxY(imgView.frame) + 24, sW, label.font.lineHeight);
  126. // 视图
  127. XYNoDataView *view = [[XYNoDataView alloc] init];
  128. [view addSubview:imgView];
  129. [view addSubview:label];
  130. // 实现跟随 TableView 滚动
  131. [view addObserver:self forKeyPath:kXYNoDataViewObserveKeyPath options:NSKeyValueObservingOptionNew context:nil];
  132. return view;
  133. }
  134. /**
  135. 监听
  136. */
  137. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  138. if ([keyPath isEqualToString:kXYNoDataViewObserveKeyPath]) {
  139. /**
  140. 在 TableView 滚动 ContentOffset 改变时, 会同步改变 backgroundView 的 frame.origin.y
  141. 可以实现, backgroundView 位置相对于 TableView 不动, 但是我们希望
  142. backgroundView 跟随 TableView 的滚动而滚动, 只能强制设置 frame.origin.y 永远为 0
  143. 兼容 MJRefresh
  144. */
  145. CGRect frame = [[change objectForKey:NSKeyValueChangeNewKey] CGRectValue];
  146. if (frame.origin.y != 0) {
  147. frame.origin.y = 0;
  148. self.backgroundView.frame = frame;
  149. }
  150. }
  151. }
  152. #pragma mark - 属性
  153. /**
  154. 设置已经加载完成数据了
  155. */
  156. - (void)setIsInitFinish:(BOOL)finish {
  157. objc_setAssociatedObject(self, @selector(isInitFinish), @(finish), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  158. }
  159. /**
  160. 是否已经加载完成数据
  161. */
  162. - (BOOL)isInitFinish {
  163. id obj = objc_getAssociatedObject(self, _cmd);
  164. return [obj boolValue];
  165. }
  166. /**
  167. 移除 KVO 监听
  168. */
  169. - (void)freeNoDataViewIfNeeded {
  170. if ([self.backgroundView isKindOfClass:[XYNoDataView class]]) {
  171. [self.backgroundView removeObserver:self forKeyPath:kXYNoDataViewObserveKeyPath context:nil];
  172. }
  173. }
  174. - (void)xy_dealloc {
  175. [self freeNoDataViewIfNeeded];
  176. [self xy_dealloc];
  177. NSLog(@"TableView + XY 视图正常销毁");
  178. }
  179. @end