/JXPagingView

类似微博主页、简书主页等效果。多页面嵌套,既可以上下滑动,也可以左右滑动切换页面。支持HeaderView悬浮、支持下拉刷新、上拉加载更多。

Primary LanguageObjective-CMIT LicenseMIT

JXPagingView

类似微博主页、简书主页、QQ联系人页面等效果。多页面嵌套,既可以上下滑动,也可以左右滑动切换页面。支持HeaderView悬浮、支持下拉刷新、上拉加载更多。

功能特点

  • 支持OC与Swift;
  • 支持列表懒加载,等到列表真正显示的时候才加载,而不是一次性加载所有列表;
  • 支持首页下拉刷新、列表视图下拉刷新、列表视图上拉加载更多;
  • 支持悬浮SectionHeader的垂直位置调整;
  • 支持从顶部用力往上滚动,下面的列表会跟着滚动,而不会突然卡主,需要使用JXPagerSmoothView类;
  • 列表封装简洁,只要遵从JXPagingViewListViewDelegate协议即可。UIView、UIViewController等都可以;
  • 使用JXCategoryView/JXSegmentedView分类控制器,几乎支持所有主流效果、高度自定义、可灵活扩展;
  • 支持横竖屏切换;
  • 支持点击状态栏滚动当前列表到顶部;
  • 支持列表显示和消失的生命周期方法;
  • isListHorizontalScrollEnabled属性控制列表是否可以左右滑动,默认YES;
  • 支持FDFullscreenPopGesture等全屏手势兼容处理;

预览

效果 预览图
头图缩放
参考ZoomViewController
Zoom
主页下拉刷新&列表上拉加载更多
参考RefreshViewController
Refresh
列表下拉刷新
参考ListRefreshViewController
Refresh
悬浮sectionHeader位置调整 Refresh
导航栏隐藏
参考NaviBarHiddenViewController
Refresh
CollectionView列表示例
参考CollectionViewViewController.swift
只有swift的demo工程有该示例
Refresh
HeaderView更新高度示例
参考HeightChangeAnimationViewController.swift
只有swift demo工程才有该示例
Refresh
PagingView嵌套CategoryView
参考NestViewController
只有 OC!OC!OC! 的demo工程才有该示例
操作比较特殊,如果需要此效果,
请认真参考源码,有问题多试试
参考NestViewController.h类
Nest
CategoryView嵌套PagingView
参考NestViewController.swift
只有 Swift!Swift!Swift! 的demo工程才有该示例
操作比较特殊,如果需要此效果,
请认真参考源码,有问题多试试
参考NestViewController.swift类
Nest
点击状态栏 Zoom
横竖屏旋转 Zoom
JXPageListView
顶部需要自定义cell的场景,类似于电商APP首页,滑动到列表最底部才是分类控制器
该效果是另一个库,点击查看JXPageListView
该效果是另一个库,点击查看JXPageListView
该效果是另一个库,点击查看JXPageListView
list
JXPagerSmoothView
类似淘宝、转转首页
从顶部用力往上滚动,下面的列表会继续滚动
smooth

安装

手动

Swift版本: Clone代码,拖入JXPagingView-Swift文件夹,使用JXPagingView类;

OC版本: Clone代码,拖入JXPagerView文件夹,使用JXPagerView类;

CocoaPods

  • Swift版本

支持swift版本:5.0+

target '<Your Target Name>' do
    pod 'JXPagingView/Paging'
end
  • OC版本
target '<Your Target Name>' do
    pod 'JXPagingView/Pager'
end

Swift与OC的仓库地址不一样,请注意选择!

pod repo update然后再pod install

使用

swift版本使用类似,只是类名及相关API更改为JXPagingView,具体细节请查看Swfit工程。

1、初始化JXCategoryTitleViewJXPagerView

self.categoryView = [[JXCategoryTitleView alloc] initWithFrame:frame];
//配置categoryView,细节参考源码

self.pagerView = [[JXPagerView alloc] initWithDelegate:self];
[self.view addSubview:self.pagerView];

