美文网首页求职iOS开发iOS进阶
iOS开发实战 - 完美解决UIScrollView嵌套滑动手势

iOS开发实战 - 完美解决UIScrollView嵌套滑动手势

作者: ArchLL | 来源:发表于2018-05-03 19:29 被阅读3051次

    1.本文介绍如何通过改变内外层scrollView的contentOffset来达到子列表页吸顶等自定义悬浮;本文看起来有点长,但是相对其他方法确实是比较简单的,如果恰巧要做这个功能,希望你认真看一看,或许真的可以帮到你;

    2.最近我抽出一些时间整理了一个 demo,与下方展示的代码不同的是:该Demo中的分页部分的子视图使用的是controller,适合更复杂的分页,耦合度更低,如有疑问或者建议可在下方评论;

    3.之前也写过类似 简书个人主页效果的demo ,如果想看关于头部放大、分页吸顶效果可移止本文末尾;

    4.已更新两次,完美解决各种冲突、卡顿问题,可在下方查看更新内容;

    先来看一下效果吧
    实战演示
    最新更新(2018/05/16):

    真正完美解决当前页子视图(scrollView/collectionView)左右滑动和外层tableView的上下滑动不能互斥的问题, 包括除了分页部分的其他子视图,虽然上一次更新的办法也可以移植当页的其他可左右滚动的视图,但是因为一旦你的手指一直往下滑动屏幕,一旦到达能左右滑动的区域,这个时候你的手指滑动方向一般都不是直上直下的,就会触发子视图左右滑动,从而导致外层的tableView停止滑动,如果这个页面可以左右滑动的子视图比较多,就会造成一卡一卡的感觉,体验非常不好;

    这次的更新不仅能解决左右滑动和上下滑动互斥问题,同时也优化了滑动体验,通过更改外层tableView的基类滑动手势的透穿方法,根据区域改变是否允许手势的透穿,该方法不仅能解决左右滑动和上下滑动互斥问题,同时也优化了滑动体验;

    解决当前页面除了分页之外的其他左右滚动视图左右滑动与外层tableView上下滑动不能互斥的问题,比如品牌列表
    本次更新代码比较少,但是很关键,可直接去看下方 1.1 BaseTableView 部分代码,有问题可评论或私信我;
    
    上一次更新(2018/05/11):

    解决下方分页部分左右滑动和外层tableView的上下滑动不能互斥的问题,因为一旦你的手指一直往下滑动屏幕,到达能左右滑动的区域,这个时候你的手指滑动方向一般都不是直上直下的,就会触发子视图左右滑动,从而让外层的tableView停止滑动,如果这个页面可以左右滑动的子视图比较多,就会造成一卡一卡的感觉,该方法可以参考一下,不推荐使用,因为没从根本上解决问题,当然如果主页就下方分页这部分可以左右滑动,倒也无所谓;

    解决分页部分未吸顶时,分页部分左右滑动不能与外层tableView上下滑动互斥的问题
    2018/05/11 更新主要代码
    
    //内层可左右滑动的子视图处理代码
    //增加分页视图左右滑动和外界tableView上下滑动互斥处理
    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
        [[NSNotificationCenter defaultCenter] postNotificationName:IsScrollHomeVC object:nil userInfo:@{@"canScroll":@"0"}];
    }
    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        [[NSNotificationCenter defaultCenter] postNotificationName:IsScrollHomeVC object:nil userInfo:@{@"canScroll":@"1"}];
    }
    
    //--------------------------------------------------------
    
    //外层tableView代码处理
    //注册允许首页外层tableView滚动通知-解决子视图左右滑动和外层tableView上下滑动的冲突问题
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptMsgOfSubView:) 
    name:IsScrollHomeVC object:nil];
    
    - (void)acceptMsgOfSubView:(NSNotification *)notification{
        NSDictionary *userInfo = notification.userInfo;
        NSString *canScroll = userInfo[@"canScroll"];
        if ([canScroll isEqualToString:@"1"]) {
            _mainTableView.scrollEnabled = YES;
        }else if([canScroll isEqualToString:@"0"]) {
            _mainTableView.scrollEnabled = NO;
        }
    }
    
    

    案例分析:

    1.外层tableView+中间层scrollView+内层collectionView;
    2.存在滑动冲突的是外层的tableView和内层的collectionView;
    3.首页子scrollView的左右滑动和外层tableView上下滑动的互斥处理;
    4.⚠️我这里的footerView是猜你喜欢以下部分,包含猜你喜欢(猜你喜欢不是通过viewForHeader获得的);

    核心代码:

    1.先看外层的tableView处理

    1.1 BaseTableView

    #import "XXHomeBaseTableView.h"
    #define pagingHeaderHeight 60
    
    @implementation XXHomeBaseTableView
    //注意:下方的tableView是继承自XXHomeBaseTableView,至关重要,目的是判断是否让外层tableView的手势透传到子视图
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
        //分页列表高度
        CGFloat naviBarHeight = NaviBarHeight;
        CGFloat tabBarHeight = TabBarHeight;
        CGFloat listHeight = SCREEN_HEIGHT - naviBarHeight - tabBarHeight - pagingHeaderHeight;
        CGPoint currentPoint = [gestureRecognizer locationInView:self];
        if (CGRectContainsPoint(CGRectMake(0, self.contentSize.height - listHeight, SCREEN_WIDTH, listHeight), currentPoint) ) {
            return YES;
        }
        return NO;
    }
    
    @end
    
    

    1.2 主控制器主要代码:

    #define sectionHeaderHeight 80.0
    #define pagingHeaderHeight 60.0
    
    @interface XXHomeViewController ()<UITableViewDelegate, UITableViewDataSource>
    @property (nonatomic, strong) XXHomeBaseTableView *mainTableView;
    @property (nonatomic, strong) XXHomeFooterView *footerView;
    @property (nonatomic, assign) BOOL canScroll;
    @property (nonatomic, assign) BOOL isTopIsCanNotMoveTabView;//到达顶部不能移动mainTableView
    @property (nonatomic, assign) BOOL isTopIsCanNotMoveTabViewPre;//到达顶部不能移动子控制器的tableView
    
    @end
    
    @implementation XXHomeViewController
    {
        CGFloat  _footerViewHeight; //底部猜你喜欢高度
        CGFloat  _listHeight;             //底部分页列表高度
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //底部分页列表高度
        _listHeight = SCREEN_HEIGHT - self.naviBarHeight - self.tabBarHeight - pagingHeaderHeight;
        //底部猜你喜欢高度
        _footerViewHeight = sectionHeaderHeight + ReSize_UIHeight(300.0) + pagingHeaderHeight + _listHeight;
        //注册允许首页外层tableView滚动通知-解决和footerView的上下滑动冲突问题
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptMsgOfFooterView:) name:@"leaveTop" object:nil];
        //注册允许首页外层tableView滚动通知-解决子视图左右滑动和外层tableView上下滑动的冲突问题
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptMsgOfSubView:) name:IsScrollHomeVC object:nil];
    }
    
    //接收通知
    - (void)acceptMsgOfFooterView:(NSNotification *)notification{
        NSDictionary *userInfo = notification.userInfo;
        NSString *canScroll = userInfo[@"canScroll"];
        if ([canScroll isEqualToString:@"1"]) {
            _canScroll = YES;
        }
    }
    
    //解决分页部分未达到临界点时,左右滑动和外层tableView上下滑动不能互斥的问题
    - (void)acceptMsgOfSubView:(NSNotification *)notification{
        NSDictionary *userInfo = notification.userInfo;
        NSString *canScroll = userInfo[@"canScroll"];
        if ([canScroll isEqualToString:@"1"]) {
            _mainTableView.scrollEnabled = YES;
        }else if([canScroll isEqualToString:@"0"]) {
            _mainTableView.scrollEnabled = NO;
        }
    }
    
    #pragma mark 处理联动
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        if (scrollView == _mainTableView) {
            //当前偏移量
            CGFloat yOffset  = scrollView.contentOffset.y;
            //临界点偏移量
            CGFloat tabyOffset = scrollView.contentSize.height  - _listHeight  - pagingHeaderHeight - self.naviBarHeight;
            
            //更改状态栏的字体颜色
            if (yOffset >= tableHeaderViewHeight) {
                if (yOffset <= (scrollView.contentSize.height  - _footerViewHeight + pagingHeaderHeight)) {
                    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
                }else{
                    [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
                }
            }else {
                [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
            }
            
            //滑动到一定高度后,更改底部按钮的显示
            if (yOffset > (SCREEN_HEIGHT - self.tabBarHeight)*3) {
                _isTop = NO;
                //超过三屏 首页按钮显示为@"返回顶部"
                [[NSNotificationCenter defaultCenter] postNotificationName:UpdateTabBarItem object:nil userInfo:@{@"status":@"1"}];
            }else {
                if (!_isTop) {
                    _isTop = YES;
                    //否则显示@"首页"
                    [[NSNotificationCenter defaultCenter] postNotificationName:UpdateTabBarItem object:nil userInfo:@{@"status":@"0"}];
                }
            }
            
            //解决scrollView嵌套手势冲突问题
            if (yOffset >= tabyOffset) {
                //当分页视图滑动至导航栏时,禁止外层tableView滑动
                _mainTableView.contentOffset = CGPointMake(0, tabyOffset);
                _isTopIsCanNotMoveTabView = YES;
            }else{
                //当分页视图和顶部导航栏分离时,允许外层tableView滑动
                _isTopIsCanNotMoveTabView = NO;
            }
            
            //取反
            _isTopIsCanNotMoveTabViewPre = !_isTopIsCanNotMoveTabView;
            
            if (!_isTopIsCanNotMoveTabViewPre) {
                NSLog(@"滑动到顶端");
                _canScroll = NO;
                [[NSNotificationCenter defaultCenter] postNotificationName:@"goTop" object:nil userInfo:@{@"canScroll":@"1"}];
            }else {
                NSLog(@"滑动到底部后开始下拉页面");
                if (!_canScroll) {
                    NSLog(@"让分页部分保持吸顶状态");
                    _mainTableView.contentOffset = CGPointMake(0, tabyOffset);
                }
            }
        }
    }
    
    #pragma mark 懒加载
    - (UITableView *)mainTableView {
        if (!_mainTableView) {
            //初始化最好在tableView创建之前设置(️:如果在tableView创建之后,设置了tableView的contentInset,比如你要头部headerView的放大效果,就会出问题,因为contentInset的设置会调用scrollViewDidScroll这个方法)
            _canScroll = YES;
            _isTopIsCanNotMoveTabView = NO;
    
            //创建tableView等代码省略...
        }
        return _mainTableView;
    }
    
    - (XXHomeFooterView *)footerView {
        if (!_footerView) {
            CGFloat listHeight = SCREEN_HEIGHT - self.naviBarHeight - self.tabBarHeight - pagingHeaderHeight;
            CGFloat height = sectionHeaderHeight + ReSize_UIHeight(300.0) + pagingHeaderHeight + listHeight; //标题区域的高度 + 轮播图高度 + 按钮区域高度 + 列表高度
            _footerView = [[XXHomeFooterView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, height)];
            _footerView.superController = self;
        }
        return _footerView;
    }
    
    

    2.中间层 - footerView,包含中层scrollView(collectionView的容器)和collectionView的创建
    为了你们更清晰的看到footerView的具体构建,这里贴出footerView的全部代码,关注内层collectionView和外层tableView滑动手势冲突的小伙伴可移至第三部分

    footerView
    scrollView禁用上下滑动

    相关代码:

    #import "XXHomeFooterView.h"
    #import "YSCommodityDetailsVC.h"
    #import "XXHomeGuessLikeListView.h"
    #import "XXHomeBaseModel.h"
    #import "XXHomeViewController.h"
    
    #define btnWidth 48
    #define btnHeight 40
    #define pagingHeaderHeight 60
    #define carouselViewHeight ReSize_UIHeight(300.0)
    
    @interface XXHomeFooterView () <SDCycleScrollViewDelegate, UIScrollViewDelegate>
    @property (weak, nonatomic) IBOutlet UIView *carouselBgView;          //轮播图背景
    @property (weak, nonatomic) IBOutlet UIView *btnBgView;                  //按钮背景
    @property (weak, nonatomic) IBOutlet UIScrollView *listBgScrollView; //商品列表背景
    
    @property (nonatomic, strong) SDCycleScrollView *carouselView; //轮播图
    @property (nonatomic, strong) NSArray *btnTitleArr; //按钮title
    @property (nonatomic, strong) UIView *line; //btn下滑线
    @property (nonatomic, strong) UIButton *lastSelectBtn; //上次选中的btn
    
    //数据
    @property (nonatomic,   copy) NSArray *carouselDataArr; //轮播图数据
    
    @end
    
    
    @implementation XXHomeFooterView
    {
        CGFloat  _listHeight;
        CGFloat  _naviBarHeight;
        CGFloat  _tabBarHeight;
    }
    
    - (instancetype)initWithFrame:(CGRect)frame superVC:(XXHomeViewController *)superVC {
        if (self = [super initWithFrame:frame]) {
           self = [[[NSBundle mainBundle] loadNibNamed:@"XXHomeFooterView" owner:self options:nil] lastObject];
           self.frame = frame;
            _naviBarHeight = NaviBarHeight;
            _tabBarHeight = TabBarHeight;
            _listHeight = SCREEN_HEIGHT - _naviBarHeight - _tabBarHeight - pagingHeaderHeight;
           self.superController = superVC;
           //注册通知-用于标签栏通知其返回顶部
           [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backTop) name:BackTopHomeFooterView object:nil];
           [self createUI];
           self.listBgScrollView.delegate = self;
        }
        return self;
    }
    
    - (NSArray *)btnTitleArr {
        if (!_btnTitleArr) {
            _btnTitleArr = @[@"包袋", @"礼服", @"旅行"];
        }
        return _btnTitleArr;
    }
    
    - (void)createUI {
        //轮播图
        self.carouselView = [SDCycleScrollView cycleScrollViewWithFrame:CGRectMake(0, 0, self.width, ReSize_UIHeight(self.carouselBgView.height)) delegate:self placeholderImage:[UIImage imageNamed:@"XXHome_swiper_botttom"]];
        [self.carouselBgView addSubview:self.carouselView];
        
        //中间按钮区域
        for (NSInteger i = 0; i < self.btnTitleArr.count; i++) {
            //创建按钮
            UIButton * btn = [UIButton buttonWithType:(UIButtonTypeCustom)];
            btn.backgroundColor = [UIColor clearColor];
            [btn setTitle:_btnTitleArr[i] forState:(UIControlStateNormal)];
            btn.titleLabel.font = NFont(13);
            [btn setTitleColor:[UIColor blackColor] forState:(UIControlStateSelected)];
            [btn setTitleColor:[UIColor colorWithHexString:@"#9C9C9C"] forState:(UIControlStateNormal)];
            [btn addTarget:self action:@selector(btnClickAction:) forControlEvents:(UIControlEventTouchUpInside)];
            btn.tag = 100 + i;
            [self.btnBgView addSubview:btn];
            
            if (i == 0) {
                [btn mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.left.offset(5);
                    make.top.offset(10);
                    make.width.offset(btnWidth);
                    make.height.offset(btnHeight);
                }];
                //初始选中
                btn.selected = YES;
                _lastSelectBtn = btn;
            }else if (i == 1) {
                [btn mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.centerX.offset(0);
                    make.top.offset(10);
                    make.width.offset(btnWidth);
                    make.height.offset(btnHeight);
                }];
            }else {
                [btn mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.right.offset(-5);
                    make.top.offset(10);
                    make.width.offset(btnWidth);
                    make.height.offset(btnHeight);
                }];
            }
        }
        //下划线
        _line = [UIView new];
        _line.backgroundColor = [UIColor blackColor];
        [self.btnBgView addSubview:_line];
        [_line mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.offset(15);
            make.top.offset(btnHeight + 5);
            make.width.offset(btnWidth - 20);
            make.height.offset(2);
        }];
        
        //商品列表页
        _listBgScrollView.contentSize = CGSizeMake(self.btnTitleArr.count * self.width, 0);
        for (NSInteger i = 0; i < self.btnTitleArr.count ; i++) {
            XXHomeGuessLikeListView *pageView = [[XXHomeGuessLikeListView alloc] init];
            pageView.tag = 200+i;
            pageView.currentIndex = i;
            pageView.superController = self.superController;
            [_listBgScrollView addSubview:pageView];
            [pageView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.left.offset(i*self.width);
                make.top.offset(0);
                make.width.offset(self.width);
                make.height.offset(_listHeight);
            }];
    
            //item点击的回调
            __weak typeof(self) weakSelf = self;
            pageView.didSelectItemBlock = ^(IOShopListModel *model) {
                //商品详情
                YSCommodityDetailsVC *shopDetail = [[YSCommodityDetailsVC alloc] init];
                shopDetail.shopDetailID = [NSString stringWithFormat:@"%li",model.shopId];
                shopDetail.ppName = @"商品详情";
                [weakSelf.superController.navigationController pushViewController:shopDetail animated:YES];
            };
        }
    }
    
    
    #pragma mark 更新数据
    - (void)updateData:(NSDictionary *)dataDic {
        //轮播图
        _carouselDataArr = [dataDic[@"swiper"] copy];
        NSMutableArray *carouselImageArr = [NSMutableArray array];
        for (XXHomeBaseModel  *model in _carouselDataArr) {
            NSArray *imgArr = model.imgs;
            if (imgArr.count > 0) {
                [carouselImageArr addObject:imgArr[0][@"url"]];
            }
        }
        _carouselView.imageURLStringsGroup = carouselImageArr;
        
        //包袋、礼服、旅行生活
        //包袋
        XXHomeGuessLikeListView *pageView1 = [self viewWithTag:200];
        [pageView1 updateDataWithIds:dataDic[@"handbags"]];
        //礼服
        XXHomeGuessLikeListView *pageView2 = [self viewWithTag:201];
        [pageView2 updateDataWithIds:dataDic[@"fulldress"]];
        //旅行生活
        XXHomeGuessLikeListView *pageView3 = [self viewWithTag:202];
        [pageView3 updateDataWithIds:dataDic[@"travellife"]];
    }
    
    #pragma mark 按钮的点击事件
    - (void)btnClickAction:(UIButton *)sender {
        //更新按钮状态
        [self updateBtnSAndPageViewStatusWithIndex:sender.tag-100];
    }
    
    //更新按钮的状态
    - (void)updateBtnSAndPageViewStatusWithIndex:(NSInteger)index {
        //更新按钮下标
        //获取当前btn
        UIButton *sender = [self viewWithTag:index+100];
        //先改变上一次选中button的字体大小和状态(颜色)
        _lastSelectBtn.selected = NO;
        //再改变当前选中button的字体大小和状态(颜色)
        sender.selected = YES;
        //移动下划线
        [UIView animateWithDuration:0.15 animations:^{
            CGPoint point = _line.center;
            point.x = sender.center.x;
            _line.center = point;
        }];
        //更新_lastSelectBtn
        _lastSelectBtn = sender;
        
        //更新列表页下标
        [_listBgScrollView setContentOffset:CGPointMake(index * _listBgScrollView.width, 0) animated:YES];
    }
    
    //增加分页视图左右滑动和外界tableView上下滑动互斥处理
    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
        [[NSNotificationCenter defaultCenter] postNotificationName:IsScrollHomeVC object:nil userInfo:@{@"canScroll":@"0"}];
    }
    
    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
        [[NSNotificationCenter defaultCenter] postNotificationName:IsScrollHomeVC object:nil userInfo:@{@"canScroll":@"1"}];
    }
    
    // 列表页结束滑动
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        CGFloat offetX = scrollView.contentOffset.x;
        NSInteger index = (NSInteger)offetX/scrollView.width;
        [self updateBtnSAndPageViewStatusWithIndex:index];
    }
    
    #pragma mark SDCycleScrollViewDelegate
    /** 点击图片回调 */
    - (void)cycleScrollView:(SDCycleScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index {
    
    }
    
    @end
    
    
    1. 内层collectionView的手势处理
    #import <UIKit/UIKit.h>
    #import "IOShopListModel.h"
    
    @interface XXHomeGuessLikeListView : UIView
    
    @property (nonatomic, copy) NSArray *dataArray;
    @property (nonatomic, copy) void (^didSelectItemBlock)(IOShopListModel *model); //item点击事件的回调
    
    //更新数据
    - (void)updateDataWithIds:(NSArray *)ids;
    
    @end
    
    #import "XXHomeGuessLikeListView.h"
    #import "ShopCollectionViewCell.h"
    #import "BanLiYearCardVC.h"
    
    @interface XXHomeGuessLikeListView () <UIScrollViewDelegate>
    
    @property (weak,  nonatomic) IBOutlet UICollectionView *collectionView;
    @property (nonatomic,   copy) NSArray *idArray;
    @property (nonatomic,   copy) NSArray *commodityListArr;
    @property (nonatomic, strong) UIScrollView * scrollView;
    @property (nonatomic, assign) BOOL canScroll;
    
    @end
    
    
    @implementation XXHomeGuessLikeListView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
            self = [[[NSBundle mainBundle] loadNibNamed:@"XXHomeGuessLikeListView" owner:self options:nil] lastObject];
            self.frame = frame;
        }
        return self;
    }
    
    - (void)awakeFromNib {
        [super awakeFromNib];
        [_collectionView registerClass:[ShopCollectionViewCell class] forCellWithReuseIdentifier:@"ShopCollectionViewCell"];
        //子控制器视图到达顶部的通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptMsg:) name:@"goTop" object:nil];
        //子控制器视图离开顶部的通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptMsg:) name:@"leaveTop" object:nil];
    }
    
    - (void)dealloc {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    
    //接收信息,处理通知
    - (void)acceptMsg:(NSNotification *)notification {
        NSString *notificationName = notification.name;
        if ([notificationName isEqualToString:@"goTop"]) {
            NSDictionary *userInfo = notification.userInfo;
            NSString *canScroll = userInfo[@"canScroll"];
            if ([canScroll isEqualToString:@"1"]) {
                _canScroll = YES;
                _collectionView.showsVerticalScrollIndicator = YES;
            }
        }else if([notificationName isEqualToString:@"leaveTop"]){
            _canScroll = NO;
            _collectionView.contentOffset = CGPointZero;
            _collectionView.showsVerticalScrollIndicator = NO;
        }
    }
    
    //更新数据
    - (void)updateDataWithIds:(NSArray *)ids {
        _dataArray = ids;
        //请求商品列表
        if (_dataArray.count > 0) {
            //将id数组转成字符串
            NSString *idStrs = [_dataArray componentsJoinedByString:@","];
            NSDictionary *dic = @{@"ids":idStrs};
            [YQHttpRequest getData:dic url:@"/commodity/guessLikeCommodityList" success:^(id responseDic) {
                if ([responseDic isKindOfClass:[NSArray class]]) {
                    NSArray *dataArr = [NSArray modelArrayWithClass:IOShopListModel.class json:responseDic];
                    if (dataArr.count > 0) {
                        _commodityListArr = dataArr;
                        [_collectionView reloadData];
                    }
                }else{
                    [MBProgressHUD showError:@"请求列表失败"];
                }
            } fail:^(NSError *error) {
                if (error) {
                    [MBProgressHUD showError:@"请求列表失败"];
                }
            }];
        }
    }
    
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        if (scrollView == _collectionView) {
            if (!self.canScroll) {
                [scrollView setContentOffset:CGPointZero];
            }
            CGFloat offsetY = scrollView.contentOffset.y;
            if (offsetY <= 0) {
                [[NSNotificationCenter defaultCenter] postNotificationName:@"leaveTop" object:nil userInfo:@{@"canScroll":@"1"}];
            }
        }
    }
    
    
    #pragma mark CollectionView Delegate
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
        return self.commodityListArr.count;
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        ShopCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ShopCollectionViewCell" forIndexPath:indexPath];
        if (self.commodityListArr.count > 0) {
            cell.model = _commodityListArr[indexPath.row];
        }
        return cell;
    }
    
    - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
        if (self.didSelectItemBlock) {
            IOShopListModel *model = _commodityListArr[indexPath.row];
            self.didSelectItemBlock(model);
        }
    }
    
    #pragma mark FlowLayoutDelegate
    - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
        return CGSizeMake((self.width-10)/2.0, 316.0);
    }
    
    @end
    
    
    补充:
    1. 由于该项目下方分页的都是一样的类型collectionView,并且布局也一样,所以我这里使用了在scrollView上添加collectionView,并没有产出冗余的代码,如果你的业务逻辑稍复杂,可采用子控制器替换直接添加collectionView的方式,你也可以看我整理的 Demo,其中使用的就是添加子控制器的方式;

    2. 之前我也写过相似的demo,效果是头部放大,下面分页部分上滑吸顶,也是采用上面这种方式解决滑动冲突,不过还有不同的是里面的分页部分我采用的是子控制器的方式,里面做了很详细的注释;
      笔 记
      Demo

    3. 除了改变contentOffset这种方式,还有没有其他方式可以解决scrollView嵌套手势冲突问题呢?
      (1) 没故事的卓同学 - 嵌套UIScrollview的滑动冲突解决方案
      (2) 军_andy - iOS 嵌套UIScrollview的滑动冲突另一种解决方案
      上面第一种我试过,但是有瑕疵, 第二种加入了动画,但是集成稍显麻烦,下面这两种是飞羽田海的推荐,很不错,大家可以参考一下:
      (3) 腾讯开源框架-特斯拉组件" 体验比较好 内有OC和Swift版本,
      地址: https://github.com/xichen744/SPPage
      (4) 这个开源库很不错,目前star数1400多,一直在维护,
      地址: https://github.com/Roylee-ML/SwipeTableView

    亲们喜欢的话,请给个 ❤️ 额,谢谢亲们的支持!!!

    相关文章

      网友评论

      • 白色天空729:楼主,我有个问题请教你。在uiscollView 上面嵌套一层UItableView,然后我想使uitableView滑动偏移量大于60前,我想实现表格向上移动的x轴偏移量与scrollView保持一致,大于60后scrollView停止滚动,表格恢复滚动,我之前是判断scrollView滚动的偏移量是否大于60,但是发现问题就是,我在60的临界值这个将当前的scrollView的scrollEnabled属性动态改变,但是发现scrollView在持续滚动的同时,仍然不能滚动,将手抬起后,方能恢复滚动。。。
        ArchLL:如果遇到难以理解的地方可以问我
        ArchLL:@Metro追光者 你可以参考一下我的demo, 如果你领会了我的做法,你这个需求就能实现
        ArchLL:@白色天空729 不能通过scrollEnabled改变,因为你这是持续滑动
      • 铸剑三载:大致思路是用子VC的方式,通过通知来实现内外scrollView的无缝衔接,思路还是很不错的。
      • bdc08bbd8fb2:你demo这个效果还是很差,从上面滑下来到菜单栏就会停住,没有一个连贯性,如果只是这样的话,根本也不需要你那么麻烦,有很简单的解决方案,而且还不会在滑动有任何问题。但是现在是想要滑动ScrollView的到底部的时候也要有连贯性让tableView也跟着滑动,不能解决这个问题那没啥用
        ArchLL:@一叶知秋YY 当菜单栏吸顶的时候上下滑动菜单栏没反应,这个是故意为之,当然如果想让滑动这一点也可以做到,不过需要菜单页对应的tableVIew的contentOffset=0,还有你的第二个问题不太明白。最后,我的demo是有很多不完整的地方,不过旨在解决滑动冲突处理方向,不要因为别人的东西不能满足你的需求,就妄加评论,你所说的更简单的解决方案不妨说出来大家看一下,你能拿出更好的解决方案,自然更有说服力。
      • 凡几多:您好,我下载了您上面那个demo,就是里面文字是“普吉岛、夏威夷、洛杉矶、新泽西”的那个demo。我按照demo实现了,但是包括demo都有这个bug,就是上下滑动tableView的时候,手指或鼠标如果正好是触摸在“普吉岛、夏威夷、洛杉矶、新泽西”这个范围内,想进行滑动是滑动不了的,只能放在其他位置。也就是说在主tableView可滑动、子tableView不可滑动时,无法触摸子tableView的headerView进行滑动。您知道这个问题吗?如果解决呢?多谢
        凡几多:找到问题所在了,我把我项目里的tableView的父类中的CGFloat listHeight = kMainScreenHeight - naviBarHeight - segmentMenuHeight;里的 segmentMenuHeight去掉就行了。但是demo里去掉这个好像没效果。不过目前是已经解决问题了。
      • cloud_sky_z:你好,我想问下,下面没有tabbar的情况下,你怎么适配iphoneX的,因为设置了临界点,列表滑动到一定位置,没法继续上移,会导致在iphoneX上列表滑到最底下的时候,底下的34px上仍然有内容,按理说这时安全区域上是不该有内容的才对。我现在在弄这个适配的问题
        cloud_sky_z:正常情况下,tableview在滑动时,地下那34px是有内容的,那样才能凸显出iphoneX全面屏得优势,当tableview滑动到底部时,内容滑完了,手松开tableview这时底部那34是应该空出来的。
        ArchLL:@topStarSky 如果是这样子的话,你只需在初始化控制器的时候判断是否为iPhoneX,调整外层tableview的height或者约束,以及临界值就好了
        ArchLL:@topStarSky 你是想让安全区域一直没有内容吗?
      • C_HPY:下了你们的app看了,兄弟可以啊。
        ArchLL:@C己__ 谢谢,希望可以帮到你
      • 大牛在郑州:这是什么APP?
        ArchLL:星洞
      • 郑一一一一:header 还没完全从屏幕中消失的情况下,用力往上滑,只能做到 分页栏在顶部,但是分页栏下面 UITableView 就不能继续往下滚动了。。一起想想办法
        郑一一一一:@小屁番茄 不对啊,可是当 header 没了,再用力下滑,怎么就不是吸顶了呢
        郑一一一一:@Metro追光者 噢,明白了
        ArchLL:@小屁番茄 是故意这样做的,不然的话,你怎么让分页栏吸顶?
      • onetwo_a:1,能每个子tableView都可以下拉刷新而不是拉整个页面刷新吗?
        2,能保存子tableview的滑动进度吗,也就是说横滚之后contentOffset不变0
        onetwo_a:但是有的应用临界值上面的的界面没那么大,是有子tableview下拉刷新这种效果的,而且也很流畅不知道是怎么做的。
        onetwo_a:是的,我就是在思考内外滑动不连贯的问题,因为如果子tableview有下拉刷新的话就要有弹性效果,这个弹性会导致内外滑动都不连贯。
        ArchLL:对于你问的第一个问题,并不适用我这里,因为临界点以上的内容很多,也需要刷新数据,如果两者都可以刷新,那么内外滑动切换就不连贯;第二个问题我暂时还没想到解决办法
      • 飞羽田海:如果在左右滑动的同时 上下不能滑动就完美了
        ArchLL:@飞羽田海 我是这样处理的,下面分页部分左右切换的时候,collectionView不能上下滑动
        飞羽田海:@Metro追光者 我以前也写过和你类似的这个,但我看微博的个人中心,在左右滑动的时候 上下是不能滑动的 个人觉得那样的体验更好一些
        ArchLL:@飞羽田海 这个建议不错,这两天抽空优化一下

      本文标题:iOS开发实战 - 完美解决UIScrollView嵌套滑动手势

      本文链接:https://www.haomeiwen.com/subject/fwylrftx.html