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

首先查看效果图

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

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

相关文章

SiC MOSFET体二极管双极性退化及电流密度影响的研究

标题&#xff1a;Investigation of the bipolar degradation of SiC MOSFET body diodes and the influence of current density (IEEE International Reliability Physics Symposium (IRPS)) 摘要 摘要-双极退化在使用双极操作模式的4H-SiC器件中仍然是一个需要考虑的关键问题…

Excel 表列序号

题目链接 Excel 表列序号 题目描述 注意点 columnTitle 仅由大写英文组成1 < columnTitle.length < 7 解答思路 对于"CAB"&#xff0c;计算其序列号的思路&#xff1a;字母B的贡献值为2&#xff0c;字母A的贡献值为1 * 26&#xff0c;字母C的贡献值为3 * …

排查200M宽带下行速度低于100M问题(七十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

流量分析1--菜刀666

1&#xff1a;菜刀666&#xff1a; 题目描述 分析流量包&#xff0c;过滤http数据流 追踪TCP数据流 对比第5个流和第7个流发现&#xff0c;同样的目录下 多出了6666.jpg。猜测是由攻击者上传&#xff0c;直接在请求包里搜索FFD8--FFD9 保存为1.jpg 利用foremost工具对1.jpg进…

“无忧文件安全!上海迅软DSE文件加密软件助您轻松管控分公司数据!

许多大型企业集团由于旗下有着分布在不同城市的分支机构&#xff0c;因此在规划数据安全解决方案时&#xff0c;不适合采用市面上常见的集中式部署方式来管控各分部服务器&#xff0c;而迅软DSE文件加密软件支持采用分布式部署的方式来解决这一问题。 企业用户只需在总部内部署…

【移动端vant 地址选择滑动不了】

分析&#xff1a; H5页面直接在浏览器打开是没有任何问题的&#xff0c;但是内嵌到小程序中就会出现&#xff0c;目前已出现在抖音&#xff0c;快手&#xff0c;小程序中&#xff0c;其他的没有试 大致看了一下&#xff0c;滑动不了的原因&#xff0c;可能是页面禁止滑动或滚动…

《使用ThinkPHP6开发项目》 - 设置项目环境变量

《使用ThinkPHP6开发项目》 - 安装ThinkPHP框架-CSDN博客 在上一编我们讲了ThinkPHP6框架的创建&#xff0c;创建完成ThinkPHP6框架后&#xff0c;我们这里就可以开始设置我们的环境变量了。 安装完成ThinkPHP6框架生成的项目文件 修改项目配置我们修改项目config文件夹里的对…

MySQL 数据库如何实现 XA 规范?

本文我们来讨论 MySQL 的 XA 规范有哪些应用相关的内容。 MySQL 为我们提供了分布式事务解决方案&#xff0c;在前面的内容中提到过 binlog 的同步&#xff0c;其实是 MySQL XA 规范的一个应用&#xff0c;那么 XA 规范是如何定义的&#xff0c;具体又是如何应用的呢&#xff…

vscode如何为python设置静态类型检测工具:mypy

设置好之后的效果如下图所示&#xff0c;你可以在下方problems一栏看到工作区所有文件存在的问题 安装mypy就像其他插件一样&#xff0c;在extensions中搜索mypy&#xff0c;再install即可。 但是安装以后&#xff0c;我的vscode弹出了以下通知&#xff1a; The mypy daemon e…

使用DockerUI结合内网穿透工具轻松实现公网访问和管理docker容器

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

【智能家居】六、摄像头安装实现监控功能点、人脸识别(face_recognition的使用)

一、定义及第三方库的说明 OCR &#xff08;光学字符识别&#xff09;文字识别、图像识别mjpg-streamer实时流式传输视频工具树莓派mjpg-streamer Face Recognition人脸识别 Dlib 计算机视觉问题的工具和算法face_recognition库OpenCV 计算机视觉和机器学习的开源库 三、香…

万界星空科技MES---制造企业的加工生产模式

在现代制造业中&#xff0c;加工生产模式是制造企业组织和管理生产过程的重要方面。不同的加工模式适用于不同的生产需求和产品类型。其中流水型、离散型和混合型是三种常见的加工生产模式。1. 流水型加工模式 流水型加工模式是一种高度自动化的生产方式&#xff0c;适用于…

Docker部署开源分布式任务调度系统DolphinScheduler与远程访问办公

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

什么是封箱打包一体机

自动封箱打包机是什么&#xff1f;有哪些特点&#xff1f; 自动封箱打包机的介绍 自动封箱打包机是一种集自动封箱及捆扎为一体的包装机&#xff0c;可配套自动化包装流水线使用&#xff0c;自动上下封胶带、多道捆扎&#xff0c;实现后道无人化包装。工作效率高&#xff0c…

深入了解Java 8日期时间新玩法之Year、YearMonth、MonthDay、DayOfWeek

推荐语 在这篇文章中&#xff0c;我们将深入探讨Java 8中Year、YearMonth、MonthDay和DayOfWeek类的功能和使用方法。这些类是在Java 8中引入的新的日期时间API的一部分&#xff0c;它们为我们提供了更灵活、更易用的日期和时间处理能力。 尽管这些类在Java 8中已经出现&…

分享一个基础面试题---手写call

分享一个基础面试题---手写call 手写call笔记第一步第二步第三步 手写call笔记 call()&#xff1a;在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。 let foo {value:1 }; function bar(){console.log(this.value); } bar.call(foo);//1注意两点&…

Android音量调节参考一

基于android 9平台分析。 在Android系统中&#xff0c;默认的设备(phone等)音量都是分开控制的&#xff0c;这些包括媒体、铃声、闹铃、蓝牙、通话通过音频流来区别不同的音量类型。每种流类型都定义最大音量、最小音量及默认音量&#xff0c;Android 9定了了11中音频流类型&am…

点评项目——商户查询缓存

2023.12.7 redis实现商户查询缓存 在企业开发中&#xff0c;用户的访问量动辄成百上千万&#xff0c;如果没有缓存机制&#xff0c;数据库将承受很大的压力。本章我们使用redis来实现商户查询缓存。 原来的操作是根据商铺id直接从数据库查询商铺信息&#xff0c;为了防止频繁地…

python中时间戳与时间字符的相互转换

1.获取时间的方法 使用的是time模块 import time time1 time.time() # time1: 1701934920.676534 timestruct time.localtime(time1) # timestruct: time.struct_time(tm_year2023, tm_mon12, tm_mday7, tm_hour15, tm_min42, tm_sec0, tm_wday3, tm_yday341, tm_isdst0)2.…

Apache Hive(部署+SQL+FineBI构建展示)

Hive架构 Hive部署 VMware虚拟机部署 一、在node1节点安装mysql数据库 二、配置Hadoop 三、下载 解压Hive 四、提供mysql Driver驱动 五、配置Hive 六、初始化元数据库 七、启动Hive(Hadoop用户) chown -R hadoop:hadoop apache-hive-3.1.3-bin hive 阿里云部…