filesbox/app/filesbox_ios/FilesBox/Pods/JXPagingView/Sources/JXPagerView/JXPagerSmoothView.m
2023-09-21 10:53:23 +08:00

362 lines
16 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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 <UIGestureRecognizerDelegate>
@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 () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, weak) id<JXPagerSmoothViewDataSource> dataSource;
@property (nonatomic, strong) JXPagerSmoothCollectionView *listCollectionView;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXPagerSmoothViewListViewDelegate>> *listDict;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, UIView*> *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<JXPagerSmoothViewListViewDelegate> list in self.listDict.allValues) {
[[list listScrollView] removeObserver:self forKeyPath:@"contentOffset"];
[[list listScrollView] removeObserver:self forKeyPath:@"contentSize"];
}
}
- (instancetype)initWithDataSource:(id<JXPagerSmoothViewDataSource>)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<JXPagerSmoothViewListViewDelegate> 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<JXPagerSmoothViewListViewDelegate> 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<JXPagerSmoothViewListViewDelegate> 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<NSKeyValueChangeKey,id> *)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<JXPagerSmoothViewListViewDelegate> 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<JXPagerSmoothViewListViewDelegate> 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<JXPagerSmoothViewListViewDelegate> 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<JXPagerSmoothViewListViewDelegate> 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<JXPagerSmoothViewListViewDelegate> 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