//关联contentScrollView,这样列表就可以和categoryView联动了。
self.categoryView.contentScrollView = self.pagerView.listContainerView.collectionView;

2、实现JXPagerViewDelegate协议

/**
 返回tableHeaderView的高度,因为内部需要比对判断,只能是整型数
 */
- (NSUInteger)tableHeaderViewHeightInPagerView:(JXPagerView *)pagerView {
    return JXTableHeaderViewHeight;
}

/**
 返回tableHeaderView
 */
- (UIView *)tableHeaderViewInPagerView:(JXPagerView *)pagerView {
    return self.userHeaderView;
}


/**
 返回悬浮HeaderView的高度,因为内部需要比对判断,只能是整型数
 */
- (NSUInteger)heightForPinSectionHeaderInPagerView:(JXPagerView *)pagerView {
    return JXheightForHeaderInSection;
}


/**
 返回悬浮HeaderView
 */
- (UIView *)viewForPinSectionHeaderInPagerView:(JXPagerView *)pagerView {
    return self.categoryView;
}

/**
 返回列表的数量
 */
- (NSInteger)numberOfListsInPagerView:(JXPagerView *)pagerView {
    //和categoryView的item数量一致
    return self.titles.count;
}

/**
 根据index初始化一个对应列表实例。注意:一定要是新生成的实例!!!
 只要遵循JXPagerViewListViewDelegate即可,无论你返回的是UIView还是UIViewController都可以。
 */
- (id<JXPagerViewListViewDelegate>)pagerView:(JXPagerView *)pagerView initListAtIndex:(NSInteger)index {
    TestListBaseView *listView = [[TestListBaseView alloc] init];
    if (index == 0) {
        listView.dataSource = @[@"橡胶火箭", @"橡胶火箭炮", @"橡胶机关枪"...].mutableCopy;
    }else if (index == 1) {
        listView.dataSource = @[@"吃烤肉", @"吃鸡腿肉", @"吃牛肉", @"各种肉"].mutableCopy;
    }else {
        listView.dataSource = @[@"【剑士】罗罗诺亚·索隆", @"【航海士】娜美", @"【狙击手】乌索普"...].mutableCopy;
    }
    [listView beginFirstRefresh];
    return listView;
}

3、实现JXPagerViewListViewDelegate协议

列表可以是任意类,UIView、UIViewController等等都可以,只要实现了JXPagerViewListViewDelegate协议就行。

⚠️⚠️⚠️一定要保证scrollCallback的正确回调,许多朋友都容易疏忽这一点,导致异常,务必重点注意!

下面的使用代码参考的是TestListBaseView

/**
 返回listView。如果是vc包裹的就是vc.view;如果是自定义view包裹的,就是自定义view自己。
 */
- (UIView *)listView {
    return self;
}

/**
 返回listView内部持有的UIScrollView或UITableView或UICollectionView
 主要用于mainTableView已经显示了header,listView的contentOffset需要重置时,内部需要访问到外部传入进来的listView内的scrollView
 */
- (UIScrollView *)listScrollView {
    return self.tableView;
}


/**
 当listView内部持有的UIScrollView或UITableView或UICollectionView的代理方法`scrollViewDidScroll`回调时,需要调用该代理方法传入的callback
 */
- (void)listViewDidScrollCallback:(void (^)(UIScrollView *))callback {
    self.scrollCallback = callback;
}

4、列表回调处理

TestListBaseView在其tableView的滚动回调中,通过调用上面持有的scrollCallback,把列表的滚动事件回调给JXPagerView内部。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    !self.scrollCallback ?: self.scrollCallback(scrollView);
}

实现原理

实现原理

JXPagerSmoothView

如果你需要类似于淘宝转转首页从顶部header用力往上滚动之后,下面的列表会跟着滚动的效果。因为JXPagerView的实现原理限制,当用户从顶部header的位置用力往上滚动,JXPagerView会在JXCategoryView刚好在顶部的时候突然停住。这个时候就需要使用JXPagerSmoothView,swift版本叫JXPagingSmoothView

