IOS-使用UICollectionView+UIScrollView实现悬停、导航条跟随列表滚动滑动

  • 本例子主要实现2个主要功能
  • 悬停
  • UIScrollView 的中Item随着滚动列表联动变化
  • 效果图:


    20200428151038-7c011ad592.[gif-2-mp4.com].gif
  • 悬停的变化通过 监听UIConllectionView ContentSet变化再改变待悬停视图和y坐标即可,核心代码如下:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
self.pageSuperViewSuperView.lgf_y = MAX(-18.0, (scrollView.contentOffset.y - 8.0));
}

*在滑动列表时,想达到滑动条,需要知道每一分组开始的y坐标,对于每一组中cell调试固定时我们可以这样做

-(void) initData{

    self.dataArray = [[NSMutableArray alloc] init];
    self.dataArray = @[@{@"便民生活" : @[@"充值中心", @"信用卡还款", @"生活缴费", @"城市服务", @"我的快递", @"医疗健康", @"记账本", @"发票管家", @"车主服务", @"交通出行", @"体育服务", @"安全备忘"]},
    @{@"财富管理" : @[@"余额宝", @"花呗", @"芝麻信用", @"借呗", @"蚂蚁保险", @"汇率换算"]},
    @{@"资金往来" : @[@"转账", @"红包", @"AA收款", @"亲情号", @"商家服务"]},
    @{@"购物娱乐" : @[@"出境", @"彩票", @"奖励金"]},
    @{@"教育公益" : @[@"校园生活", @"蚂蚁森林", @"蚂蚁庄园", @"中小学", @"运动", @"亲子账户"]},
    @{@"第三方服务" : @[@"淘票票电影", @"滴滴出行", @"饿了么外卖", @"天猫", @"淘宝", @"火车票机票", @"飞猪酒店", @"蚂上租租房", @"高德打车", @"哈啰出行"]}].mutableCopy;
    
    
    // 计算需要选中的 contentOffset.y 保存
    self.pageSelectYArray = [[NSMutableArray alloc] init];
    __block CGFloat oldY = 0.0;
    __block CGFloat newY = 0.0;
    [self.dataArray enumerateObjectsUsingBlock:^(NSMutableDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSArray *items = [obj allValues].firstObject;
        NSInteger num = ceilf(items.count / 4.0);
        newY = newY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
        [self.pageSelectYArray addObject:@[@(oldY), @(newY)]];
        oldY = oldY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
    }];
    
    [self setHeader];
    
}

然后在列表滚动的时候计算当前滚动到顶部头视图是哪一分组即可

#pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView == self.collectionViewFour) {
        // 这一句用于设置 pageSuperViewSuperView 顶端悬停效果
        self.pageSuperViewSuperView.lgf_y = MAX(-18.0, (scrollView.contentOffset.y - 8.0));
        if (!self.isSelectTitle){
            [self.pageSelectYArray enumerateObjectsUsingBlock:^(NSArray *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                CGFloat realY = scrollView.contentOffset.y;
                if (realY > [obj.firstObject floatValue] && realY < [obj.lastObject floatValue]) {
                    [self.fptView startLineMoveAnimFromValue:idx];
                }
            }];
        }
    }
}
  • 导航滚动的菜单栏需要开出这么几个接口
  • 滑动底部下划线的接口
  • 点击Item 的回调代理
#import 



/**
 滑动导航栏点击时切换页面监听协议接口
 */
@protocol ScrollNavBarChangeListener 

@optional
-(void) onChangeListener:(NSInteger) index;

@end

/**
 滑动的导航条
 */
@interface ScrollNavBar : UIView

// 滑动导航栏点击时切换页面监听事件
@property(nonatomic,weak) id delegate;


@property(nonatomic,strong) UIView* bottomLine;

@property(nonatomic,strong) NSMutableArray* titleList;
@property(nonatomic,strong) NSMutableArray* btnList;

@property(nonatomic,assign) double itemWidth;

@property(nonatomic,strong) CABasicAnimation *moveAnimation;

@property(nonatomic,assign) NSInteger nCurIndex;

@property(nonatomic,strong) UIScrollView* segmentScroll;

@property(nonatomic,assign) CGPoint finalPos;    //点击滑动条的标签,最终的位置


/**
 初始化标题
 
 @param titles 标题列表
 */
-(void)iniTitles:(NSMutableArray*) titles;

