// // RQCollectionViewController.m // YueXueChe // // Created by 张嵘 on 2018/12/18. // Copyright © 2018 lee. All rights reserved. // #import "RQCollectionViewController.h" @interface RQCollectionViewController () /// collectionView @property (nonatomic, readwrite, weak) RQCollectionView *collectionView; /// contentInset defaul is (64 , 0 , 0 , 0) @property (nonatomic, readwrite, assign) UIEdgeInsets contentInset; /// 视图模型 @property (nonatomic, readonly, strong) RQCollectionViewModel *viewModel; @property (nonatomic, readwrite, strong) UICollectionViewFlowLayout *flowLayout; @end @implementation RQCollectionViewController @dynamic viewModel; - (void)dealloc { // set nil _collectionView.dataSource = nil; _collectionView.delegate = nil; } /// init - (instancetype)initWithViewModel:(RQCollectionViewModel *)viewModel { self = [super initWithViewModel:viewModel]; if (self) { if ([viewModel shouldRequestRemoteDataOnViewDidLoad]) { @weakify(self) [[self rac_signalForSelector:@selector(viewDidLoad)] subscribeNext:^(id x) { @strongify(self) /// 请求第一页的网络数据 [self.viewModel.requestRemoteDataCommand execute:@1]; }]; } } return self; } - (void)viewDidLoad { [super viewDidLoad]; // 设置子控件 [self _su_setupSubViews]; } /// override - (void)bindViewModel { [super bindViewModel]; /// observe viewModel's dataSource @weakify(self) NSLog(@"%@", self.viewModel.dataSource); [[RACObserve(self.viewModel, dataSource) deliverOnMainThread] subscribeNext:^(id x) { @strongify(self) // 刷新数据 [self reloadData]; }]; /// 隐藏emptyView [self.viewModel.requestRemoteDataCommand.executing subscribeNext:^(NSNumber *executing) { @strongify(self) UIView *emptyDataSetView = [self.collectionView.subviews.rac_sequence objectPassingTest:^(UIView *view) { return [NSStringFromClass(view.class) isEqualToString:@"DZNEmptyDataSetView"]; }]; emptyDataSetView.alpha = 1.0 - executing.floatValue; }]; // [self.viewModel.requestRemoteDataCommand.executionSignals.switchToLatest subscribeNext:^(id _) { // @strongify(self); // /// 有网络 // self.viewModel.disableNetwork = NO; // }]; // // [self.viewModel.requestRemoteDataCommand.errors subscribeNext:^(id _) { // @strongify(self); // /// 有无网络 // self.viewModel.disableNetwork = !self.viewModel.services.client.reachabilityManager.reachable; // }]; } #pragma mark - 设置子控件 /// setup add `_su_` avoid sub class override it - (void)_su_setupSubViews{ // set up tableView /// RQ FIXED: 纯代码布局,子类如果重新布局,建议用Masonry重新设置约束 // 创建布局对象 _flowLayout = [[UICollectionViewFlowLayout alloc] init]; // 设置最小行间距 _flowLayout.minimumLineSpacing = 8; // 最小列间距 _flowLayout.minimumInteritemSpacing = 8; /** * 设置item的大小 格局item的大小自动布局列间距 * * @param 50 宽 * @param 50 高 * * @return */ _flowLayout.itemSize = CGSizeMake(0, 0); /** * 设置自动滚动的方向 垂直或者横向 */ _flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; /** * 设置集合视图内边距的大小 * * @param 20 上 * @param 20 左 * @param 20 下 * @param 20 右 * * @return UIEdgeInsetsMake 与下面的方法相同 -(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section */ _flowLayout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16); /** * 设置header区域大小 * * @param 414 414 * @param 70 无用 * * @return */ _flowLayout.headerReferenceSize = CGSizeMake(RQ_SCREEN_WIDTH, 40); /** * 设置footer区域的大小 * * @param 414 无用 * @param 70 自己设置 * * @return 如果写了这里必须设置注册 */ _flowLayout.footerReferenceSize = CGSizeMake(RQ_SCREEN_WIDTH, 10); /** 创建UICollectionView前必须先创建布局对象UICollectionViewFlowLayout - returns: UICollectionViewFlowLayout(布局对象) */ RQCollectionView *collectionView = [[RQCollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:_flowLayout]; collectionView.backgroundColor = RQ_MAIN_BACKGROUNDCOLOR; collectionView.showsVerticalScrollIndicator = NO; collectionView.showsHorizontalScrollIndicator = NO; collectionView.delegate = self; collectionView.dataSource = self; collectionView.bounces = YES; collectionView.delaysContentTouches = false; [self.view addSubview:collectionView]; /// 占位符 // tableView.emptyDataSetDelegate = self; // tableView.emptyDataSetSource = self; // [tableView mas_makeConstraints:^(MASConstraintMaker *make) { // make.edges.mas_equalTo(UIEdgeInsetsZero); // }]; self.collectionView = collectionView; collectionView.contentInset = self.contentInset; // 注册cell [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"UICollectionViewCell"]; /// RQ-MARK: 这里需要强制布局一下界面,解决由于设置了collectionView的contentInset,然而contentOffset始终是(0,0)的bug 但是这样会导致 collectionView 刷新一次,从而导致子类在 viewDidLoad 无法及时注册的cell,从而会有Crash的隐患 // [self.collectionView layoutIfNeeded]; // [self.collectionView setNeedsLayout]; // [self.collectionView updateConstraintsIfNeeded]; // [self.collectionView setNeedsUpdateConstraints]; // [self.view layoutIfNeeded]; /// 添加加载和刷新控件 if (self.viewModel.shouldPullDownToRefresh) { /// 下拉刷新 @weakify(self); [self.collectionView rq_addHeaderRefresh:^(MJRefreshNormalHeader *header) { /// 加载下拉刷新的数据 @strongify(self); [self tableViewDidTriggerHeaderRefresh]; }]; [self.collectionView.mj_header beginRefreshing]; } if (self.viewModel.shouldPullUpToLoadMore) { /// 上拉加载 @weakify(self); [self.collectionView rq_addFooterRefresh:^(MJRefreshAutoNormalFooter *footer) { /// 加载上拉刷新的数据 @strongify(self); [self tableViewDidTriggerFooterRefresh]; }]; /// 隐藏footer or 无更多数据 RAC(self.collectionView.mj_footer, hidden) = [[RACObserve(self.viewModel, dataSource) deliverOnMainThread] map:^(NSArray *dataSource) { @strongify(self) NSUInteger count = dataSource.count; /// 无数据,默认隐藏mj_footer if (count == 0) return @1; if (self.viewModel.shouldEndRefreshingWithNoMoreData) return @(0); /// because of return (count % self.viewModel.perPage)?@1:@0; }]; } if (@available(iOS 11.0, *)) { /// RQ: 适配 iPhone X + iOS 11, RQAdjustsScrollViewInsets_Never(collectionView); } } #pragma mark - 上下拉刷新事件 /// 下拉事件 - (void)tableViewDidTriggerHeaderRefresh{ @weakify(self) [[[self.viewModel.requestRemoteDataCommand execute:@1] deliverOnMainThread] subscribeNext:^(id x) { @strongify(self) self.viewModel.page = 1; /// 重置没有更多的状态 if (self.viewModel.shouldEndRefreshingWithNoMoreData) [self.collectionView.mj_footer resetNoMoreData]; } error:^(NSError *error) { @strongify(self) /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以reload = NO即可 [self.collectionView.mj_header endRefreshing]; } completed:^{ @strongify(self) /// 已经在bindViewModel中添加了对viewModel.dataSource的变化的监听来刷新数据,所以只要结束刷新即可 [self.collectionView.mj_header endRefreshing]; /// 请求完成 [self _requestDataCompleted]; }]; } /// 上拉事件 - (void)tableViewDidTriggerFooterRefresh{ @weakify(self); [[[self.viewModel.requestRemoteDataCommand execute:@(self.viewModel.page + 1)] deliverOnMainThread] subscribeNext:^(id x) { @strongify(self) self.viewModel.page += 1; } error:^(NSError *error) { @strongify(self); [self.collectionView.mj_footer endRefreshing]; } completed:^{ @strongify(self) [self.collectionView.mj_footer endRefreshing]; /// 请求完成 [self _requestDataCompleted]; }]; } #pragma mark - sub class can override it - (UIEdgeInsets)contentInset{ return UIEdgeInsetsMake((RQ_APPLICATION_NAV_BAR_HEIGHT + RQ_APPLICATION_STATUS_BAR_HEIGHT), 0, 0, 0); } /// reload tableView data - (void)reloadData{ [self.collectionView reloadData]; } /// duqueueReusavleCell - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath { return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; } /// configure cell data - (void)configureCell:(UICollectionViewCell *)cell atIndexPath:(NSIndexPath *)indexPath withObject:(id)object { } #pragma mark - 辅助方法 - (void)_requestDataCompleted{ NSUInteger count = self.viewModel.dataSource.count; /// RQ Fixed: 这里必须要等到,底部控件结束刷新后,再来设置无更多数据,否则被叠加无效 if (self.viewModel.shouldEndRefreshingWithNoMoreData && count%self.viewModel.perPage) [self.collectionView.mj_footer endRefreshingWithNoMoreData]; } #pragma mark - UICollectionViewDatasource - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { if (self.viewModel.shouldMultiSections) return self.viewModel.dataSource ? self.viewModel.dataSource.count : 0; return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { if (self.viewModel.shouldMultiSections) return [(NSArray *)self.viewModel.dataSource[section] count]; return self.viewModel.dataSource.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [self collectionView:collectionView dequeueReusableCellWithIdentifier:@"UICollectionViewCell" forIndexPath:indexPath]; // fetch object id object = nil; if (self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.section][indexPath.row]; if (!self.viewModel.shouldMultiSections) object = self.viewModel.dataSource[indexPath.row]; /// bind model [self configureCell:cell atIndexPath:indexPath withObject:(id)object]; return cell; } #pragma mark - UICollectionViewDelegate - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { [collectionView deselectItemAtIndexPath:indexPath animated:YES]; // execute commond [self.viewModel.didSelectCommand execute:indexPath]; } #pragma mark - collectionViewCell点击高亮 - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath{ // UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; } - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath{ // UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; // cell.backgroundColor = [UIColor whiteColor]; } - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath{ return YES; } @end