| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 | ////  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
 |