RQTableViewController.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. //
  2. // RQTableViewController.m
  3. // RQCommon
  4. //
  5. // Created by 张嵘 on 2018/11/23.
  6. // Copyright © 2018 张嵘. All rights reserved.
  7. //
  8. #import "RQTableViewController.h"
  9. @interface RQTableViewController ()
  10. /// tableView
  11. @property (nonatomic, readwrite, weak) RQTableView *tableView;
  12. /// contentInset defaul is (64 , 0 , 0 , 0)
  13. @property (nonatomic, readwrite, assign) UIEdgeInsets contentInset;
  14. /// 视图模型
  15. @property (nonatomic, readonly, strong) RQTableViewModel *viewModel;
  16. @end
  17. @implementation RQTableViewController
  18. @dynamic viewModel;
  19. - (void)dealloc {
  20. // set nil
  21. _tableView.dataSource = nil;
  22. _tableView.delegate = nil;
  23. }
  24. /// init
  25. - (instancetype)initWithViewModel:(RQTableViewModel *)viewModel {
  26. self = [super initWithViewModel:viewModel];
  27. if (self) {
  28. if ([viewModel shouldRequestRemoteDataOnViewDidLoad]) {
  29. @weakify(self)
  30. [[self rac_signalForSelector:@selector(viewDidLoad)] subscribeNext:^(id x) {
  31. @strongify(self)
  32. /// 请求第一页的网络数据
  33. [[self.viewModel.requestRemoteDataCommand execute:@1] subscribeCompleted:^{
  34. if (self.viewModel.dataSource.count >= self.viewModel.perPage) {
  35. self.viewModel.page = 2;
  36. }
  37. }];
  38. }];
  39. }
  40. }
  41. return self;
  42. }
  43. - (void)viewDidLoad
  44. {
  45. [super viewDidLoad];
  46. // 设置子控件
  47. [self _su_setupSubViews];
  48. }
  49. /// override
  50. - (void)bindViewModel
  51. {
  52. [super bindViewModel];
  53. /// observe viewModel's dataSource
  54. @weakify(self)
  55. [[RACObserve(self.viewModel, dataSource)
  56. deliverOnMainThread]
  57. subscribeNext:^(id x) {
  58. @strongify(self)
  59. // 刷新数据
  60. [self reloadData];
  61. }];
  62. /// 隐藏emptyView
  63. [self.viewModel.requestRemoteDataCommand.executing subscribeNext:^(NSNumber *executing) {
  64. @strongify(self)
  65. UIView *emptyDataSetView = [self.tableView.subviews.rac_sequence objectPassingTest:^(UIView *view) {
  66. return [NSStringFromClass(view.class) isEqualToString:@"DZNEmptyDataSetView"];
  67. }];
  68. emptyDataSetView.alpha = 1.0 - executing.floatValue;
  69. }];
  70. // [self.viewModel.requestRemoteDataCommand.executionSignals.switchToLatest subscribeNext:^(id _) {
  71. // @strongify(self);
  72. // /// 有网络
  73. // self.viewModel.disableNetwork = NO;
  74. // }];
  75. //
  76. // [self.viewModel.requestRemoteDataCommand.errors subscribeNext:^(id _) {
  77. // @strongify(self);
  78. // /// 有无网络
  79. // self.viewModel.disableNetwork = !self.viewModel.services.client.reachabilityManager.reachable;
  80. // }];
  81. }
  82. #pragma mark - 设置子控件
  83. /// setup add `_su_` avoid sub class override it
  84. - (void)_su_setupSubViews{
  85. // set up tableView
  86. /// RQ FIXED: 纯代码布局,子类如果重新布局,建议用Masonry重新设置约束
  87. RQTableView *tableView = [[RQTableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:self.viewModel.style];
  88. tableView.backgroundColor = RQ_LIST_BACKGROUNDCOLOR;
  89. tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  90. // set delegate and dataSource
  91. tableView.delegate = self;
  92. tableView.dataSource = self;
  93. [self.view addSubview:tableView];
  94. /// 占位符
  95. // tableView.emptyDataSetDelegate = self;
  96. // tableView.emptyDataSetSource = self;
  97. // [tableView mas_makeConstraints:^(MASConstraintMaker *make) {
  98. // make.edges.mas_equalTo(UIEdgeInsetsZero);
  99. // }];
  100. self.tableView = tableView;
  101. tableView.contentInset = self.contentInset;
  102. // 注册cell
  103. [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];
  104. /// RQ FIXED: 这里需要强制布局一下界面,解决由于设置了tableView的contentInset,然而contentOffset始终是(0,0)的bug 但是这样会导致 tableView 刷新一次,从而导致子类在 viewDidLoad 无法及时注册的cell,从而会有Crash的隐患
  105. // [self.tableView layoutIfNeeded];
  106. // [self.tableView setNeedsLayout];
  107. // [self.tableView updateConstraintsIfNeeded];
  108. // [self.tableView setNeedsUpdateConstraints];
  109. // [self.view layoutIfNeeded];
  110. /// 添加加载和刷新控件
  111. if (self.viewModel.shouldPullDownToRefresh) {
  112. /// 下拉刷新
  113. @weakify(self);
  114. [self.tableView rq_addHeaderRefresh:^(MJRefreshNormalHeader *header) {
  115. /// 加载下拉刷新的数据
  116. @strongify(self);
  117. [self tableViewDidTriggerHeaderRefresh];
  118. }];
  119. // [self.tableView.mj_header beginRefreshing];
  120. }
  121. if (self.viewModel.shouldPullUpToLoadMore) {
  122. /// 上拉加载
  123. @weakify(self);
  124. [self.tableView rq_addFooterRefresh:^(MJRefreshAutoNormalFooter *footer) {
  125. /// 加载上拉刷新的数据
  126. @strongify(self);
  127. [self tableViewDidTriggerFooterRefresh];
  128. }];
  129. /// 隐藏footer or 无更多数据
  130. RAC(self.tableView.mj_footer, hidden) = [[RACObserve(self.viewModel, dataSource)
  131. deliverOnMainThread]
  132. map:^(NSArray *dataSource) {
  133. @strongify(self)
  134. NSUInteger count = dataSource.count;
  135. /// 无数据,默认隐藏mj_footer
  136. if (count == 0) return @1;
  137. /// 无更多数据,隐藏mj_footer
  138. if (count == self.viewModel.dataTotalNum) return @1;
  139. if (self.viewModel.shouldEndRefreshingWithNoMoreData) return @(0);
  140. /// because of
  141. return (count % self.viewModel.perPage)?@1:@0;
  142. }];
  143. }
  144. if (@available(iOS 11.0, *)) {
  145. /// RQ: 适配 iPhone X + iOS 11,
  146. RQAdjustsScrollViewInsets_Never(tableView);
  147. /// iOS 11上发生tableView顶部有留白,原因是代码中只实现了heightForHeaderInSection方法,而没有实现viewForHeaderInSection方法。那样写是不规范的,只实现高度,而没有实现view,但代码这样写在iOS 11之前是没有问题的,iOS 11之后应该是由于开启了估算行高机制引起了bug。
  148. tableView.estimatedRowHeight = 0;
  149. tableView.estimatedSectionHeaderHeight = 0;
  150. tableView.estimatedSectionFooterHeight = 0;
  151. }
  152. }
  153. #pragma mark - 上下拉刷新事件
  154. /// 下拉事件
  155. - (void)tableViewDidTriggerHeaderRefresh{
  156. @weakify(self)
  157. self.viewModel.page = 1;
  158. [[[self.viewModel.requestRemoteDataCommand
  159. execute:@1]
  160. deliverOnMainThread]
  161. subscribeNext:^(id x) {
  162. @strongify(self)
  163. if (self.viewModel.dataSource.count >= self.viewModel.perPage) {
  164. self.viewModel.page = 2;
  165. }
  166. /// 重置没有更多的状态
  167. if (self.viewModel.shouldEndRefreshingWithNoMoreData) [self.tableView.mj_footer resetNoMoreData];
  168. } error:^(NSError *error) {
  169. @strongify(self)
  170. /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以reload = NO即可
  171. [self.tableView.mj_header endRefreshing];
  172. } completed:^{
  173. @strongify(self)
  174. /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以只要结束刷新即可
  175. [self.tableView.mj_header endRefreshing];
  176. /// 请求完成
  177. [self _requestDataCompleted];
  178. }];
  179. }
  180. /// 上拉事件
  181. - (void)tableViewDidTriggerFooterRefresh{
  182. @weakify(self);
  183. NSInteger currentPage = self.viewModel.page + 1;
  184. [[[self.viewModel.requestRemoteDataCommand
  185. execute:@(currentPage)]
  186. deliverOnMainThread]
  187. subscribeNext:^(id x) {
  188. @strongify(self)
  189. self.viewModel.page += 1;
  190. } error:^(NSError *error) {
  191. @strongify(self);
  192. [self.tableView.mj_footer endRefreshing];
  193. } completed:^{
  194. @strongify(self)
  195. [self.tableView.mj_footer endRefreshing];
  196. /// 请求完成
  197. [self _requestDataCompleted];
  198. }];
  199. }
  200. #pragma mark - sub class can override it
  201. /// 配置tableView的区域
  202. - (UIEdgeInsets)contentInset {
  203. return UIEdgeInsetsMake((RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), 0, 0, 0);
  204. }
  205. /// reload tableView data
  206. - (void)reloadData{
  207. [self.tableView reloadData];
  208. }
  209. /// duqueueReusavleCell
  210. - (UITableViewCell *)tableView:(UITableView *)tableView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
  211. return [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
  212. }
  213. /// configure cell data
  214. - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object {}
  215. #pragma mark - 辅助方法
  216. - (void)_requestDataCompleted{
  217. NSUInteger count = self.viewModel.dataSource.count;
  218. /// RQ Fixed: 这里必须要等到,底部控件结束刷新后,再来设置无更多数据,否则被叠加无效
  219. if (self.viewModel.shouldEndRefreshingWithNoMoreData && count%self.viewModel.perPage) [self.tableView.mj_footer endRefreshingWithNoMoreData];
  220. }
  221. #pragma mark - UITableViewDataSource
  222. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  223. if (self.viewModel.shouldMultiSections) return self.viewModel.dataSource ? self.viewModel.dataSource.count : 0;
  224. return 1;
  225. }
  226. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  227. if (self.viewModel.shouldMultiSections) return [(NSArray *)self.viewModel.dataSource[section] count];
  228. return self.viewModel.dataSource.count;
  229. }
  230. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  231. UITableViewCell *cell = [self tableView:tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
  232. // fetch object
  233. id object = nil;
  234. if (self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.section][indexPath.row];
  235. if (!self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.row];
  236. /// bind model
  237. [self configureCell:cell atIndexPath:indexPath withObject:(id)object];
  238. return cell;
  239. }
  240. #pragma mark - UITableViewDelegate
  241. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  242. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  243. // execute commond
  244. [self.viewModel.didSelectCommand execute:indexPath];
  245. }
  246. - (BOOL)shouldCustomizeNavigationBarTransitionIfHideable {
  247. return YES;
  248. }
  249. - (void)qmui_themeDidChangeByManager:(QMUIThemeManager *)manager identifier:(__kindof NSObject<NSCopying> *)identifier theme:(__kindof NSObject *)theme {
  250. [super qmui_themeDidChangeByManager:manager identifier:identifier theme:theme];
  251. [self.tableView reloadData];
  252. }
  253. @end