// // JXPagerSmoothView.m // JXPagerViewExample-OC // // Created by jiaxin on 2019/11/15. // Copyright © 2019 jiaxin. All rights reserved. // #import "JXPagerSmoothView.h" static NSString *JXPagerSmoothViewCollectionViewCellIdentifier = @"cell"; @interface JXPagerSmoothCollectionView : UICollectionView @property (nonatomic, strong) UIView *pagerHeaderContainerView; @end @implementation JXPagerSmoothCollectionView - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { CGPoint point = [touch locationInView:self.pagerHeaderContainerView]; if (CGRectContainsPoint(self.pagerHeaderContainerView.bounds, point)) { return NO; } return YES; } @end @interface JXPagerSmoothView () @property (nonatomic, weak) id dataSource; @property (nonatomic, strong) JXPagerSmoothCollectionView *listCollectionView; @property (nonatomic, strong) NSMutableDictionary > *listDict; @property (nonatomic, strong) NSMutableDictionary *listHeaderDict; @property (nonatomic, assign, getter=isSyncListContentOffsetEnabled) BOOL syncListContentOffsetEnabled; @property (nonatomic, strong) UIView *pagerHeaderContainerView; @property (nonatomic, assign) CGFloat currentPagerHeaderContainerViewY; @property (nonatomic, assign) NSInteger currentIndex; @property (nonatomic, strong) UIScrollView *currentListScrollView; @property (nonatomic, assign) CGFloat heightForPagerHeader; @property (nonatomic, assign) CGFloat heightForPinHeader; @property (nonatomic, assign) CGFloat heightForPagerHeaderContainerView; @property (nonatomic, assign) CGFloat currentListInitializeContentOffsetY; @property (nonatomic, strong) UIScrollView *singleScrollView; @end @implementation JXPagerSmoothView - (void)dealloc { for (id list in self.listDict.allValues) { [[list listScrollView] removeObserver:self forKeyPath:@"contentOffset"]; [[list listScrollView] removeObserver:self forKeyPath:@"contentSize"]; } } - (instancetype)initWithDataSource:(id)dataSource { self = [super initWithFrame:CGRectZero]; if (self) { _dataSource = dataSource; _listDict = [NSMutableDictionary dictionary]; _listHeaderDict = [NSMutableDictionary dictionary]; [self initializeViews]; } return self; } - (void)initializeViews { self.pagerHeaderContainerView = [[UIView alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; layout.minimumLineSpacing = 0; layout.minimumInteritemSpacing = 0; _listCollectionView = [[JXPagerSmoothCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; self.listCollectionView.dataSource = self; self.listCollectionView.delegate = self; self.listCollectionView.pagingEnabled = YES; self.listCollectionView.bounces = NO; self.listCollectionView.showsHorizontalScrollIndicator = NO; self.listCollectionView.scrollsToTop = NO; [self.listCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:JXPagerSmoothViewCollectionViewCellIdentifier]; if (@available(iOS 10.0, *)) { self.listCollectionView.prefetchingEnabled = NO; } if (@available(iOS 11.0, *)) { self.listCollectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } _listCollectionView.pagerHeaderContainerView = self.pagerHeaderContainerView; [self addSubview:self.listCollectionView]; } - (void)layoutSubviews { [super layoutSubviews]; self.listCollectionView.frame = self.bounds; if (CGRectEqualToRect(self.pagerHeaderContainerView.frame, CGRectZero)) { [self reloadData]; } if (self.singleScrollView != nil) { self.singleScrollView.frame = self.bounds; } } - (void)reloadData { self.currentListScrollView = nil; self.currentIndex = self.defaultSelectedIndex; self.currentPagerHeaderContainerViewY = 0; self.syncListContentOffsetEnabled = NO; [self.listHeaderDict removeAllObjects]; for (id list in self.listDict.allValues) { [[list listScrollView] removeObserver:self forKeyPath:@"contentOffset"]; [[list listScrollView] removeObserver:self forKeyPath:@"contentSize"]; [[list listView] removeFromSuperview]; } [_listDict removeAllObjects]; self.heightForPagerHeader = [self.dataSource heightForPagerHeaderInPagerView:self]; self.heightForPinHeader = [self.dataSource heightForPinHeaderInPagerView:self]; self.heightForPagerHeaderContainerView = self.heightForPagerHeader + self.heightForPinHeader; UIView *pagerHeader = [self.dataSource viewForPagerHeaderInPagerView:self]; UIView *pinHeader = [self.dataSource viewForPinHeaderInPagerView:self]; [self.pagerHeaderContainerView addSubview:pagerHeader]; [self.pagerHeaderContainerView addSubview:pinHeader]; self.pagerHeaderContainerView.frame = CGRectMake(0, 0, self.bounds.size.width, self.heightForPagerHeaderContainerView); pagerHeader.frame = CGRectMake(0, 0, self.bounds.size.width, self.heightForPagerHeader); pinHeader.frame = CGRectMake(0, self.heightForPagerHeader, self.bounds.size.width, self.heightForPinHeader); [self.listCollectionView setContentOffset:CGPointMake(self.listCollectionView.bounds.size.width*self.defaultSelectedIndex, 0) animated:NO]; [self.listCollectionView reloadData]; if ([self.dataSource numberOfListsInPagerView:self] == 0) { self.singleScrollView = [[UIScrollView alloc] init]; [self addSubview:self.singleScrollView]; [self.singleScrollView addSubview:pagerHeader]; self.singleScrollView.contentSize = CGSizeMake(self.bounds.size.width, self.heightForPagerHeader); }else if (self.singleScrollView != nil) { [self.singleScrollView removeFromSuperview]; self.singleScrollView = nil; } } #pragma mark - UICollectionViewDataSource & UICollectionViewDelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return self.bounds.size; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [self.dataSource numberOfListsInPagerView:self]; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:JXPagerSmoothViewCollectionViewCellIdentifier forIndexPath:indexPath]; id list = self.listDict[@(indexPath.item)]; if (list == nil) { list = [self.dataSource pagerView:self initListAtIndex:indexPath.item]; _listDict[@(indexPath.item)] = list; [[list listView] setNeedsLayout]; [[list listView] layoutIfNeeded]; UIScrollView *listScrollView = [list listScrollView]; if ([listScrollView isKindOfClass:[UITableView class]]) { ((UITableView *)listScrollView).estimatedRowHeight = 0; ((UITableView *)listScrollView).estimatedSectionFooterHeight = 0; ((UITableView *)listScrollView).estimatedSectionHeaderHeight = 0; } if (@available(iOS 11.0, *)) { listScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } listScrollView.contentInset = UIEdgeInsetsMake(self.heightForPagerHeaderContainerView, 0, 0, 0); self.currentListInitializeContentOffsetY = -listScrollView.contentInset.top + MIN(-self.currentPagerHeaderContainerViewY, self.heightForPagerHeader); listScrollView.contentOffset = CGPointMake(0, self.currentListInitializeContentOffsetY); UIView *listHeader = [[UIView alloc] initWithFrame:CGRectMake(0, -self.heightForPagerHeaderContainerView, self.bounds.size.width, self.heightForPagerHeaderContainerView)]; [listScrollView addSubview:listHeader]; if (self.pagerHeaderContainerView.superview == nil) { [listHeader addSubview:self.pagerHeaderContainerView]; } self.listHeaderDict[@(indexPath.item)] = listHeader; [listScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; [listScrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; } for (id listItem in self.listDict.allValues) { [listItem listScrollView].scrollsToTop = (listItem == list); } UIView *listView = [list listView]; if (listView != nil && listView.superview != cell.contentView) { for (UIView *view in cell.contentView.subviews) { [view removeFromSuperview]; } listView.frame = cell.contentView.bounds; [cell.contentView addSubview:listView]; } return cell; } - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { [self listDidAppear:indexPath.item]; } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { [self listDidDisappear:indexPath.item]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (self.delegate && [self.delegate respondsToSelector:@selector(pagerSmoothViewDidScroll:)]) { [self.delegate pagerSmoothViewDidScroll:scrollView]; } CGFloat indexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width; NSInteger index = floor(indexPercent); UIScrollView *listScrollView = [self.listDict[@(index)] listScrollView]; if (indexPercent - index == 0 && index != self.currentIndex && !(scrollView.isDragging || scrollView.isDecelerating) && listScrollView.contentOffset.y <= -self.heightForPinHeader) { [self horizontalScrollDidEndAtIndex:index]; }else { //左右滚动的时候,就把listHeaderContainerView添加到self,达到悬浮在顶部的效果 if (self.pagerHeaderContainerView.superview != self) { self.pagerHeaderContainerView.frame = CGRectMake(0, self.currentPagerHeaderContainerViewY, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height); [self addSubview:self.pagerHeaderContainerView]; } } if (index != self.currentIndex) { self.currentIndex = index; } } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { NSInteger index = scrollView.contentOffset.x/scrollView.bounds.size.width; [self horizontalScrollDidEndAtIndex:index]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { NSInteger index = scrollView.contentOffset.x/scrollView.bounds.size.width; [self horizontalScrollDidEndAtIndex:index]; } #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"contentOffset"]) { UIScrollView *scrollView = (UIScrollView *)object; if (scrollView != nil) { [self listDidScroll:scrollView]; } }else if([keyPath isEqualToString:@"contentSize"]) { UIScrollView *scrollView = (UIScrollView *)object; if (scrollView != nil) { CGFloat minContentSizeHeight = self.bounds.size.height - self.heightForPinHeader; if (minContentSizeHeight > scrollView.contentSize.height) { scrollView.contentSize = CGSizeMake(scrollView.contentSize.width, minContentSizeHeight); //新的scrollView第一次加载的时候重置contentOffset if (_currentListScrollView != nil && scrollView != _currentListScrollView) { scrollView.contentOffset = CGPointMake(0, self.currentListInitializeContentOffsetY); } } } }else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } #pragma mark - Event - (void)listDidScroll:(UIScrollView *)scrollView { if (self.listCollectionView.isDragging || self.listCollectionView.isDecelerating) { return; } NSInteger listIndex = [self listIndexForListScrollView:scrollView]; if (listIndex != self.currentIndex) { return; } self.currentListScrollView = scrollView; CGFloat contentOffsetY = scrollView.contentOffset.y + self.heightForPagerHeaderContainerView; if (contentOffsetY < self.heightForPagerHeader) { self.syncListContentOffsetEnabled = YES; self.currentPagerHeaderContainerViewY = -contentOffsetY; for (id list in self.listDict.allValues) { if ([list listScrollView] != self.currentListScrollView) { [[list listScrollView] setContentOffset:scrollView.contentOffset animated:NO]; } } UIView *listHeader = [self listHeaderForListScrollView:scrollView]; if (self.pagerHeaderContainerView.superview != listHeader) { self.pagerHeaderContainerView.frame = CGRectMake(0, 0, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height); [listHeader addSubview:self.pagerHeaderContainerView]; } }else { if (self.pagerHeaderContainerView.superview != self) { self.pagerHeaderContainerView.frame = CGRectMake(0, -self.heightForPagerHeader, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height); [self addSubview:self.pagerHeaderContainerView]; } if (self.isSyncListContentOffsetEnabled) { self.syncListContentOffsetEnabled = NO; self.currentPagerHeaderContainerViewY = -self.heightForPagerHeader; for (id list in self.listDict.allValues) { if ([list listScrollView] != scrollView) { [[list listScrollView] setContentOffset:CGPointMake(0, -self.heightForPinHeader) animated:NO]; } } } } } #pragma mark - Private - (UIView *)listHeaderForListScrollView:(UIScrollView *)scrollView { for (NSNumber *index in self.listDict) { if ([self.listDict[index] listScrollView] == scrollView) { return self.listHeaderDict[index]; } } return nil; } - (NSInteger)listIndexForListScrollView:(UIScrollView *)scrollView { for (NSNumber *index in self.listDict) { if ([self.listDict[index] listScrollView] == scrollView) { return [index integerValue]; } } return 0; } - (void)listDidAppear:(NSInteger)index { NSUInteger count = [self.dataSource numberOfListsInPagerView:self]; if (count <= 0 || index >= count) { return; } id list = self.listDict[@(index)]; if (list && [list respondsToSelector:@selector(listDidAppear)]) { [list listDidAppear]; } } - (void)listDidDisappear:(NSInteger)index { NSUInteger count = [self.dataSource numberOfListsInPagerView:self]; if (count <= 0 || index >= count) { return; } id list = self.listDict[@(index)]; if (list && [list respondsToSelector:@selector(listDidDisappear)]) { [list listDidDisappear]; } } /// 列表左右切换滚动结束之后,需要把pagerHeaderContainerView添加到当前index的列表上面 - (void)horizontalScrollDidEndAtIndex:(NSInteger)index { self.currentIndex = index; UIView *listHeader = self.listHeaderDict[@(index)]; UIScrollView *listScrollView = [self.listDict[@(index)] listScrollView]; if (listHeader != nil && listScrollView.contentOffset.y <= -self.heightForPinHeader) { for (id listItem in self.listDict.allValues) { [listItem listScrollView].scrollsToTop = ([listItem listScrollView] == listScrollView); } self.pagerHeaderContainerView.frame = CGRectMake(0, 0, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height); [listHeader addSubview:self.pagerHeaderContainerView]; } } @end