/**
 添加中间滚动视图到列表中,这个视图的个数应该与标题一一对应,超过的部分直接忽略

 @param views 中间视图容器
 */
-(void)initSegmentView:(NSMutableArray*) views;


/**
 更新标题栏状态
 
 @param idx 更新的索引
 */
-(void)updateTitleBtnStatus:(NSInteger) idx;

/**
     启动底部下划线的动画
 */
-(void)startLineMoveAnimFromValue:(NSInteger) idx;

@end

  • 源文件为:
#import "ScrollNavBar.h"

#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
#define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height)

@implementation ScrollNavBar

-(instancetype) initWithFrame:(CGRect)frame{
    
    if(self = [super initWithFrame:frame]){
        
        [self initAttr];
    }
    return self;
    
}

-(void) initAttr{
    
    self.nCurIndex = 0;
    self.btnList = [NSMutableArray array];
}

-(void)iniTitles:(NSMutableArray*) titles{
    
    self.titleList = titles;
    
    self.itemWidth = SCREEN_WIDTH / [self.titleList count];
    
    [self.titleList enumerateObjectsUsingBlock:^(NSString* title, NSUInteger idx, BOOL * _Nonnull stop) {
        UIButton* button = [[UIButton alloc] initWithFrame:CGRectMake(idx * self.itemWidth, 0, self.itemWidth, 40.0)];
        button.backgroundColor = [UIColor whiteColor];
        button.tag = idx;
        [button addTarget:self action:@selector(onClickListener:) forControlEvents:UIControlEventTouchUpInside];
        [button setTitle:title forState:UIControlStateNormal];
        button.titleLabel.font = [UIFont systemFontOfSize:12];
        [button setTitleColor:[UIColor colorWithRed:119.0 / 255.0 green:119.0 / 255.0 blue:119.0 / 255.0 alpha:1.0] forState:UIControlStateNormal];
        [self addSubview:button];
        [self.btnList  addObject:button];
        
    }];
    
    //创建底部线条
    UIView* lineView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 40.0, SCREEN_WIDTH, 0.5f)];
    lineView.backgroundColor = [UIColor colorWithRed:232.0 / 255.0 green:232.0 / 255.0 blue:233.0 / 255.0 alpha:1.0];
    [self addSubview:lineView];
    
    self.bottomLine = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 40.0, self.itemWidth, 1.5f)];
    self.bottomLine.backgroundColor = [UIColor colorWithRed:118.0f / 255.0 green:198.f / 255.0 blue:192.0f / 255.0 alpha:1.0];
    [self addSubview:self.bottomLine];


}


//本接口主要,创建中间滚动视图
-(void)initSegmentView:(NSMutableArray*) views{


}

-(void)onClickListener:(UIButton*) button{
    
    if(self.delegate != nil){
        [self.delegate onChangeListener:button.tag];
    }
    
    [self.segmentScroll scrollRectToVisible:CGRectMake(SCREEN_WIDTH * button.tag, 42.5, SCREEN_WIDTH, SCREEN_HEIGHT) animated:NO];
    
    
    NSValue* fromValue = [NSValue valueWithCGPoint:CGPointMake(self.nCurIndex * self.itemWidth + 0.5 * self.itemWidth,40.0)];
    NSValue* toValue = [NSValue valueWithCGPoint:CGPointMake(button.tag * self.itemWidth + 0.5 * self.itemWidth, 40.0)];
    [self startLineMoveAnimFromValue:fromValue toValue:toValue duration:0.3];

    self.nCurIndex = button.tag;
    
    [self updateTitleBtnStatus:button.tag];
    
    self.finalPos = CGPointMake(button.tag * self.itemWidth + 0.5 * self.itemWidth, 40.0);
}

-(void) updateTitleBtnStatus:(NSInteger)idx{

    [self.btnList enumerateObjectsUsingBlock:^(UIButton* button, NSUInteger index, BOOL * _Nonnull stop) {
       
        if(idx == index){
            [button setTitleColor:[UIColor colorWithRed:118.0f / 255.0 green:198.f / 255.0 blue:192.0f / 255.0 alpha:1.0] forState:UIControlStateNormal];
            
        }else{
            [button setTitleColor:[UIColor colorWithRed:119.0 / 255.0 green:119.0 / 255.0 blue:119.0 / 255.0 alpha:1.0] forState:UIControlStateNormal];
        }

    }];
}

