封装了一个顺滑嵌套滚动的框架

2023-12-04 11:04

本文主要是介绍封装了一个顺滑嵌套滚动的框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

首先查看效果图

就是开始滚动的时候,上面的头部和下面的内容是
一起滚动的,但是当滚动到segment 的时候,segment
是悬停 的,下面的tableView是分区的
请添加图片描述

架构设计

我们设计一个架构,以下面的tablView为主体,上面的内容放在tablView 的contentInset.top范围内,当上滑滚动的时候,如果滑动距离超过了segment的位置,则将segment放在tableView的父视图上,这样达到了一个悬停的效果,看上去是悬停了,其实是segment的父视图从tableView 变成了tableView的父视图,下拉的时候同样如此,当下拉达到临界便宜量的时候,segment的就从tableView的父视图变成了tablView,这样就跟着tableView滚动了,达到了顺畅嵌套滚动的效果

代码

`
@interface LBPageSmoothView()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UIGestureRecognizerDelegate>

@property (nonatomic, weak) id dataSource;
@property (nonatomic, strong) GKPageSmoothCollectionView *listCollectionView;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id> *listDict;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, UIView *> *listHeaderDict;

@property (nonatomic, assign) LBPageSmoothHoverType hoverType;

@property (nonatomic, strong) UIView *headerContainerView;
@property (nonatomic, weak) UIView *headerView;
@property (nonatomic, weak) UIView *segmentedView;
@property (nonatomic, weak) UIScrollView *currentListScrollView;

@property (nonatomic, strong) UIView *bottomContainerView;

@property (nonatomic, assign) BOOL syncListContentOffsetEnabled;
@property (nonatomic, assign) CGFloat currentHeaderContainerViewY;

@property (nonatomic, assign) CGFloat headerContainerHeight;
@property (nonatomic, assign) CGFloat headerHeight;
@property (nonatomic, assign) CGFloat segmentedHeight;
@property (nonatomic, assign) CGFloat currentListInitializeContentOffsetY;

@property (nonatomic, assign) NSInteger currentIndex;

@property (nonatomic, assign) BOOL isLoaded;

@property (nonatomic, strong) UIPanGestureRecognizer *panGesture;

@property (nonatomic, weak) UIScrollView *scrollView;
@property (nonatomic, assign) BOOL isDragScrollView;
@property (nonatomic, assign) CGFloat lastTransitionY;
@property (nonatomic, assign) BOOL isOnTop;

@property (nonatomic, assign) CGFloat currentListPanBeganContentOffsetY;
@property (nonatomic, assign) BOOL originBounces;
@property (nonatomic, assign) BOOL originShowsVerticalScrollIndicator;

@end

@implementation LBPageSmoothView

  • (instancetype)initWithDataSource:(id)dataSource {
    if (self = [super initWithFrame:CGRectZero]) {
    self.dataSource = dataSource;
    _listDict = [NSMutableDictionary dictionary];
    _listHeaderDict = [NSMutableDictionary dictionary];
    _ceilPointHeight = 0;

      [self addSubview:self.listCollectionView];[self addSubview:self.headerContainerView];[self refreshHeaderView];
    

    }
    return self;
    }

  • (void)dealloc {
    for (id listItem in self.listDict.allValues) {
    [listItem.listScrollView removeObserver:self forKeyPath:@“contentOffset”];
    }
    }

  • (void)layoutSubviews {
    [super layoutSubviews];

    if (self.listCollectionView.superview == self) {
    self.listCollectionView.frame = self.bounds;
    }else {
    CGRect frame = self.listCollectionView.frame;
    frame.origin.y = self.segmentedHeight;
    frame.size.height = self.bottomContainerView.frame.size.height - self.segmentedHeight;
    self.listCollectionView.frame = frame;
    }
    }

  • (void)refreshHeaderView {
    [self loadHeaderAndSegmentedView];

    __weak __typeof(self) weakSelf = self;
    [self refreshWidthCompletion:^(CGSize size) {
    __strong __typeof(weakSelf) self = weakSelf;
    CGRect frame = self.headerContainerView.frame;
    if (CGRectEqualToRect(frame, CGRectZero)) {
    frame = CGRectMake(0, 0, size.width, self.headerContainerHeight);
    }else {
    frame.size.height = self.headerContainerHeight;
    }
    self.headerContainerView.frame = frame;

      self.headerView.frame = CGRectMake(0, 0, size.width, self.headerHeight);self.segmentedView.frame =  CGRectMake(0, self.headerHeight, size.width, self.segmentedHeight);for (id<LBPageSmoothListViewDelegate> list in self.listDict.allValues) {list.listScrollView.contentInset = UIEdgeInsetsMake(self.headerContainerHeight, 0, 0, 0);}if (self.isBottomHover) {self.bottomContainerView.frame = CGRectMake(0, size.height - self.segmentedHeight, size.width, size.height - self.ceilPointHeight);if (self.headerHeight > size.height) {self.segmentedView.frame = CGRectMake(0, 0, size.width, self.segmentedHeight);[self.bottomContainerView addSubview:self.segmentedView];}}
    

    }];
    }

  • (void)updateSegmentHeight:(CGFloat)height
    {
    CGFloat offset = height - self.segmentedHeight;
    self.segmentedHeight = height;
    self.headerContainerHeight = self.headerHeight + self.segmentedHeight;
    for (id list in self.listDict.allValues) {
    CGFloat currentOffset = list.listScrollView.contentOffset.y;
    list.listScrollView.contentInset = UIEdgeInsetsMake(self.headerContainerHeight, 0, 0, 0);
    CGPoint tooffset = CGPointMake(0, currentOffset - offset);
    list.listScrollView.contentOffset = tooffset;
    }

    self.segmentedView.frame = CGRectMake(CGRectGetMinX(self.segmentedView.frame), CGRectGetMinY(self.segmentedView.frame), CGRectGetWidth(self.segmentedView.frame), height);

    self.headerContainerView.frame = CGRectMake(CGRectGetMinX(self.headerContainerView.frame), CGRectGetMinY(self.headerContainerView.frame), CGRectGetWidth(self.headerContainerView.frame), self.headerContainerHeight);
    for (UIView *listHeaderView in self.listHeaderDict.allValues) {
    listHeaderView.frame = CGRectMake(0, -self.headerContainerHeight, self.bounds.size.width, self.headerContainerHeight);
    }
    }

  • (void)reloadData {
    self.currentListScrollView = nil;
    self.currentIndex = self.defaultSelectedIndex;
    self.syncListContentOffsetEnabled = NO;
    self.currentHeaderContainerViewY = 0;
    self.isLoaded = YES;

    [self.listHeaderDict removeAllObjects];

    for (id list in self.listDict.allValues) {
    [list.listScrollView removeObserver:self forKeyPath:@“contentOffset”];
    [list.listView removeFromSuperview];
    }
    [_listDict removeAllObjects];

    __weak __typeof(self) weakSelf = self;
    [self refreshWidthCompletion:^(CGSize size) {
    __strong __typeof(weakSelf) self = weakSelf;
    [self.listCollectionView setContentOffset:CGPointMake(size.width * self.currentIndex, 0) animated:NO];
    [self.listCollectionView reloadData];
    }];
    }

  • (void)scrollToOriginalPoint {
    [self.currentListScrollView setContentOffset:CGPointMake(0, -self.headerContainerHeight) animated:YES];
    }

  • (void)scrollToCriticalPoint {
    [self.currentListScrollView setContentOffset:CGPointMake(0, -(self.segmentedHeight+self.ceilPointHeight)) animated:YES];
    }

  • (void)showingOnTop {
    if (self.bottomContainerView.isHidden) return;
    [self dragBegan];
    [self dragShowing];
    }

  • (void)showingOnBottom {
    if (self.bottomContainerView.isHidden) return;
    [self dragDismiss];
    }

  • (void)setBottomHover:(BOOL)bottomHover {
    _bottomHover = bottomHover;

    if (bottomHover) {
    __weak __typeof(self) weakSelf = self;
    [self refreshWidthCompletion:^(CGSize size) {
    __strong __typeof(weakSelf) self = weakSelf;
    self.bottomContainerView.frame = CGRectMake(0, size.height - self.segmentedHeight, size.width, size.height - self.ceilPointHeight);
    [self addSubview:self.bottomContainerView];

          if (self.headerHeight > size.height) {self.segmentedView.frame = CGRectMake(0, 0, size.width, self.segmentedHeight);[self.bottomContainerView addSubview:self.segmentedView];}}];
    

    }else {
    [self.bottomContainerView removeFromSuperview];
    }
    }

  • (void)setAllowDragBottom:(BOOL)allowDragBottom {
    _allowDragBottom = allowDragBottom;

    if (self.bottomHover) {
    if (allowDragBottom) {
    [self.bottomContainerView addGestureRecognizer:self.panGesture];
    }else {
    [self.bottomContainerView removeGestureRecognizer:self.panGesture];
    }
    }
    }

#pragma mark - UICollectionViewDataSource

  • (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.isLoaded ? 1 : 0;
    }

  • (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [self.dataSource numberOfListsInSmoothView:self];
    }

  • (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GKPageSmoothViewCellID forIndexPath:indexPath];
    id list = self.listDict[@(indexPath.item)];
    if (list == nil) {
    list = [self.dataSource smoothView: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).estimatedSectionHeaderHeight = 0;((UITableView *)listScrollView).estimatedSectionFooterHeight = 0;}if (@available(iOS 11.0, *)) {listScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;}if (CGSizeEqualToSize(listScrollView.contentSize, CGSizeZero)) {listScrollView.contentSize = CGSizeMake(listScrollView.contentSize.width, self.bounds.size.height);}if (!self.isOnTop) {listScrollView.contentInset = UIEdgeInsetsMake(self.headerContainerHeight, 0, self.bottomInset, 0);self.currentListInitializeContentOffsetY = -listScrollView.contentInset.top + MIN(-self.currentHeaderContainerViewY, (self.headerHeight - self.ceilPointHeight));listScrollView.contentOffset = CGPointMake(0, self.currentListInitializeContentOffsetY);}UIView *listHeader = [[UIView alloc] initWithFrame:CGRectMake(0, -self.headerContainerHeight, self.bounds.size.width, self.headerContainerHeight)];[listScrollView addSubview:listHeader];if (!self.isOnTop && self.headerContainerView.superview == nil) {[listHeader addSubview:self.headerContainerView];}self.listHeaderDict[@(indexPath.item)] = listHeader;[listScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];// bug fix #69 修复首次进入时可能出现的headerView无法下拉的问题[listScrollView setContentOffset:listScrollView.contentOffset];
    

    }
    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;
    }

  • (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.listCollectionView.bounds.size;
    }

  • (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)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    self.panGesture.enabled = NO;
    }

  • (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if ([self.delegate respondsToSelector:@selector(smoothView:scrollViewDidScroll:)]) {
    [self.delegate smoothView:self scrollViewDidScroll:scrollView];
    }

    NSInteger index = scrollView.contentOffset.x / scrollView.bounds.size.width;

    NSInteger ratio = (int)scrollView.contentOffset.x % (int)scrollView.bounds.size.width;

    if (!self.isOnTop) {
    UIScrollView *listScrollView = self.listDict[@(index)].listScrollView;
    if (index != self.currentIndex && ratio == 0 && !(scrollView.isDragging || scrollView.isDecelerating) && listScrollView.contentOffset.y <= -(self.segmentedHeight + self.ceilPointHeight)) {
    [self horizontalScrollDidEndAtIndex:index];
    }else {
    // 左右滚动的时候,把headerContainerView添加到self,达到悬浮的效果
    if (self.headerContainerView.superview != self) {
    CGRect frame = self.headerContainerView.frame;
    frame.origin.y = self.currentHeaderContainerViewY;
    self.headerContainerView.frame = frame;
    [self addSubview:self.headerContainerView];
    }
    }
    }

    if (index != self.currentIndex && ratio == 0) {
    self.currentIndex = index;
    }
    }

  • (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
    NSInteger index = scrollView.contentOffset.x / scrollView.bounds.size.width;
    [self horizontalScrollDidEndAtIndex:index];
    }
    self.panGesture.enabled = YES;
    }

  • (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSInteger index = scrollView.contentOffset.x / scrollView.bounds.size.width;
    [self horizontalScrollDidEndAtIndex:index];
    self.panGesture.enabled = YES;
    }

  • (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
    // 修复快速闪烁问题
    self.currentIndex = scrollView.contentOffset.x / scrollView.bounds.size.width;
    self.currentListScrollView = self.listDict[@(self.currentIndex)].listScrollView;
    }

#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 listScrollViewDidScroll:scrollView];
    }
    }else {
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
    }

#pragma mark - Gesture

  • (void)handlePanGesture:(UIPanGestureRecognizer *)panGesture {
    if (panGesture.state == UIGestureRecognizerStateBegan) {
    if ([self.delegate respondsToSelector:@selector(smoothViewDragBegan:)]) {
    [self.delegate smoothViewDragBegan:self];
    }
    [self dragBegan];

      // 记录scrollView的某些属性self.originBounces = self.scrollView.bounces;self.originShowsVerticalScrollIndicator = self.scrollView.showsVerticalScrollIndicator;// bug fix #47,当UIScrollView向下滚动的时候,向下拖拽完成手势操作导致的错乱问题if (self.currentListScrollView.isDecelerating) {[self.currentListScrollView setContentOffset:self.currentListScrollView.contentOffset animated:NO];}
    

    }

    CGPoint translation = [panGesture translationInView:self.bottomContainerView];
    if (self.isDragScrollView) {
    [self allowScrolling:self.scrollView];
    // 当UIScrollView在最顶部时,处理视图的滑动
    if (self.scrollView.contentOffset.y <= 0) {
    if (translation.y > 0) { // 向下拖拽
    [self forbidScrolling:self.scrollView];
    self.isDragScrollView = NO;

              CGRect frame = self.bottomContainerView.frame;frame.origin.y += translation.y;self.bottomContainerView.frame = frame;if (!self.isAllowDragScroll) {self.scrollView.panGestureRecognizer.enabled = NO;self.scrollView.panGestureRecognizer.enabled = YES;}}}
    

    }else {
    CGFloat offsetY = self.scrollView.contentOffset.y;
    CGFloat ceilPointY = self.ceilPointHeight;

      if (offsetY <= 0) {[self forbidScrolling:self.scrollView];if (translation.y > 0) { // 向下拖拽CGRect frame = self.bottomContainerView.frame;frame.origin.y += translation.y;self.bottomContainerView.frame = frame;}else if (translation.y < 0 && self.bottomContainerView.frame.origin.y > ceilPointY) { // 向上拖拽CGRect frame = self.bottomContainerView.frame;frame.origin.y = MAX((self.bottomContainerView.frame.origin.y + translation.y), ceilPointY);self.bottomContainerView.frame = frame;}}else {if (translation.y < 0 && self.bottomContainerView.frame.origin.y > ceilPointY) {CGRect frame = self.bottomContainerView.frame;frame.origin.y = MAX((self.bottomContainerView.frame.origin.y + translation.y), ceilPointY);self.bottomContainerView.frame = frame;}if (self.bottomContainerView.frame.origin.y > ceilPointY) {[self forbidScrolling:self.scrollView];}else {[self allowScrolling:self.scrollView];}}
    

    }

    if (panGesture.state == UIGestureRecognizerStateEnded) {
    CGPoint velocity = [panGesture velocityInView:self.bottomContainerView];
    if (velocity.y < 0) { // 上滑
    if (fabs(self.lastTransitionY) > 5 && self.isDragScrollView == NO) {
    [self dragShowing];
    }else {
    if (self.bottomContainerView.frame.origin.y > (self.ceilPointHeight + self.bottomContainerView.frame.size.height / 2)) {
    [self dragDismiss];
    }else {
    [self dragShowing];
    }
    }
    }else { // 下滑
    if (fabs(self.lastTransitionY) > 5 && self.isDragScrollView == NO && !self.scrollView.isDecelerating) {
    [self dragDismiss];
    }else {
    if (self.bottomContainerView.frame.origin.y > (self.ceilPointHeight + self.bottomContainerView.frame.size.height / 2)) {
    [self dragDismiss];
    }else {
    [self dragShowing];
    }
    }
    }

      [self allowScrolling:self.scrollView];self.isDragScrollView = NO;self.scrollView = nil;
    

    }

    [panGesture setTranslation:CGPointZero inView:self.bottomContainerView];
    self.lastTransitionY = translation.y;
    }

#pragma mark - UIGestureRecognizerDelegate

  • (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if (gestureRecognizer == self.panGesture) {
    UIView *touchView = touch.view;
    while (touchView != nil) {
    if (touchView == self.currentListScrollView) {
    self.scrollView = (UIScrollView *)touchView;
    self.isDragScrollView = YES;
    break;
    }else if (touchView == self.bottomContainerView) {
    self.isDragScrollView = NO;
    break;
    }
    touchView = (UIView *)[touchView nextResponder];
    }
    }
    return YES;
    }

  • (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
    // 左右滑动时禁止上下滑动
    CGPoint transition = [gestureRecognizer translationInView:gestureRecognizer.view];
    if (transition.x != 0) return NO;
    return YES;
    }

  • (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if (gestureRecognizer == self.panGesture) {
    if (otherGestureRecognizer == self.scrollView.panGestureRecognizer) {
    return YES;
    }
    }
    return NO;
    }

#pragma mark - Private Methods

  • (void)listScrollViewDidScroll:(UIScrollView *)scrollView {
    if (self.listCollectionView.isDragging || self.listCollectionView.isDecelerating) return;

    if (self.isOnTop) { // 在顶部时无需处理headerView
    // 取消scrollView下滑时的弹性效果
    // buf fix #47,iOS12及以下系统isDragging会出现不准确的情况,所以这里改为用isTracking判断
    if (self.isAllowDragScroll && (scrollView.isTracking || scrollView.isDecelerating)) {
    if (scrollView.contentOffset.y < 0) {
    scrollView.contentOffset = CGPointZero;
    }
    }

      if ([self.delegate respondsToSelector:@selector(smoothView:listScrollViewDidScroll:contentOffset:)]) {[self.delegate smoothView:self listScrollViewDidScroll:scrollView contentOffset:scrollView.contentOffset];}
    

    }else { // 不在顶部,通过列表scrollView滑动确定悬浮位置
    NSInteger listIndex = [self listIndexForListScrollView:scrollView];
    if (listIndex != self.currentIndex) return;
    self.currentListScrollView = scrollView;
    CGFloat contentOffsetY = scrollView.contentOffset.y + self.headerContainerHeight;

      if (contentOffsetY < (self.headerHeight - self.ceilPointHeight)) {self.hoverType = LBPageSmoothHoverTypeNone;self.syncListContentOffsetEnabled = YES;self.currentHeaderContainerViewY = -contentOffsetY;for (id<LBPageSmoothListViewDelegate> list in self.listDict.allValues) {if (list.listScrollView != scrollView) {[list.listScrollView setContentOffset:scrollView.contentOffset animated:NO];}}UIView *listHeader = [self listHeaderForListScrollView:scrollView];if (self.headerContainerView.superview != listHeader) {CGRect frame = self.headerContainerView.frame;frame.origin.y = 0;self.headerContainerView.frame = frame;[listHeader addSubview:self.headerContainerView];}if (self.isControlVerticalIndicator && self.ceilPointHeight != 0) {self.currentListScrollView.showsVerticalScrollIndicator = NO;}if (self.isBottomHover) {if (contentOffsetY < (self.headerContainerHeight - self.frame.size.height)) {self.hoverType = LBPageSmoothHoverTypeBottom;if (self.segmentedView.superview != self.bottomContainerView) {self.bottomContainerView.hidden = NO;CGRect frame = self.segmentedView.frame;frame.origin.y = 0;self.segmentedView.frame = frame;[self.bottomContainerView addSubview:self.segmentedView];}}else {if (self.segmentedView.superview != self.headerContainerView) {self.bottomContainerView.hidden = YES;CGRect frame = self.segmentedView.frame;frame.origin.y = self.headerHeight;self.segmentedView.frame = frame;[self.headerContainerView addSubview:self.segmentedView];}}}}else {self.hoverType = LBPageSmoothHoverTypeTop;if (self.headerContainerView.superview != self) {CGRect frame = self.headerContainerView.frame;frame.origin.y = - (self.headerHeight - self.ceilPointHeight);self.headerContainerView.frame = frame;[self addSubview:self.headerContainerView];}if (self.isControlVerticalIndicator) {self.currentListScrollView.showsVerticalScrollIndicator = YES;}if (self.syncListContentOffsetEnabled) {self.syncListContentOffsetEnabled = NO;self.currentHeaderContainerViewY = -(self.headerHeight - self.ceilPointHeight);for (id<LBPageSmoothListViewDelegate> listItem in self.listDict.allValues) {if (listItem.listScrollView != scrollView) {[listItem.listScrollView setContentOffset:CGPointMake(0, -(self.segmentedHeight + self.ceilPointHeight)) animated:NO];}}}}CGPoint contentOffset = CGPointMake(scrollView.contentOffset.x, contentOffsetY);if ([self.delegate respondsToSelector:@selector(smoothView:listScrollViewDidScroll:contentOffset:)]) {[self.delegate smoothView:self listScrollViewDidScroll:scrollView contentOffset:contentOffset];}
    

    }
    }

  • (void)loadHeaderAndSegmentedView {
    self.headerView = [self.dataSource headerViewInSmoothView:self];
    self.segmentedView = [self.dataSource segmentedViewInSmoothView:self];
    [self.headerContainerView addSubview:self.headerView];
    [self.headerContainerView addSubview:self.segmentedView];

    self.headerHeight = self.headerView.bounds.size.height;
    self.segmentedHeight = self.segmentedView.bounds.size.height;
    self.headerContainerHeight = self.headerHeight + self.segmentedHeight;
    }

  • (void)refreshWidthCompletion:(void(^)(CGSize size))completion {
    if (self.bounds.size.width == 0) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    !completion ? : completion(self.bounds.size);
    });
    }else {
    !completion ? : completion(self.bounds.size);
    }
    }

  • (void)horizontalScrollDidEndAtIndex:(NSInteger)index {
    self.currentIndex = index;
    UIView *listHeader = self.listHeaderDict[@(index)];
    UIScrollView *listScrollView = self.listDict[@(index)].listScrollView;
    self.currentListScrollView = listScrollView;
    if (self.isOnTop) return;
    if (listHeader != nil && listScrollView.contentOffset.y <= -(self.segmentedHeight + self.ceilPointHeight)) {
    for (id listItem in self.listDict.allValues) {
    listItem.listScrollView.scrollsToTop = (listItem.listScrollView == listScrollView);
    }
    CGRect frame = self.headerContainerView.frame;
    frame.origin.y = 0;
    self.headerContainerView.frame = frame;
    if (self.headerContainerView.superview != listHeader) {
    [listHeader addSubview:self.headerContainerView];
    }
    }
    }

  • (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 numberOfListsInSmoothView:self];
    if (count <= 0 || index >= count) return;

    id list = self.listDict[@(index)];
    if (list && [list respondsToSelector:@selector(listViewDidAppear)]) {
    [list listViewDidAppear];
    }
    }

  • (void)listDidDisappear:(NSInteger)index {
    NSUInteger count = [self.dataSource numberOfListsInSmoothView:self];
    if (count <= 0 || index >= count) return;

    id list = self.listDict[@(index)];
    if (list && [list respondsToSelector:@selector(listViewDidDisappear)]) {
    [list listViewDidDisappear];
    }
    }

  • (void)allowScrolling:(UIScrollView *)scrollView {
    scrollView.bounces = self.originBounces;
    scrollView.showsVerticalScrollIndicator = self.originShowsVerticalScrollIndicator;
    }

  • (void)forbidScrolling:(UIScrollView *)scrollView {
    scrollView.contentOffset = CGPointZero;
    scrollView.bounces = NO;
    scrollView.showsVerticalScrollIndicator = NO;
    }

  • (void)dragBegan {
    self.isOnTop = YES;
    [self setupShowingLayout];
    }

  • (void)dragDismiss {
    [UIView animateWithDuration:0.25 animations:^{
    CGRect frame = self.bottomContainerView.frame;
    frame.origin.y = self.frame.size.height - self.segmentedHeight;
    self.bottomContainerView.frame = frame;
    } completion:^(BOOL finished) {
    [self setupDismissLayout];

      self.isOnTop = NO;if ([self.delegate respondsToSelector:@selector(smoothViewDragEnded:isOnTop:)]) {[self.delegate smoothViewDragEnded:self isOnTop:self.isOnTop];}
    

    }];
    }

  • (void)dragShowing {
    [UIView animateWithDuration:0.25 animations:^{
    CGRect frame = self.bottomContainerView.frame;
    frame.origin.y = self.ceilPointHeight;
    self.bottomContainerView.frame = frame;
    } completion:^(BOOL finished) {
    if ([self.delegate respondsToSelector:@selector(smoothViewDragEnded:isOnTop:)]) {
    [self.delegate smoothViewDragEnded:self isOnTop:self.isOnTop];
    }
    }];
    }

  • (void)setupShowingLayout {
    // 将headerContainerView添加到self
    if (self.headerContainerView.superview != self) {
    CGRect frame = self.headerContainerView.frame;
    frame.origin.y = -(self.currentListScrollView.contentOffset.y + self.headerContainerHeight);
    self.headerContainerView.frame = frame;
    [self insertSubview:self.headerContainerView belowSubview:self.bottomContainerView];
    }

    // 将listCollectionView添加到bottomContainerView
    if (self.listCollectionView.superview != self.bottomContainerView) {
    CGRect frame = self.listCollectionView.frame;
    frame.origin.y = self.segmentedHeight;
    frame.size.height = self.bottomContainerView.frame.size.height - self.segmentedHeight;
    self.listCollectionView.frame = frame;
    [self.bottomContainerView addSubview:self.listCollectionView];
    self->_listCollectionView.headerContainerView = nil;

      // 记录当前列表的滑动位置self.currentListPanBeganContentOffsetY = self.currentListScrollView.contentOffset.y;for (id<LBPageSmoothListViewDelegate> list in self.listDict.allValues) {list.listScrollView.contentInset = UIEdgeInsetsZero;list.listScrollView.contentOffset = CGPointZero;CGRect frame = list.listView.frame;frame.size = self.listCollectionView.bounds.size;list.listView.frame = frame;}
    

    }
    }

  • (void)setupDismissLayout {
    UIView *listHeader = [self listHeaderForListScrollView:self.currentListScrollView];
    if (self.headerContainerView.superview != listHeader) {
    CGRect frame = self.headerContainerView.frame;
    frame.origin.y = 0;
    self.headerContainerView.frame = frame;
    [listHeader addSubview:self.headerContainerView];
    }

    if (self.listCollectionView.superview != self) {
    self.listCollectionView.frame = self.bounds;
    [self insertSubview:self.listCollectionView belowSubview:self.bottomContainerView];
    self->_listCollectionView.headerContainerView = self.headerContainerView;

      for (id<LBPageSmoothListViewDelegate> list in self.listDict.allValues) {list.listScrollView.contentInset = UIEdgeInsetsMake(self.headerContainerHeight, 0, 0, 0);list.listScrollView.contentOffset = CGPointZero;CGRect frame = list.listView.frame;frame.size = self.listCollectionView.bounds.size;list.listView.frame = frame;}self.currentListScrollView.contentOffset = CGPointMake(0, self.currentListPanBeganContentOffsetY);
    

    }
    }

#pragma mark - Getter

  • (UICollectionView *)listCollectionView {
    if (!_listCollectionView) {
    UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    layout.minimumLineSpacing = 0;
    layout.minimumInteritemSpacing = 0;
    _listCollectionView = [[GKPageSmoothCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
    _listCollectionView.dataSource = self;
    _listCollectionView.delegate = self;
    _listCollectionView.pagingEnabled = YES;
    _listCollectionView.bounces = NO;
    _listCollectionView.showsHorizontalScrollIndicator = NO;
    _listCollectionView.scrollsToTop = NO;
    [_listCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:GKPageSmoothViewCellID];
    if (@available(iOS 11.0, *)) {
    _listCollectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
    if (@available(iOS 10.0, *)) {
    _listCollectionView.prefetchingEnabled = NO;
    }
    _listCollectionView.backgroundColor = [UIColor clearColor];
    _listCollectionView.headerContainerView = self.headerContainerView;
    }
    return _listCollectionView;
    }

  • (UIView *)headerContainerView {
    if (!_headerContainerView) {
    _headerContainerView = [UIView new];
    }
    return _headerContainerView;
    }

  • (UIView *)bottomContainerView {
    if (!_bottomContainerView) {
    _bottomContainerView = [UIView new];
    _bottomContainerView.backgroundColor = UIColor.whiteColor;
    }
    return _bottomContainerView;
    }

  • (UIPanGestureRecognizer *)panGesture {
    if (!_panGesture) {
    _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
    _panGesture.delegate = self;
    }
    return _panGesture;
    }

@end
``

这篇关于封装了一个顺滑嵌套滚动的框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/453225

相关文章

鸿蒙中Axios数据请求的封装和配置方法

《鸿蒙中Axios数据请求的封装和配置方法》:本文主要介绍鸿蒙中Axios数据请求的封装和配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.配置权限 应用级权限和系统级权限2.配置网络请求的代码3.下载在Entry中 下载AxIOS4.封装Htt

SpringBoot中封装Cors自动配置方式

《SpringBoot中封装Cors自动配置方式》:本文主要介绍SpringBoot中封装Cors自动配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot封装Cors自动配置背景实现步骤1. 创建 GlobalCorsProperties

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

python展开嵌套列表的多种方法

《python展开嵌套列表的多种方法》本文主要介绍了python展开嵌套列表的多种方法,包括for循环、列表推导式和sum函数三种方法,具有一定的参考价值,感兴趣的可以了解一下... 目录一、嵌套列表格式二、嵌套列表展开方法(一)for循环(1)for循环+append()(2)for循环+pyPhWiFd

最新Spring Security实战教程之Spring Security安全框架指南

《最新SpringSecurity实战教程之SpringSecurity安全框架指南》SpringSecurity是Spring生态系统中的核心组件,提供认证、授权和防护机制,以保护应用免受各种安... 目录前言什么是Spring Security?同类框架对比Spring Security典型应用场景传统

Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)

《Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)》:本文主要介绍Java导入、导出excel的相关资料,讲解了使用Java和ApachePOI库将数据导出为Excel文件,包括... 目录前言一、引入Apache POI依赖二、用法&步骤2.1 创建Excel的元素2.3 样式和字体2.

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

Python结合Flask框架构建一个简易的远程控制系统

《Python结合Flask框架构建一个简易的远程控制系统》这篇文章主要为大家详细介绍了如何使用Python与Flask框架构建一个简易的远程控制系统,能够远程执行操作命令(如关机、重启、锁屏等),还... 目录1.概述2.功能使用系统命令执行实时屏幕监控3. BUG修复过程1. Authorization