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的座標時我們可以這樣做,即記住每一組的開始y座標,然後通過ScrollView的contentOffset y 座標來判斷這個值落在哪一個區間內,再取出這個值在之前記住這個y座標的索引

-(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 <UIKit/UIKit.h>



/**
 滑動導航欄點擊時切換頁面監聽協議接口
 */
@protocol ScrollNavBarChangeListener <NSObject>

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

@end

/**
 滑動的導航條
 */
@interface ScrollNavBar : UIView<CAAnimationDelegate>

// 滑動導航欄點擊時切換頁面監聽事件
@property(nonatomic,weak) id<ScrollNavBarChangeListener> 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 <UIKit/UIKit.h>

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 ()<UICollectionViewDelegate,UICollectionViewDataSource,ScrollNavBarChangeListener>{
    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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章