-(void)startLineMoveAnimFromValue:(NSInteger) idx{
    
     NSValue* fromValue = [NSValue valueWithCGPoint:CGPointMake(self.nCurIndex * self.itemWidth + 0.5 * self.itemWidth,40.0)];
       NSValue* toValue = [NSValue valueWithCGPoint:CGPointMake(idx * self.itemWidth + 0.5 * self.itemWidth, 40.0)];
       [self startLineMoveAnimFromValue:fromValue toValue:toValue duration:0.3];

       self.nCurIndex = idx;
       
       [self updateTitleBtnStatus:idx];
       
       self.finalPos = CGPointMake(idx * self.itemWidth + 0.5 * self.itemWidth, 40.0);
}

//private method---线条移动启动动画
-(void) startLineMoveAnimFromValue:(id) fromValue toValue:(id) toValue  duration:(CFTimeInterval) time{
    
    self.moveAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    self.moveAnimation.fromValue = fromValue;
    self.moveAnimation.toValue = toValue;
    self.moveAnimation.delegate = self;
    self.moveAnimation.removedOnCompletion = NO;
    self.moveAnimation.fillMode = kCAFillModeForwards;
    self.moveAnimation.duration = time;
    
    [self.bottomLine.layer removeAllAnimations];
    [self.bottomLine.layer addAnimation:self.moveAnimation forKey:@"onStart"];
   

}

-(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    
       if([self.bottomLine.layer.animationKeys.lastObject isEqualToString:@"onStart"]){
        
           CGRect frame = self.bottomLine.frame;
           frame.origin.x = self.finalPos.x;
           self.bottomLine.frame = frame;
           
    }
}


-(void)dealloc{
   

}



-(void) updateLabelStatus:(NSNotification *)msg{
    
    
    NSDictionary* tmpInfo = [msg object];
    UIScrollView* scroll = (UIScrollView*)[tmpInfo objectForKey:@"scroll"];
    
    
    [self.bottomLine.layer removeAllAnimations];
    CGRect frame = self.bottomLine.frame;
    frame.origin.x = scroll.contentOffset.x / SCREEN_WIDTH * self.itemWidth;
    self.bottomLine.frame = frame;
    [self.bottomLine layoutIfNeeded];
    
    self.nCurIndex = scroll.contentOffset.x / SCREEN_WIDTH;
    [self updateTitleBtnStatus:self.nCurIndex];
}

@end
   
  • 测试用的vc为

#import 

NS_ASSUME_NONNULL_BEGIN

@interface StickScrollVC : UIViewController


@property (copy, nonatomic) NSString *type;
@property (copy, nonatomic) NSArray *titles;


@end

NS_ASSUME_NONNULL_END
  • 源文件为:
#import "StickScrollVC.h"
#import "CollectionViewLayout.h"
#import "ItemView.h"
#import "MicroTool.h"
#import "UIScrollView+LXBScrollView.h"
#import "ScrollNavBar.h"
#import "UIView+LXB.h"

static NSString *ItemIdentifier = @"ItemIdentifier";
static NSString *leaveDetailsHeadID = @"leaveDetailsHeadID";
static NSString *leaveDetailsFooterID = @"leaveDetailsFooterID";

@interface StickScrollVC (){
    NSMutableArray *listArray;
}

@property (assign, nonatomic) CGFloat headerHeight;
// 我的应用数据源数组
@property (strong, nonatomic) NSMutableArray *myDataArray;
// 最近使用数据源数组
@property (strong, nonatomic) NSMutableArray *latelyDataArray;
// 底下数据源数组
@property (strong, nonatomic) NSMutableArray *dataArray;
// 记录用于滚动选择判断的 contentOffset.y
@property (strong, nonatomic) NSMutableArray *pageSelectYArray;


@property (strong, nonatomic)  UIView *pageSuperView;
@property (strong, nonatomic)  UIView *pageSuperViewSuperView;
@property (strong, nonatomic)  UIView *headerView;

@property (strong, nonatomic)  UICollectionView *collectionViewFour;
@property (strong, nonatomic) ScrollNavBar *fptView;
@property (assign, nonatomic) BOOL isSelectTitle;






@end