因为与JXPagerView的原理完全不同,所以各自会有一些特性的区别,但是从使用体验来说,是完全一致的。具体使用细节请参考demo示例。

实现原理参考JXPagerSmoothView文章解析

特殊说明

JXCategoryView、JXSegmentedView

悬浮的HeaderView,用的是我写的:OC版本-JXCategoryViewSwift版本-JXSegmentedView。几乎实现了所有主流效果,而且非常容易自定义扩展,强烈推荐阅读。

头图缩放说明

头图缩放原理,参考这个库:JXTableViewZoomHeaderImageView

列表下拉刷新说明

需要使用JXPagerListRefreshView类(是JXPagerView的子类)

关于下方列表视图的代理方法- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath有时候需要点击两次才回调

出现步骤:当手指放在下方列表视图往下拉,直到TableHeaderView完全显示。

原因:经过上面的步骤之后,手指已经离开屏幕且列表视图已经完全静止,UIScrollView的isDragging属性却依然是true。就导致了后续的第一次点击,让系统认为当前UIScrollView依然在滚动,该点击就让UIScrollView停止下来,没有继续转发给UITableView,就没有转化成didSelectRow事件。

解决方案:经过N种尝试之后,还是没有回避掉系统的isDragging异常为true的bug。大家可以在自定义cell最下方放置一个与cell同大小的button,把button的touchUpInside事件当做didSelectRow的回调。因为UIButton在响应链中的优先级要高于UIGestureRecognizer。

代码:请参考TestTableViewCell类的配置。

指定默认选中index

默认显示index=2的列表,代码如下:

self.pagerView.defaultSelectedIndex = 2;
self.categoryView.defaultSelectedIndex = 2;

顶部轮播图手势处理

如果TableHeaderView添加了轮播图,获取其他可以横向滚动的UIScrollView。如果不处理,就会出现左右滚动轮播图的时候又可以触发整个页面的上下滚动。为了规避该问题,请参考示例仓库中BannerViewController类的处理方法。即可同一时间只允许左右滚动或者上下滚动。

关于JXCategoryView点击item之后的切换处理

因为底层触发列表加载是在代理方法:- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath回调里面。 所以,如果当前有5个item,当前在第1个,用户点击了第5个。categoryView默认是通过设置contentOffset.x滚动到指定的位置,这个时候有个问题,就会触发中间2、3、4的cellForItemAtIndexPath方法。也就会触发2、3、4的列表加载。

为了避免这种情况,请加上下面这段代码:

self.categoryView.contentScrollViewClickTransitionAnimationEnabled = NO;

关于列表用UIViewController封装且要支持横竖屏的tips

在列表UIViewController类里面一定要加上下面这段代码:(不要问我为什么,我也不知道,谁知道系统内部是怎么操作的,反正加上就没毛病了)

- (void)loadView {
    self.view = [[UIView alloc] init];
}

FDFullscreenPopGesture等全屏手势兼容处理

全屏手势兼容处理文档,点击查看 ❗️❗️❗️

迁移指南

  • 0.0.9版本将下面两个API的返回值修改为了NSUInteger(swift版本为Int),之前版本是CGFloat,升级为0.0.9及以上的时候,记得修改一下使用地方的返回值类型,不然会引起crash。
    • - (NSUInteger)heightForPinSectionHeaderInPagerView:(JXPagerView *)pagerView
    • - (NSUInteger)tableHeaderViewHeightInPagerView:(JXPagerView *)pagerView
  • 1.0.0版本: 删除代理方法- (NSArray <id<JXPagerViewListViewDelegate>> *)listViewsInPagerView:(JXPagerView *)pagerView;,请参考示例使用下面两个代理方法:
    • - (NSInteger)numberOfListsInPagerView:(JXPagerView *)pagerView;
    • - (id<JXPagerViewListViewDelegate>)pagerView:(JXPagerView *)pagerView initListAtIndex:(NSInteger)index;

补充

有不明白的地方,建议多看下源码。再有疑问的,欢迎提Issue交流🤝