RQCollectionViewController.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. //
  2. // RQCollectionViewController.m
  3. // YueXueChe
  4. //
  5. // Created by 张嵘 on 2018/12/18.
  6. // Copyright © 2018 lee. All rights reserved.
  7. //
  8. #import "RQCollectionViewController.h"
  9. @interface RQCollectionViewController ()
  10. /// collectionView
  11. @property (nonatomic, readwrite, weak) RQCollectionView *collectionView;
  12. /// contentInset defaul is (64 , 0 , 0 , 0)
  13. @property (nonatomic, readwrite, assign) UIEdgeInsets contentInset;
  14. /// 视图模型
  15. @property (nonatomic, readonly, strong) RQCollectionViewModel *viewModel;
  16. @property (nonatomic, readwrite, strong) UICollectionViewFlowLayout *flowLayout;
  17. @end
  18. @implementation RQCollectionViewController
  19. @dynamic viewModel;
  20. - (void)dealloc {
  21. // set nil
  22. _collectionView.dataSource = nil;
  23. _collectionView.delegate = nil;
  24. }
  25. /// init
  26. - (instancetype)initWithViewModel:(RQCollectionViewModel *)viewModel {
  27. self = [super initWithViewModel:viewModel];
  28. if (self) {
  29. if ([viewModel shouldRequestRemoteDataOnViewDidLoad]) {
  30. @weakify(self)
  31. [[self rac_signalForSelector:@selector(viewDidLoad)] subscribeNext:^(id x) {
  32. @strongify(self)
  33. /// 请求第一页的网络数据
  34. [self.viewModel.requestRemoteDataCommand execute:@1];
  35. }];
  36. }
  37. }
  38. return self;
  39. }
  40. - (void)viewDidLoad {
  41. [super viewDidLoad];
  42. // 设置子控件
  43. [self _su_setupSubViews];
  44. }
  45. /// override
  46. - (void)bindViewModel {
  47. [super bindViewModel];
  48. /// observe viewModel's dataSource
  49. @weakify(self)
  50. NSLog(@"%@", self.viewModel.dataSource);
  51. [[RACObserve(self.viewModel, dataSource)
  52. deliverOnMainThread]
  53. subscribeNext:^(id x) {
  54. @strongify(self)
  55. // 刷新数据
  56. [self reloadData];
  57. }];
  58. /// 隐藏emptyView
  59. [self.viewModel.requestRemoteDataCommand.executing subscribeNext:^(NSNumber *executing) {
  60. @strongify(self)
  61. UIView *emptyDataSetView = [self.collectionView.subviews.rac_sequence objectPassingTest:^(UIView *view) {
  62. return [NSStringFromClass(view.class) isEqualToString:@"DZNEmptyDataSetView"];
  63. }];
  64. emptyDataSetView.alpha = 1.0 - executing.floatValue;
  65. }];
  66. // [self.viewModel.requestRemoteDataCommand.executionSignals.switchToLatest subscribeNext:^(id _) {
  67. // @strongify(self);
  68. // /// 有网络
  69. // self.viewModel.disableNetwork = NO;
  70. // }];
  71. //
  72. // [self.viewModel.requestRemoteDataCommand.errors subscribeNext:^(id _) {
  73. // @strongify(self);
  74. // /// 有无网络
  75. // self.viewModel.disableNetwork = !self.viewModel.services.client.reachabilityManager.reachable;
  76. // }];
  77. }
  78. #pragma mark - 设置子控件
  79. /// setup add `_su_` avoid sub class override it
  80. - (void)_su_setupSubViews{
  81. // set up tableView
  82. /// RQ FIXED: 纯代码布局,子类如果重新布局,建议用Masonry重新设置约束
  83. // 创建布局对象
  84. _flowLayout = [[UICollectionViewFlowLayout alloc] init];
  85. // 设置最小行间距
  86. _flowLayout.minimumLineSpacing = 8;
  87. // 最小列间距
  88. _flowLayout.minimumInteritemSpacing = 8;
  89. /**
  90. * 设置item的大小 格局item的大小自动布局列间距
  91. *
  92. * @param 50 宽
  93. * @param 50 高
  94. *
  95. * @return
  96. */
  97. _flowLayout.itemSize = CGSizeMake(0, 0);
  98. /**
  99. * 设置自动滚动的方向 垂直或者横向
  100. */
  101. _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
  102. /**
  103. * 设置集合视图内边距的大小
  104. *
  105. * @param 20 上
  106. * @param 20 左
  107. * @param 20 下
  108. * @param 20 右
  109. *
  110. * @return UIEdgeInsetsMake 与下面的方法相同 -(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
  111. */
  112. _flowLayout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16);
  113. /**
  114. * 设置header区域大小
  115. *
  116. * @param 414 414
  117. * @param 70 无用
  118. *
  119. * @return
  120. */
  121. _flowLayout.headerReferenceSize = CGSizeMake(RQ_SCREEN_WIDTH, 40);
  122. /**
  123. * 设置footer区域的大小
  124. *
  125. * @param 414 无用
  126. * @param 70 自己设置
  127. *
  128. * @return 如果写了这里必须设置注册
  129. */
  130. _flowLayout.footerReferenceSize = CGSizeMake(RQ_SCREEN_WIDTH, 10);
  131. /**
  132. 创建UICollectionView前必须先创建布局对象UICollectionViewFlowLayout
  133. - returns: UICollectionViewFlowLayout(布局对象)
  134. */
  135. RQCollectionView *collectionView = [[RQCollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:_flowLayout];
  136. collectionView.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR;
  137. collectionView.showsVerticalScrollIndicator = NO;
  138. collectionView.showsHorizontalScrollIndicator = NO;
  139. collectionView.delegate = self;
  140. collectionView.dataSource = self;
  141. collectionView.bounces = YES;
  142. collectionView.delaysContentTouches = false;
  143. [self.view addSubview:collectionView];
  144. /// 占位符
  145. // tableView.emptyDataSetDelegate = self;
  146. // tableView.emptyDataSetSource = self;
  147. // [tableView mas_makeConstraints:^(MASConstraintMaker *make) {
  148. // make.edges.mas_equalTo(UIEdgeInsetsZero);
  149. // }];
  150. self.collectionView = collectionView;
  151. collectionView.contentInset = self.contentInset;
  152. // 注册cell
  153. [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCell"];
  154. /// RQ-MARK: 这里需要强制布局一下界面,解决由于设置了collectionView的contentInset,然而contentOffset始终是(0,0)的bug 但是这样会导致 collectionView 刷新一次,从而导致子类在 viewDidLoad 无法及时注册的cell,从而会有Crash的隐患
  155. // [self.collectionView layoutIfNeeded];
  156. // [self.collectionView setNeedsLayout];
  157. // [self.collectionView updateConstraintsIfNeeded];
  158. // [self.collectionView setNeedsUpdateConstraints];
  159. // [self.view layoutIfNeeded];
  160. /// 添加加载和刷新控件
  161. if (self.viewModel.shouldPullDownToRefresh) {
  162. /// 下拉刷新
  163. @weakify(self);
  164. [self.collectionView rq_addHeaderRefresh:^(MJRefreshNormalHeader *header) {
  165. /// 加载下拉刷新的数据
  166. @strongify(self);
  167. [self tableViewDidTriggerHeaderRefresh];
  168. }];
  169. [self.collectionView.mj_header beginRefreshing];
  170. }
  171. if (self.viewModel.shouldPullUpToLoadMore) {
  172. /// 上拉加载
  173. @weakify(self);
  174. [self.collectionView rq_addFooterRefresh:^(MJRefreshAutoNormalFooter *footer) {
  175. /// 加载上拉刷新的数据
  176. @strongify(self);
  177. [self tableViewDidTriggerFooterRefresh];
  178. }];
  179. /// 隐藏footer or 无更多数据
  180. RAC(self.collectionView.mj_footer, hidden) = [[RACObserve(self.viewModel, dataSource)
  181. deliverOnMainThread]
  182. map:^(NSArray *dataSource) {
  183. @strongify(self)
  184. NSUInteger count = dataSource.count;
  185. /// 无数据,默认隐藏mj_footer
  186. if (count == 0) return @1;
  187. if (self.viewModel.shouldEndRefreshingWithNoMoreData) return @(0);
  188. /// because of
  189. return (count % self.viewModel.perPage)?@1:@0;
  190. }];
  191. }
  192. if (@available(iOS 11.0, *)) {
  193. /// RQ: 适配 iPhone X + iOS 11,
  194. RQAdjustsScrollViewInsets_Never(collectionView);
  195. }
  196. }
  197. #pragma mark - 上下拉刷新事件
  198. /// 下拉事件
  199. - (void)tableViewDidTriggerHeaderRefresh{
  200. @weakify(self)
  201. [[[self.viewModel.requestRemoteDataCommand
  202. execute:@1]
  203. deliverOnMainThread]
  204. subscribeNext:^(id x) {
  205. @strongify(self)
  206. self.viewModel.page = 1;
  207. /// 重置没有更多的状态
  208. if (self.viewModel.shouldEndRefreshingWithNoMoreData) [self.collectionView.mj_footer resetNoMoreData];
  209. } error:^(NSError *error) {
  210. @strongify(self)
  211. /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以reload = NO即可
  212. [self.collectionView.mj_header endRefreshing];
  213. } completed:^{
  214. @strongify(self)
  215. /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以只要结束刷新即可
  216. [self.collectionView.mj_header endRefreshing];
  217. /// 请求完成
  218. [self _requestDataCompleted];
  219. }];
  220. }
  221. /// 上拉事件
  222. - (void)tableViewDidTriggerFooterRefresh{
  223. @weakify(self);
  224. [[[self.viewModel.requestRemoteDataCommand
  225. execute:@(self.viewModel.page + 1)]
  226. deliverOnMainThread]
  227. subscribeNext:^(id x) {
  228. @strongify(self)
  229. self.viewModel.page += 1;
  230. } error:^(NSError *error) {
  231. @strongify(self);
  232. [self.collectionView.mj_footer endRefreshing];
  233. } completed:^{
  234. @strongify(self)
  235. [self.collectionView.mj_footer endRefreshing];
  236. /// 请求完成
  237. [self _requestDataCompleted];
  238. }];
  239. }
  240. #pragma mark - sub class can override it
  241. - (UIEdgeInsets)contentInset{
  242. return UIEdgeInsetsMake((RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), 0, 0, 0);
  243. }
  244. /// reload tableView data
  245. - (void)reloadData{
  246. [self.collectionView reloadData];
  247. }
  248. /// duqueueReusavleCell
  249. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
  250. return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
  251. }
  252. /// configure cell data
  253. - (void)configureCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object {
  254. }
  255. #pragma mark - 辅助方法
  256. - (void)_requestDataCompleted{
  257. NSUInteger count = self.viewModel.dataSource.count;
  258. /// RQ Fixed: 这里必须要等到,底部控件结束刷新后,再来设置无更多数据,否则被叠加无效
  259. if (self.viewModel.shouldEndRefreshingWithNoMoreData && count%self.viewModel.perPage) [self.collectionView.mj_footer endRefreshingWithNoMoreData];
  260. }
  261. #pragma mark - UICollectionViewDatasource
  262. - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
  263. if (self.viewModel.shouldMultiSections) return self.viewModel.dataSource ? self.viewModel.dataSource.count : 0;
  264. return 1;
  265. }
  266. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  267. if (self.viewModel.shouldMultiSections) return [(NSArray *)self.viewModel.dataSource[section] count];
  268. return self.viewModel.dataSource.count;
  269. }
  270. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  271. UICollectionViewCell *cell = [self collectionView:collectionView dequeueReusableCellWithIdentifier:@"UICollectionViewCell" forIndexPath:indexPath];
  272. // fetch object
  273. id object = nil;
  274. if (self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.section][indexPath.row];
  275. if (!self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.row];
  276. /// bind model
  277. [self configureCell:cell atIndexPath:indexPath withObject:(id)object];
  278. return cell;
  279. }
  280. #pragma mark - UICollectionViewDelegate
  281. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
  282. [collectionView deselectItemAtIndexPath:indexPath animated:YES];
  283. // execute commond
  284. [self.viewModel.didSelectCommand execute:indexPath];
  285. }
  286. #pragma mark - collectionViewCell点击高亮
  287. - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath{
  288. // UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
  289. }
  290. - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath{
  291. // UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
  292. // cell.backgroundColor = [UIColor whiteColor];
  293. }
  294. - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath{
  295. return YES;
  296. }
  297. @end