@implementation StickScrollVC

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
   
    self.view.backgroundColor = BgColor;
    listArray = [NSMutableArray arrayWithObjects:@"便民生活",@"财富管理",@"资金往来",@"购物娱乐",@"教育公益",@"第三方服务", nil];
    [self createCollectionView];
    
    
    
    UIButton* back = [[UIButton alloc] initWithFrame:CGRectMake(30, 20, 130, 30)];
    back.backgroundColor = [UIColor grayColor];
    back.tag = 10;
    [back setTitle:@"back" forState:UIControlStateNormal];
    [back addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:back];
    
    
    [self initData];

}

-(void) initData{

    self.dataArray = [[NSMutableArray alloc] init];
    self.dataArray = @[@{@"便民生活" : @[@"充值中心", @"信用卡还款", @"生活缴费", @"城市服务", @"我的快递", @"医疗健康", @"记账本", @"发票管家", @"车主服务", @"交通出行", @"体育服务", @"安全备忘"]},
    @{@"财富管理" : @[@"余额宝", @"花呗", @"芝麻信用", @"借呗", @"蚂蚁保险", @"汇率换算"]},
    @{@"资金往来" : @[@"转账", @"红包", @"AA收款", @"亲情号", @"商家服务"]},
    @{@"购物娱乐" : @[@"出境", @"彩票", @"奖励金"]},
    @{@"教育公益" : @[@"校园生活", @"蚂蚁森林", @"蚂蚁庄园", @"中小学", @"运动", @"亲子账户"]},
    @{@"第三方服务" : @[@"淘票票电影", @"滴滴出行", @"饿了么外卖", @"天猫", @"淘宝", @"火车票机票", @"飞猪酒店", @"蚂上租租房", @"高德打车", @"哈啰出行"]}].mutableCopy;
    
    
    // 计算需要选中的 contentOffset.y 保存
    self.pageSelectYArray = [[NSMutableArray alloc] init];
    __block CGFloat oldY = 0.0;
    __block CGFloat newY = 0.0;
    [self.dataArray enumerateObjectsUsingBlock:^(NSMutableDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSArray *items = [obj allValues].firstObject;
        NSInteger num = ceilf(items.count / 4.0);
        newY = newY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
        [self.pageSelectYArray addObject:@[@(oldY), @(newY)]];
        oldY = oldY + (idx == 0 ? 10.0 : 50.0) + num * 70.0;
    }];
    
    [self setHeader];
    
}

#pragma mark - 头部视图配置
- (void)setHeader {
    

    // 设置头高度(可动态适配,我这边只用于示例代码因此是写死的高度)
    self.headerHeight = 454.0 + 10.0;
    // 添加头
    self.headerView = [[UIView alloc] init];
    self.headerView.frame = CGRectMake(0.0, -self.headerHeight, lgf_ScreenWidth, self.headerHeight);
    [self.collectionViewFour addSubview:self.headerView];
    [self.collectionViewFour sendSubviewToBack:self.headerView];
    // 添加 LGFFreePTView 父控件
    self.pageSuperViewSuperView = [[UIView alloc] init];
    self.pageSuperViewSuperView.frame = CGRectMake(0.0, -10.0, lgf_ScreenWidth, 48.0);
    self.pageSuperViewSuperView.backgroundColor = [UIColor greenColor];
    [self.collectionViewFour addSubview:self.pageSuperViewSuperView];
    
    [self setCVContentInset:0.0];
    
    self.fptView = [[ScrollNavBar alloc] initWithFrame:CGRectMake(0, 10, lgf_ScreenWidth, 83)];
    self.fptView.delegate = self;
    [self.fptView iniTitles:listArray];
    [self.pageSuperViewSuperView addSubview:self.fptView];

}

- (void)setCVContentInset:(CGFloat)top {
    CGFloat lastY = lgf_ScreenHeight - IPhoneX_NAVIGATION_BAR_HEIGHT - 40 - ([[[self.pageSelectYArray lastObject] lastObject] floatValue] - [[[self.pageSelectYArray lastObject] firstObject] floatValue] + top);
    [self.collectionViewFour setContentInset:UIEdgeInsetsMake(self.headerHeight, 0.0, lastY, 0.0)];
    [self.collectionViewFour lgf_ScrollToTopAnimated:NO];
}

-(void)onClick:(UIButton*) view{
    [self.navigationController popViewControllerAnimated:false];
}

