您的当前位置:首页正文

悬停视图-SFHoverTableView

来源:要发发知识网

项目中需要使用悬停的效果,临时找过三方就写了,后来自己尝试模仿大佬编写的效果,如果有错误请指正

QQ20190815-104001-HD.gif
  • 其中的部分代码如下:
//
//  SFAqArtController.m
//  SFHoverTableView
//
//  Created by 随风流年 on 2019/8/12.
//  Copyright © 2019 随风流年. All rights reserved.
//

#import "SFAqArtController.h"
#import "SFHeaderView.h"
#import "SPPageMenu.h"

#import "SFArtFirstController.h"
#import "SFArtSecondController.h"
#import "SFArtThirdController.h"
#import "SFArtFourController.h"

@interface SFAqArtController ()<SPPageMenuDelegate,UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) SFHeaderView *headerView;
@property (nonatomic, strong) SPPageMenu   *pageMenu;
@property (nonatomic, assign) CGFloat lastPageMenuY;
@property (nonatomic, assign) CGFloat lastPoint;

@end

@implementation SFAqArtController
- (void)leftItmeClick:(UIBarButtonItem *)item{
    [self.navigationController popViewControllerAnimated:YES];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.navigationController.navigationBar.translucent = NO;
    self.navigationItem.title = @"爱奇艺";
    UIBarButtonItem *leftItem = [[UIBarButtonItem alloc]initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(leftItmeClick:)];
    self.navigationItem.leftBarButtonItem = leftItem;
    self.lastPageMenuY = HeaderViewH;
    // 添加一个全屏的scrollView
    [self.view addSubview:self.scrollView];
    [self.view addSubview:self.pageMenu];
    SFArtFirstController *firstVC = [[SFArtFirstController alloc]init];
    [self addChildViewController:firstVC];
    firstVC.headerView = self.headerView;
    
    [self addChildViewController:[[SFArtSecondController alloc]init]];
    [self addChildViewController:[[SFArtThirdController alloc]init]];
    [self addChildViewController:[[SFArtFourController alloc]init]];
    
    [self.scrollView addSubview:self.childViewControllers[0].view];
    //监听子控制器中scrollView正在滑动所发出的通知
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(subScrollViewDidScroll:) name:ArtChildScrollViewDidScrollNSNotification object:nil];
}
#pragma mark -- ArtChildScrollViewDidScrollNSNotification --
- (void)subScrollViewDidScroll:(NSNotification *)noti{
   
    // 取出当前正在滑动的tableView
    UIScrollView *scrollingScrollView = noti.userInfo[@"scrollingScrollView"];
    CGFloat offsetDifference = [noti.userInfo[@"offsetDifference"] floatValue];
    
    CGFloat distanceY;
    
    SFAqArtBaseController *baseVc = self.childViewControllers[self.pageMenu.selectedItemIndex];
    
    // 取出的scrollingScrollView并非是唯一的,当有多个子控制器上的scrollView同时滑动时都会发出通知来到这个方法,所以要过滤
    if (scrollingScrollView == baseVc.scrollView && baseVc.isFirstViewLoaded == NO) {
        // 让分页菜单跟随scrollView滑动
        CGRect pageMenuFrame = self.pageMenu.frame;
        
        if (pageMenuFrame.origin.y >= 0) {
            // 往上滑
            if (offsetDifference > 0) {
                NSLog(@"往上上上滑---:%.1f===%.1f"
                      ,scrollingScrollView.contentOffset.y
                      ,self.pageMenu.frame.origin.y);

                if (((scrollingScrollView.contentOffset.y+self.pageMenu.frame.origin.y)>=HeaderViewH) || scrollingScrollView.contentOffset.y < 0) {
                    // 分页菜单的y值等于当前正在滑动且显示在屏幕范围内的的scrollView的contentOffset.y的改变量(这是最难的点)
                    pageMenuFrame.origin.y += -offsetDifference;
                    if (pageMenuFrame.origin.y <= 0) {
                        pageMenuFrame.origin.y = 0;
                    }
                    
                }
            } else { // 往下滑
                NSLog(@"往下滑---:%.1f===%.1f"
                      ,scrollingScrollView.contentOffset.y
                      ,self.pageMenu.frame.origin.y);
                
                if ((scrollingScrollView.contentOffset.y+self.pageMenu.frame.origin.y)-0<HeaderViewH) {
                    pageMenuFrame.origin.y = -scrollingScrollView.contentOffset.y+HeaderViewH+0;
                }
            }
        }
        self.pageMenu.frame = pageMenuFrame;
        
        // 配置头视图的y值
        [self adjustHeaderY];
        
        // 记录分页菜单的y值改变量
        NSLog(@"分页菜单改变之前:%f===:%f===%.1f",pageMenuFrame.origin.y,self.lastPageMenuY,self.pageMenu.frame.origin.y);
        distanceY = pageMenuFrame.origin.y - self.lastPageMenuY;
        self.lastPageMenuY = self.pageMenu.frame.origin.y;
        NSLog(@"分页菜单改变之后之后之后:%f",self.lastPageMenuY);

        // 让其余控制器的scrollView跟随当前正在滑动的scrollView滑动
        [self followScrollingScrollView:scrollingScrollView distanceY:distanceY];
        
    }
    baseVc.isFirstViewLoaded = NO;
}
//所有子控制器上特定的scrollView同时联动
- (void)followScrollingScrollView:(UIScrollView *)scrollingScrollView distanceY:(CGFloat)distanceY{
    SFAqArtBaseController *baseVC = nil;
    for (int i = 0; i<self.childViewControllers.count; i++) {
        baseVC = self.childViewControllers[i];
        if (baseVC.scrollView == scrollingScrollView) {
            continue;
        }else{
            //除去当前正在滑动的scrollView外,其余scrollView的改变量等于分页菜单的改变量
            CGPoint contentOffSet = baseVC.scrollView.contentOffset;
            contentOffSet.y += - distanceY;
            baseVC.scrollView.contentOffset = contentOffSet;
        }
    }
}
#pragma mark - SPPageMenuDelegate -
- (void)pageMenu:(SPPageMenu *)pageMenu itemSelectedFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex{
    if (!self.childViewControllers.count) {return;}
    //如果上一次点击的button下标与当前点击的button的下标之差大于等于2,说明跨界面移动了,此时不动画
    if (labs(toIndex - fromIndex) >= 2) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.scrollView setContentOffset:CGPointMake(self->_scrollView.frame.size.width * toIndex, 0) animated:NO];
        });
    }else{
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.scrollView setContentOffset:CGPointMake(self->_scrollView.frame.size.width * toIndex, 0) animated:YES];
        });
    }
    SFAqArtBaseController *targetViewController = self.childViewControllers[toIndex];
    if (self.scrollView.dragging || self.scrollView.decelerating || self.scrollView.contentOffset.x / SCREEN_WIDTH == self.pageMenu.selectedItemIndex) {
        // 1. 切换headerView的父视图 2.将headerView的x、y值都归0
        targetViewController.headerView = self.headerView;
    }
    if ([targetViewController isViewLoaded]) {
        return;//如果已经加载过,就不再加载
    }
    //是第一次加载控制器的View,这个属性是为了防止下面的偏移量的改变导致scrollViewDidScroll
    targetViewController.isFirstViewLoaded = YES;
    targetViewController.view.frame = CGRectMake(SCREEN_WIDTH * toIndex, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    UIScrollView *s = targetViewController.scrollView;
    CGPoint contentOffset = s.contentOffset;
    contentOffset.y = - self.pageMenu.frame.origin.y + HeaderViewH;
    if (contentOffset.y >= HeaderViewH) {
        contentOffset.y = HeaderViewH;
    }
    s.contentOffset = contentOffset;
    [self.scrollView addSubview:targetViewController.view];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    if (scrollView == self.scrollView) {
        SFAqArtBaseController *baseVC = self.childViewControllers[self.pageMenu.selectedItemIndex];
        if ([baseVC isViewLoaded]) {
            [self.scrollView bringSubviewToFront:baseVC.view];
        }
        //如果是手指滑动
        if (scrollView.dragging || scrollView.decelerating) {
            //横向切换tableView时,头部不要跟随tableView偏移
            CGRect headerFrame = self.headerView.frame;
            headerFrame.origin.x = scrollView.contentOffset.x - SCREEN_WIDTH * self.pageMenu.selectedItemIndex;
            self.headerView.frame = headerFrame;
        }else{
            //如果不是手指滑动,通过点击pageMenu上的item滑动。这里先将headerView加到self.view上,目的是过度一下,如果不过度,点击相邻item,改变scrollView的偏移量使用了动画参数,这个动画会导致切换headerview有一个闪跳现象
            CGRect rectInView = [self.headerView convertRect:self.headerView.bounds toView:self.view];
            rectInView.origin.x = 0;
            [self adjustHeaderY];
            [self.view addSubview:self.headerView];
            self.headerView.frame = rectInView;
            
            if (scrollView.contentOffset.x / SCREEN_WIDTH == self.pageMenu.selectedItemIndex)  {
                [self.headerView removeFromSuperview];
                baseVC.headerView = self.headerView;
                [self adjustHeaderY];
            }
        }
        //如果scrollView的内容很少,在屏幕内,自动回落
        if (scrollView.contentOffset.x / SCREEN_WIDTH == self.pageMenu.selectedItemIndex) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                if (baseVC.scrollView.contentSize.height < SCREEN_HEIGHT && [baseVC isViewLoaded]) {
                    [baseVC.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
                }
            });
        }
    }
}
-(void)adjustHeaderY{
    //取出当前子控制器
    SFAqArtBaseController *baseVC = self.childViewControllers[self.pageMenu.selectedItemIndex];
    CGRect headerFrame = self.headerView.frame;
    //将pageMenu的frame切换到当前正在滑动的scrollView上
    CGRect pageMenuFrameInScrollView = [self.pageMenu convertRect:self.pageMenu.bounds toView:baseVC.scrollView];
    NSLog(@"pageMenuY:%.1f",pageMenuFrameInScrollView.origin.y);
    //每个tableView的头视图的y值都等于pageMenu的y值减去头部高度,这是为了保证头部的底部永远跟pageMenu的顶部紧贴
    headerFrame.origin.y = pageMenuFrameInScrollView.origin.y - HeaderViewH;
    self.headerView.frame = headerFrame;
    
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    if (scrollView == self.scrollView) {
        [self adjustHeaderY];
    }
    SFAqArtBaseController *baseVc = self.childViewControllers[self.pageMenu.selectedItemIndex];
    // 这个方法是因为手指拖拽了scrollView松开手指,结束减速时调用,如果是因为代码改变scrollView偏移量不会来到这个方法
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (baseVc.scrollView.contentSize.height < SCREEN_HEIGHT && [baseVc isViewLoaded]) {
            [baseVc.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
        }
    });
}
#pragma mark --- lazy ---
- (UIScrollView *)scrollView{
    if (!_scrollView) {
        _scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - BottomMargin)];;
        _scrollView.delegate = self;
        _scrollView.pagingEnabled = YES;
        _scrollView.showsVerticalScrollIndicator = NO;
        _scrollView.showsHorizontalScrollIndicator = NO;
        _scrollView.contentSize = CGSizeMake(SCREEN_WIDTH * 4, 0);
    }
    return  _scrollView;
}
- (SFHeaderView *)headerView{
    if (!_headerView) {
        _headerView = [[SFHeaderView alloc]init];
        _headerView.frame = CGRectMake(0, 0, SCREEN_WIDTH, HeaderViewH);
        _headerView.backgroundColor = [UIColor greenColor];
    }
    return _headerView;
}
- (SPPageMenu *)pageMenu{
    if (!_pageMenu) {
        _pageMenu = [SPPageMenu pageMenuWithFrame:CGRectMake(0, CGRectGetMaxY(self.headerView.frame), SCREEN_WIDTH, PageMenuH) trackerStyle:SPPageMenuTrackerStyleLineAttachment];
        [_pageMenu setItems:@[@"第一页",@"第二页",@"第三页",@"第四页"] selectedItemIndex:0];
        _pageMenu.delegate = self;
        _pageMenu.itemTitleFont = [UIFont systemFontOfSize:16];
        _pageMenu.selectedItemTitleColor = [UIColor blackColor];
        _pageMenu.unSelectedItemTitleColor = [UIColor colorWithWhite:0 alpha:0.6];
        _pageMenu.tracker.backgroundColor = [UIColor orangeColor];
        _pageMenu.permutationWay = SPPageMenuPermutationWayNotScrollEqualWidths;
        _pageMenu.bridgeScrollView = self.scrollView;
    }
    return _pageMenu;
}

