RQCollectionViewController.m 11 KB

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