-(void)createCollectionView{

    CGRect size = CGRectMake(0, 50, self.view.frame.size.width, self.view.frame.size.height - 64);
    CollectionViewLayout *mcvl=[[CollectionViewLayout alloc] init];
    self.collectionViewFour = [[UICollectionView alloc] initWithFrame:size collectionViewLayout:mcvl];

    [self.collectionViewFour registerClass:[ItemView class] forCellWithReuseIdentifier:ItemIdentifier];

    self.collectionViewFour.showsHorizontalScrollIndicator=NO;
    self.collectionViewFour.showsVerticalScrollIndicator=NO;
    self.collectionViewFour.backgroundColor=[UIColor whiteColor];
    self.collectionViewFour.delegate = self;
    self.collectionViewFour.dataSource = self;
    //一定要注册headview
    [self.collectionViewFour registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:leaveDetailsHeadID];
    //一定要注册footerview
    [self.collectionViewFour registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:leaveDetailsFooterID];
    [self.view addSubview:self.collectionViewFour];

}


//有多少个sections
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return listArray.count;

}
//每个section 中有多少个items
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{

    NSArray *items = [self.dataArray[section] allValues].firstObject;
    return items.count;

}
//section X item X位置处应该显示什么内容
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    ItemView *cell=nil;
    if (cell==nil) {
        cell = [collectionView dequeueReusableCellWithReuseIdentifier:ItemIdentifier forIndexPath:indexPath];

    }
    cell.contentView.backgroundColor = [UIColor whiteColor];
    if (indexPath.row/2 == 0) {
        cell.imgView.backgroundColor = [UIColor redColor];
    }else{
        cell.imgView.backgroundColor = [UIColor greenColor];
    }
    cell.nameLabel.text = [self.dataArray[indexPath.section] allValues].firstObject[indexPath.item];
    return cell;
}
//cell的header与footer的显示内容
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{

    if (kind == UICollectionElementKindSectionHeader){
        UICollectionReusableView *reusableHeaderView = nil;
        if (reusableHeaderView==nil) {
            reusableHeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
                                                                   withReuseIdentifier:leaveDetailsHeadID
                                                                         forIndexPath:indexPath];
            reusableHeaderView.backgroundColor = [UIColor whiteColor];
            UILabel *label = (UILabel *)[reusableHeaderView viewWithTag:100];
            if (!label) {
                label = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, self.view.frame.size.width, 40)];
                label.tag = 100;
                [reusableHeaderView addSubview:label];
            }
            label.text = listArray[indexPath.section];

        }
        if (indexPath.section == 0) {
            reusableHeaderView.lgf_height = 0.0;
        } else {
            reusableHeaderView.lgf_height = 40.0;
        }
        return reusableHeaderView;

    }else if (kind == UICollectionElementKindSectionFooter){

        UICollectionReusableView *reusableFooterView = nil;
        if (reusableFooterView == nil) {

            reusableFooterView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter  withReuseIdentifier:leaveDetailsFooterID forIndexPath:indexPath];
            reusableFooterView.backgroundColor = BgColor;
        }

        return reusableFooterView;
    }
    return nil;
    
}


#pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView == self.collectionViewFour) {
        // 这一句用于设置 pageSuperViewSuperView 顶端悬停效果
        self.pageSuperViewSuperView.lgf_y = MAX(-18.0, (scrollView.contentOffset.y - 8.0));
        if (!self.isSelectTitle){
            [self.pageSelectYArray enumerateObjectsUsingBlock:^(NSArray *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                CGFloat realY = scrollView.contentOffset.y;
                if (realY > [obj.firstObject floatValue] && realY < [obj.lastObject floatValue]) {
                    [self.fptView startLineMoveAnimFromValue:idx];
                }
            }];
        }
    }
}

//滑动条的滚动接口
- (void)onChangeListener:(NSInteger)index{
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(configIsSelectTitle) object:nil];
    self.isSelectTitle = YES;
     [self.collectionViewFour setContentOffset:CGPointMake(0, [[self.pageSelectYArray[index] firstObject] floatValue] ) animated:YES];
    [self performSelector:@selector(configIsSelectTitle) withObject:nil afterDelay:0.3];

}

- (void)configIsSelectTitle {
    self.isSelectTitle = NO;
}

@end

你可能感兴趣的:(IOS-使用UICollectionView+UIScrollView实现悬停、导航条跟随列表滚动滑动)