@end








/*
 
 UIView *redView = [[UIView alloc]initWithFrame:CGRectMake(200, 200, 100, 100)];
 redView.backgroundColor = [UIColor redColor];
 [self.view addSubview:redView];
 
 UIView *greenView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 50, 50)];
 greenView.backgroundColor = [UIColor greenColor];
 [self.view addSubview:greenView];
 
 UIView *blueView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 40, 40)];
 blueView.backgroundColor = [UIColor blueColor];
 [redView addSubview:blueView];
 
 CGRect rec = [self.view convertRect:blueView.frame fromView:redView];
 NSLog(@"rec:%@",NSStringFromCGRect(rec));//rec:{{200, 200}, {40, 40}}
 // redview中的blueview相对于self.view的位置 ✔️
 
 CGRect rec1 = [self.view convertRect:redView.frame fromView:blueView];
 NSLog(@"rec1:%@",NSStringFromCGRect(rec1));//rec1:{{400, 400}, {100, 100}} ✅
 // 蓝色view中的定义一个相对于蓝色view的frame的view,这个view相对于self.view的位置
 
 CGRect rect =  [redView convertRect:greenView.frame toView:self.view];
 NSLog(@"rect:%@",NSStringFromCGRect(rect));//rect:{{300, 300}, {50, 50}} ✅
 /// 在redView中,定义一个相对于redView(100,100),大小为(50,50)的view,这个view相对于self.view的位置
 
 
 /// 需要注意的是toview可以传nil
 CGRect rect1 =  [redView convertRect:greenView.frame toView:nil];
 NSLog(@"rect1:%@",NSStringFromCGRect(rect1));//rect1:{{100, 100}, {50, 50}} ✅
 /// 上面的代码的意思是:在redView中,定义一个目标区域,该区域相对于window的位置(nil代表的是self.view.window)
 
 CGRect rect2 =  [redView convertRect:greenView.frame toView:blueView];
 NSLog(@"rect2:%@",NSStringFromCGRect(rect2));//rect2:{{100, 100}, {50, 50}}
 //在redView中,定义一个相对于redview(100,100),大小为(50,50)的view,这个view相对于blueView的位置
 
 CGRect rect3 =  [redView convertRect:CGRectMake(80, 80, 80, 80) toView:blueView];
 NSLog(@"rect3:%@",NSStringFromCGRect(rect3));//rect3:{{80, 80}, {80, 80}}
 //在redView中,定义一个相对于redview(80,80),大小为(80,80)的view,这个view相对于blueView的位置
 
 CGRect rect4 =  [redView convertRect:CGRectMake(80, 80, 80, 80) toView:greenView];
 NSLog(@"rect4:%@",NSStringFromCGRect(rect4));//rect4:{{180, 180}, {80, 80}}
 //在redView中,定义一个相对于redview(80,80),大小为(80,80)的view,这个view相对于greenView的位置
 
 CGRect rect5 =  [redView convertRect:CGRectMake(20, 20, 80, 80) toView:greenView];
 NSLog(@"rect5:%@",NSStringFromCGRect(rect5));//rect5:{{120, 120}, {80, 80}}
 //在redView中,定义一个相对于redview(80,80),大小为(80,80)的view,这个view相对于greenView的位置
 
 CGRect newRect = [redView convertRect:greenView.bounds toView:nil];
 NSLog(@"newRect:%@",NSStringFromCGRect(newRect));//newRect:{{0, 0}, {50, 50}}
 
 CGRect newRect0 = [redView convertRect:greenView.frame toView:nil];
 NSLog(@"newRect0:%@",NSStringFromCGRect(newRect0));//newRect0:{{100, 100}, {50, 50}}
 
 CGRect newRect1 = [redView convertRect:redView.bounds toView:nil];
 NSLog(@"newRect1:%@",NSStringFromCGRect(newRect1));//newRect1:{{0, 0}, {100, 100}}
 
 CGRect newRect2 = [redView convertRect:redView.frame toView:nil];
 NSLog(@"newRect2:%@",NSStringFromCGRect(newRect2));//newRect2:{{200, 200}, {100, 100}}
 

 2019-08-12 14:54:04.798060+0800 SFHoverTableView[10946:186139] rec:{{200, 200}, {40, 40}}
 2019-08-12 14:54:04.798231+0800 SFHoverTableView[10946:186139] rec1:{{400, 400}, {100, 100}}
 2019-08-12 14:54:04.798333+0800 SFHoverTableView[10946:186139] rect:{{300, 300}, {50, 50}}
 2019-08-12 14:54:04.798420+0800 SFHoverTableView[10946:186139] rect1:{{100, 100}, {50, 50}}
 2019-08-12 14:54:04.798508+0800 SFHoverTableView[10946:186139] rect2:{{100, 100}, {50, 50}}
 2019-08-12 14:54:04.798594+0800 SFHoverTableView[10946:186139] rect3:{{80, 80}, {80, 80}}
 2019-08-12 14:54:04.798678+0800 SFHoverTableView[10946:186139] rect4:{{180, 180}, {80, 80}}
 2019-08-12 14:54:04.798758+0800 SFHoverTableView[10946:186139] rect5:{{120, 120}, {80, 80}}
 2019-08-12 14:54:04.798832+0800 SFHoverTableView[10946:186139] newRect:{{0, 0}, {50, 50}}
 2019-08-12 14:54:04.798913+0800 SFHoverTableView[10946:186139] newRect0:{{100, 100}, {50, 50}}
 2019-08-12 14:54:04.799096+0800 SFHoverTableView[10946:186139] newRect1:{{0, 0}, {100, 100}}
 2019-08-12 14:54:04.799377+0800 SFHoverTableView[10946:186139] newRect2:{{200, 200}, {100, 100}}
 


//总结:
//toView就是从左往右开始读代码,也是从左往右理解意思
//fromView就是从右往左开始读代码,也是从右往左理解意思

 */