410 lines
19 KiB
Objective-C
410 lines
19 KiB
Objective-C
//
|
||
// JXPagerView.m
|
||
// JXPagerView
|
||
//
|
||
// Created by jiaxin on 2018/8/27.
|
||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||
//
|
||
|
||
#import "JXPagerView.h"
|
||
@class JXPagerListContainerScrollView;
|
||
@class JXPagerListContainerCollectionView;
|
||
|
||
@interface JXPagerView () <UITableViewDataSource, UITableViewDelegate, JXPagerListContainerViewDelegate>
|
||
@property (nonatomic, weak) id<JXPagerViewDelegate> delegate;
|
||
@property (nonatomic, strong) JXPagerMainTableView *mainTableView;
|
||
@property (nonatomic, strong) JXPagerListContainerView *listContainerView;
|
||
@property (nonatomic, strong) UIScrollView *currentScrollingListView;
|
||
@property (nonatomic, strong) id<JXPagerViewListViewDelegate> currentList;
|
||
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict;
|
||
@property (nonatomic, strong) UIView *tableHeaderContainerView;
|
||
@property (nonatomic, strong) NSMutableDictionary<NSString *, id<JXPagerViewListViewDelegate>> *listCache;
|
||
@end
|
||
|
||
@implementation JXPagerView
|
||
|
||
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate {
|
||
return [self initWithDelegate:delegate listContainerType:JXPagerListContainerType_CollectionView];
|
||
}
|
||
|
||
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate listContainerType:(JXPagerListContainerType)type {
|
||
self = [super initWithFrame:CGRectZero];
|
||
if (self) {
|
||
_delegate = delegate;
|
||
_validListDict = [NSMutableDictionary dictionary];
|
||
_automaticallyDisplayListVerticalScrollIndicator = YES;
|
||
_isListHorizontalScrollEnabled = YES;
|
||
|
||
_mainTableView = [[JXPagerMainTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||
self.mainTableView.showsVerticalScrollIndicator = NO;
|
||
self.mainTableView.showsHorizontalScrollIndicator = NO;
|
||
self.mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||
self.mainTableView.scrollsToTop = NO;
|
||
self.mainTableView.dataSource = self;
|
||
self.mainTableView.delegate = self;
|
||
[self refreshTableHeaderView];
|
||
[self.mainTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
|
||
if (@available(iOS 11.0, *)) {
|
||
self.mainTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||
}
|
||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
|
||
if (@available(iOS 15.0, *)) {
|
||
self.mainTableView.sectionHeaderTopPadding = 0;
|
||
}
|
||
#endif
|
||
[self addSubview:self.mainTableView];
|
||
|
||
_listContainerView = [[JXPagerListContainerView alloc] initWithType:type delegate:self];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)layoutSubviews {
|
||
[super layoutSubviews];
|
||
|
||
if (!CGRectEqualToRect(self.bounds, self.mainTableView.frame)) {
|
||
self.mainTableView.frame = self.bounds;
|
||
[self.mainTableView reloadData];
|
||
}
|
||
}
|
||
|
||
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
|
||
_defaultSelectedIndex = defaultSelectedIndex;
|
||
|
||
self.listContainerView.defaultSelectedIndex = defaultSelectedIndex;
|
||
}
|
||
|
||
- (void)setIsListHorizontalScrollEnabled:(BOOL)isListHorizontalScrollEnabled {
|
||
_isListHorizontalScrollEnabled = isListHorizontalScrollEnabled;
|
||
|
||
self.listContainerView.scrollView.scrollEnabled = isListHorizontalScrollEnabled;
|
||
}
|
||
|
||
- (void)reloadData {
|
||
self.currentList = nil;
|
||
self.currentScrollingListView = nil;
|
||
[_validListDict removeAllObjects];
|
||
//根据新数据删除不需要的list
|
||
if (self.allowsCacheList) {
|
||
NSMutableArray *newListIdentifierArray = [NSMutableArray array];
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(numberOfListsInPagerView:)]) {
|
||
NSInteger listCount = [self.delegate numberOfListsInPagerView:self];
|
||
for (NSInteger index = 0; index < listCount; index ++) {
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:listIdentifierAtIndex:)]) {
|
||
NSString *listIdentifier = [self.delegate pagerView:self listIdentifierAtIndex:index];
|
||
[newListIdentifierArray addObject:listIdentifier];
|
||
}
|
||
}
|
||
}
|
||
NSArray *existedKeys = self.listCache.allKeys;
|
||
for (NSString *listIdentifier in existedKeys) {
|
||
if (![newListIdentifierArray containsObject:listIdentifier]) {
|
||
[self.listCache removeObjectForKey:listIdentifier];
|
||
}
|
||
}
|
||
}
|
||
[self refreshTableHeaderView];
|
||
if (self.pinSectionHeaderVerticalOffset != 0 && self.mainTableView.contentOffset.y > self.pinSectionHeaderVerticalOffset) {
|
||
self.mainTableView.contentOffset = CGPointZero;
|
||
}
|
||
[self.mainTableView reloadData];
|
||
[self.listContainerView reloadData];
|
||
}
|
||
|
||
- (void)resizeTableHeaderViewHeightWithAnimatable:(BOOL)animatable duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
|
||
if (animatable) {
|
||
UIViewAnimationOptions options = UIViewAnimationOptionCurveLinear;
|
||
switch (curve) {
|
||
case UIViewAnimationCurveEaseIn: options = UIViewAnimationOptionCurveEaseIn; break;
|
||
case UIViewAnimationCurveEaseOut: options = UIViewAnimationOptionCurveEaseOut; break;
|
||
case UIViewAnimationCurveEaseInOut: options = UIViewAnimationOptionCurveEaseInOut; break;
|
||
default: break;
|
||
}
|
||
[UIView animateWithDuration:duration delay:0 options:options animations:^{
|
||
CGRect frame = self.tableHeaderContainerView.bounds;
|
||
frame.size.height = [self.delegate tableHeaderViewHeightInPagerView:self];
|
||
self.tableHeaderContainerView.frame = frame;
|
||
self.mainTableView.tableHeaderView = self.tableHeaderContainerView;
|
||
[self.mainTableView setNeedsLayout];
|
||
[self.mainTableView layoutIfNeeded];
|
||
} completion:^(BOOL finished) { }];
|
||
}else {
|
||
CGRect frame = self.tableHeaderContainerView.bounds;
|
||
frame.size.height = [self.delegate tableHeaderViewHeightInPagerView:self];
|
||
self.tableHeaderContainerView.frame = frame;
|
||
self.mainTableView.tableHeaderView = self.tableHeaderContainerView;
|
||
}
|
||
}
|
||
|
||
#pragma mark - Private
|
||
|
||
- (void)refreshTableHeaderView {
|
||
UIView *tableHeaderView = [self.delegate tableHeaderViewInPagerView:self];
|
||
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, [self.delegate tableHeaderViewHeightInPagerView:self])];
|
||
[containerView addSubview:tableHeaderView];
|
||
tableHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
|
||
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
|
||
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
|
||
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
|
||
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
|
||
[containerView addConstraints:@[top, leading, bottom, trailing]];
|
||
self.tableHeaderContainerView = containerView;
|
||
self.mainTableView.tableHeaderView = containerView;
|
||
}
|
||
|
||
- (void)adjustMainScrollViewToTargetContentInsetIfNeeded:(UIEdgeInsets)insets {
|
||
if (UIEdgeInsetsEqualToEdgeInsets(insets, self.mainTableView.contentInset) == NO) {
|
||
self.mainTableView.delegate = nil;
|
||
self.mainTableView.contentInset = insets;
|
||
self.mainTableView.delegate = self;
|
||
}
|
||
}
|
||
|
||
- (void)listViewDidScroll:(UIScrollView *)scrollView {
|
||
self.currentScrollingListView = scrollView;
|
||
[self preferredProcessListViewDidScroll:scrollView];
|
||
}
|
||
|
||
//仅用于处理设置了pinSectionHeaderVerticalOffset,又添加了MJRefresh的下拉刷新。这种情况会导致JXPagingView和MJRefresh来回设置contentInset值。针对这种及其特殊的情况,就内部特殊处理了。通过下面的判断条件,来判定当前是否处于下拉刷新中。请勿让pinSectionHeaderVerticalOffset和下拉刷新设置的contentInset.top值相同。
|
||
//具体原因参考:https://github.com/pujiaxin33/JXPagingView/issues/203
|
||
- (BOOL)isSetMainScrollViewContentInsetToZeroEnabled:(UIScrollView *)scrollView {
|
||
//scrollView.contentInset.top不为0,且scrollView.contentInset.top不等于pinSectionHeaderVerticalOffset,即可认为列表正在刷新。所以这里必须要保证pinSectionHeaderVerticalOffset和MJRefresh的mj_insetT的值不相等。
|
||
BOOL isRefreshing = scrollView.contentInset.top != 0 && scrollView.contentInset.top != self.pinSectionHeaderVerticalOffset;
|
||
return !isRefreshing;
|
||
}
|
||
|
||
#pragma mark - UITableViewDataSource, UITableViewDelegate
|
||
|
||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||
return 1;
|
||
}
|
||
|
||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||
return MAX(self.bounds.size.height - [self.delegate heightForPinSectionHeaderInPagerView:self] - self.pinSectionHeaderVerticalOffset, 0);
|
||
}
|
||
|
||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
|
||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||
cell.backgroundColor = [UIColor clearColor];
|
||
if (self.listContainerView.superview != cell.contentView) {
|
||
[cell.contentView addSubview:self.listContainerView];
|
||
}
|
||
if (!CGRectEqualToRect(self.listContainerView.frame, cell.bounds)) {
|
||
self.listContainerView.frame = cell.bounds;
|
||
}
|
||
return cell;
|
||
}
|
||
|
||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||
return [self.delegate heightForPinSectionHeaderInPagerView:self];
|
||
}
|
||
|
||
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
|
||
return [self.delegate viewForPinSectionHeaderInPagerView:self];
|
||
}
|
||
|
||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
|
||
return 1;
|
||
}
|
||
|
||
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
|
||
UIView *footer = [[UIView alloc] initWithFrame:CGRectZero];
|
||
footer.backgroundColor = [UIColor clearColor];
|
||
return footer;
|
||
}
|
||
|
||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||
if (self.pinSectionHeaderVerticalOffset != 0) {
|
||
if (!(self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > [self minContentOffsetYInListScrollView:self.currentScrollingListView])) {
|
||
//没有处于滚动某一个listView的状态
|
||
if (scrollView.contentOffset.y >= self.pinSectionHeaderVerticalOffset) {
|
||
//固定的位置就是contentInset.top
|
||
[self adjustMainScrollViewToTargetContentInsetIfNeeded:UIEdgeInsetsMake(self.pinSectionHeaderVerticalOffset, 0, 0, 0)];
|
||
}else {
|
||
if ([self isSetMainScrollViewContentInsetToZeroEnabled:scrollView]) {
|
||
[self adjustMainScrollViewToTargetContentInsetIfNeeded:UIEdgeInsetsZero];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
[self preferredProcessMainTableViewDidScroll:scrollView];
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(mainTableViewDidScroll:)]) {
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||
[self.delegate mainTableViewDidScroll:scrollView];
|
||
#pragma GCC diagnostic pop
|
||
}
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidScroll:)]) {
|
||
[self.delegate pagerView:self mainTableViewDidScroll:scrollView];
|
||
}
|
||
}
|
||
|
||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||
self.listContainerView.scrollView.scrollEnabled = NO;
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewWillBeginDragging:)]) {
|
||
[self.delegate pagerView:self mainTableViewWillBeginDragging:scrollView];
|
||
}
|
||
}
|
||
|
||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
|
||
if (self.isListHorizontalScrollEnabled && !decelerate) {
|
||
self.listContainerView.scrollView.scrollEnabled = YES;
|
||
}
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidEndDragging:willDecelerate:)]) {
|
||
[self.delegate pagerView:self mainTableViewDidEndDragging:scrollView willDecelerate:decelerate];
|
||
}
|
||
}
|
||
|
||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||
if (self.isListHorizontalScrollEnabled) {
|
||
self.listContainerView.scrollView.scrollEnabled = YES;
|
||
}
|
||
if ([self isSetMainScrollViewContentInsetToZeroEnabled:scrollView]) {
|
||
if (self.mainTableView.contentInset.top != 0 && self.pinSectionHeaderVerticalOffset != 0) {
|
||
[self adjustMainScrollViewToTargetContentInsetIfNeeded:UIEdgeInsetsZero];
|
||
}
|
||
}
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidEndDecelerating:)]) {
|
||
[self.delegate pagerView:self mainTableViewDidEndDecelerating:scrollView];
|
||
}
|
||
}
|
||
|
||
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
|
||
if (self.isListHorizontalScrollEnabled) {
|
||
self.listContainerView.scrollView.scrollEnabled = YES;
|
||
}
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidEndScrollingAnimation:)]) {
|
||
[self.delegate pagerView:self mainTableViewDidEndScrollingAnimation:scrollView];
|
||
}
|
||
}
|
||
|
||
#pragma mark - JXPagerListContainerViewDelegate
|
||
|
||
- (NSInteger)numberOfListsInlistContainerView:(JXPagerListContainerView *)listContainerView {
|
||
return [self.delegate numberOfListsInPagerView:self];
|
||
}
|
||
|
||
- (id<JXPagerViewListViewDelegate>)listContainerView:(JXPagerListContainerView *)listContainerView initListForIndex:(NSInteger)index {
|
||
id<JXPagerViewListViewDelegate> list = self.validListDict[@(index)];
|
||
if (list == nil) {
|
||
if (self.allowsCacheList && self.delegate && [self.delegate respondsToSelector:@selector(pagerView:listIdentifierAtIndex:)]) {
|
||
NSString *listIdentifier = [self.delegate pagerView:self listIdentifierAtIndex:index];
|
||
list = self.listCache[listIdentifier];
|
||
}
|
||
}
|
||
if (list == nil) {
|
||
list = [self.delegate pagerView:self initListAtIndex:index];
|
||
__weak typeof(self)weakSelf = self;
|
||
__weak typeof(id<JXPagerViewListViewDelegate>) weakList = list;
|
||
[list listViewDidScrollCallback:^(UIScrollView *scrollView) {
|
||
weakSelf.currentList = weakList;
|
||
[weakSelf listViewDidScroll:scrollView];
|
||
}];
|
||
_validListDict[@(index)] = list;
|
||
if (self.allowsCacheList && self.delegate && [self.delegate respondsToSelector:@selector(pagerView:listIdentifierAtIndex:)]) {
|
||
NSString *listIdentifier = [self.delegate pagerView:self listIdentifierAtIndex:index];
|
||
self.listCache[listIdentifier] = list;
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
|
||
|
||
- (void)listContainerViewWillBeginDragging:(JXPagerListContainerView *)listContainerView {
|
||
self.mainTableView.scrollEnabled = NO;
|
||
}
|
||
|
||
- (void)listContainerViewWDidEndScroll:(JXPagerListContainerView *)listContainerView {
|
||
self.mainTableView.scrollEnabled = YES;
|
||
}
|
||
|
||
- (void)listContainerView:(JXPagerListContainerView *)listContainerView listDidAppearAtIndex:(NSInteger)index {
|
||
self.currentScrollingListView = [self.validListDict[@(index)] listScrollView];
|
||
for (id<JXPagerViewListViewDelegate> listItem in self.validListDict.allValues) {
|
||
if (listItem == self.validListDict[@(index)]) {
|
||
[listItem listScrollView].scrollsToTop = YES;
|
||
}else {
|
||
[listItem listScrollView].scrollsToTop = NO;
|
||
}
|
||
}
|
||
}
|
||
|
||
- (Class)scrollViewClassInlistContainerView:(JXPagerListContainerView *)listContainerView {
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewClassInlistContainerViewInPagerView:)]) {
|
||
return [self.delegate scrollViewClassInlistContainerViewInPagerView:self];
|
||
}
|
||
return nil;
|
||
}
|
||
|
||
@end
|
||
|
||
@implementation JXPagerView (UISubclassingGet)
|
||
|
||
- (CGFloat)mainTableViewMaxContentOffsetY {
|
||
return [self.delegate tableHeaderViewHeightInPagerView:self] - self.pinSectionHeaderVerticalOffset;
|
||
}
|
||
|
||
@end
|
||
|
||
@implementation JXPagerView (UISubclassingHooks)
|
||
|
||
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView {
|
||
if (self.mainTableView.contentOffset.y < self.mainTableViewMaxContentOffsetY) {
|
||
//mainTableView的header还没有消失,让listScrollView一直为0
|
||
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
|
||
[self.currentList listScrollViewWillResetContentOffset];
|
||
}
|
||
[self setListScrollViewToMinContentOffsetY:scrollView];
|
||
if (self.automaticallyDisplayListVerticalScrollIndicator) {
|
||
scrollView.showsVerticalScrollIndicator = NO;
|
||
}
|
||
}else {
|
||
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
|
||
self.mainTableView.contentOffset = CGPointMake(0, self.mainTableViewMaxContentOffsetY);
|
||
if (self.automaticallyDisplayListVerticalScrollIndicator) {
|
||
scrollView.showsVerticalScrollIndicator = YES;
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView {
|
||
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > [self minContentOffsetYInListScrollView:self.currentScrollingListView]) {
|
||
//mainTableView的header已经滚动不见,开始滚动某一个listView,那么固定mainTableView的contentOffset,让其不动
|
||
[self setMainTableViewToMaxContentOffsetY];
|
||
}
|
||
|
||
if (scrollView.contentOffset.y < self.mainTableViewMaxContentOffsetY) {
|
||
//mainTableView已经显示了header,listView的contentOffset需要重置
|
||
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
|
||
if ([list respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
|
||
[list listScrollViewWillResetContentOffset];
|
||
}
|
||
[self setListScrollViewToMinContentOffsetY:[list listScrollView]];
|
||
}
|
||
}
|
||
|
||
if (scrollView.contentOffset.y > self.mainTableViewMaxContentOffsetY && self.currentScrollingListView.contentOffset.y == [self minContentOffsetYInListScrollView:self.currentScrollingListView]) {
|
||
//当往上滚动mainTableView的headerView时,滚动到底时,修复listView往上小幅度滚动
|
||
[self setMainTableViewToMaxContentOffsetY];
|
||
}
|
||
}
|
||
|
||
- (void)setMainTableViewToMaxContentOffsetY {
|
||
self.mainTableView.contentOffset = CGPointMake(0, self.mainTableViewMaxContentOffsetY);
|
||
}
|
||
|
||
- (void)setListScrollViewToMinContentOffsetY:(UIScrollView *)scrollView {
|
||
scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, [self minContentOffsetYInListScrollView:scrollView]);
|
||
}
|
||
|
||
- (CGFloat)minContentOffsetYInListScrollView:(UIScrollView *)scrollView {
|
||
if (@available(iOS 11.0, *)) {
|
||
return -scrollView.adjustedContentInset.top;
|
||
}
|
||
return -scrollView.contentInset.top;
|
||
}
|
||
|
||
|
||
@end
|