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

news2025/2/26 5:37:56

首先查看效果图

就是开始滚动的时候,上面的头部和下面的内容是
一起滚动的,但是当滚动到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.coloradmin.cn/o/1281905.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu系统配置深度学习环境之nvidia显卡驱动和cuda安装

前言 NVIDIA 显卡驱动是为了确保 NVIDIA 显卡能够正确运行而开发的软件。显卡驱动负责与操作系统通信&#xff0c;管理显卡的各种功能&#xff0c;并提供性能优化和兼容性保证。安装适用于特定显卡型号和操作系统版本的最新驱动程序是确保显卡能够正常工作的重要步骤。 CUDA 是…

Python:核心知识点整理大全1-笔记

在本章中&#xff0c;你将运行自己的第一个程序——hello_ world.py。为 此&#xff0c;你首先需要检查自己的计算机是否安装了Python&#xff1b;如果没有安装&#xff0c; 你需要安装它。你还要安装一个文本编辑器&#xff0c;用于编写和运行Python 程序。你输入Python代码时…

【Altera】Cyclone10 FPGA DDR3使用

目录 开发板 硬件 框图 原理图 测试工具 DDR IP核配置 调试及遇到的问题 读写仲裁时序 问题1.拉高read后&#xff0c;wait一直没反应 问题2.DDR校正不过的一个可能性 延伸学习 开发板 Intel官方提供c10的开发套件&#xff1a;Intel Cyclone 10 GX FPGA Development …

Web前端JS如何控制 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据

写在前面&#xff1a; 接上篇博文&#xff1a;Web前端JS如何获取 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据 讲解了如何根据视频链接地址&#xff0c;实现在播放时实时的显示该视频的音频轨道情况&#xff0c;并实时的将各音频轨道数据以可视化&#x…

04. 函数

目录 1、前言 2、Python中的函数 2.1、内置函数 2.2、自定义函数 2.3、函数调用 3、函数的参数 3.1、形参和实参 3.2、位置参数&#xff08;Positional Arguments&#xff09; 3.3、默认参数&#xff08;Default Arguments&#xff09;&#xff1a; 3.4、关键字参数&a…

jsp页面之间传值的一些问题总结

在http协议中一共有4种方法来完成这件事情&#xff1a; 1&#xff09;url传值 2&#xff09;表单传值 3&#xff09;Cookie方法 4&#xff09;Session方法 1、URL传值 将页面1中的值传给页面2 <body> <%String str"123";int numberInteger.parseInt(s…

Siemens-NXUG二次开发-C/C++/Python环境配置[20231204]

Siemens-NXUG二次开发-C/C/Python运行方式[20231204] 1.NX/UG C/C/Python API官方开发文档2.运行方式2.1内部模式2.2 外部模式2.3 许可证书服务器启动 3.C/C环境配置4.Python环境配置5.第三方环境配置 1.NX/UG C/C/Python API官方开发文档 西门子NX/UG Python api开发文档&…

Python----练习:使用面向对象实现报名系统开发

第一步&#xff1a;分析哪些动作是由哪些实体发出的 学生提出报名 学生提供相关资料 学生缴费 机构收费 教师分配教室 班级增加学生信息 于是&#xff0c;在整个过程中&#xff0c;一共有四个实体&#xff1a;学生、机构、教师、班级&#xff01;在现实中的一个具体的实…

【Cell Signaling + 神经递质(neurotransmitter) ; 神经肽 】

Neuroscience EndocytosisExcitatory synapse pathwayGlutamatergic synapseInflammatory PainInhibitors of axonal regenerationNeurotrophin signaling pathwaySecreted Extracellular VesiclesSynaptic vesicle cycle

shell编程系列(11)-使用grep查找文本

文章目录 前言grep的使用根据关键字查找反向查找 结语 前言 grep命令也是我们在日常使用linux&#xff0c;编写shell脚本中会用到的一个高频命令&#xff0c;grep主要是帮助我们查找我们想要的内容&#xff0c;类似于我们在office word里面的 Ctrl f 查找功能&#xff0c;但是…

力扣每日一题day26[42. 接雨水]

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] …

redis 常见问题分析

目录 redis 使用分析 一、redis 双写一致性分析 常见方式 1、先写数据库&#xff0c;后写缓存 2、先写数据库&#xff0c;后删缓存 3、先删缓存&#xff0c;再写数据库 4、延迟双删 二、redis 常见异常分析 一、缓存穿透 1、概念 2、解决方案 二、缓存雪崩 1、概念…

【BAT】关于在for中SET变量失效的问题

如题&#xff0c;在for中进行一些细化操作时发现SET变量一直不成功(尤其在读取文件时)   简单搜了下看到有个帖子(BAT求助与讨论-SET变量失效)是讨论这个问题的&#xff0c;然后有个老哥给出的解答&#xff0c;其中使用了使用CALL&#xff0c;这让我想起几天前看到的一个博客…

YouTube Premium 会员白嫖教程

前言 YouTube是美国Alphabet旗下的视频分享网站&#xff0c;也是目前全球最大的视频搜索和分享平台&#xff0c;同时允许用户上传、观看、分享及评论视频 1、点击自己的头像&#xff0c;点击购买内容与会员 2、点击免费试订 3、这里选择个人 4、点击开始试用一个月 5、添加一…

每周一算法:背包问题(三)多重背包

多重背包 有 N N N件物品和一个容量是 M M M的背包。第 i i i种物品最多有 s i s_i si​件&#xff0c;每件的体积是 v i v_i vi​&#xff0c;价值是 w i w_i wi​。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输…

基于ssm Vue的戒烟网站源码和论文

基于ssm Vue的戒烟网站源码和论文734 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 环境&#xff1a; jdk8 tomcat8.5 开发技术 ssm 摘要 随着互联网的高速发展&#xff0c;线上管理成为当代人们管理事物的重要手段之一&#xff…

C++ 系列 第五篇 C++ 算术运算符及类型转换

系列文章 C 系列 前篇 为什么学习C 及学习计划-CSDN博客 C 系列 第一篇 开发环境搭建&#xff08;WSL 方向&#xff09;-CSDN博客 C 系列 第二篇 你真的了解C吗&#xff1f;本篇带你走进C的世界-CSDN博客 C 系列 第三篇 C程序的基本结构-CSDN博客 C 系列 第四篇 C 数据类型…

nodejs微信小程序+python+PHP问卷调查系统的设计与实现-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

从存储器原理看 cpu 内存墙的本质

冯诺伊曼结构是个 cpu 和主存储器的通信结构&#xff0c;一个通信过程时延可分为处理时延&#xff0c;传播时延&#xff0c;排队时延&#xff0c;三者共同构筑了内存墙。其中处理时延最核心也最无解&#xff0c;先看它。 看个作为标准存储单元的双稳态触发器(至少教科书上就这…

2.1 Linux C 编程

一、Hello World 1、在用户根目录下创建一个C_Program&#xff0c;并在这里面创建3.1文件夹来保存Hellow World程序&#xff1b; 2、安装最新版nvim ①sudo apt-get install ninja-build gettext cmake unzip curl ②sudo apt install lua5.1 ③git clone https://github.…