This commit is contained in:
启星
2025-08-12 14:27:12 +08:00
parent 9d18b353b1
commit 1bd5e77c45
8785 changed files with 978163 additions and 2 deletions

21
Pods/JXPagingView/LICENSE generated Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 暴走的鑫鑫
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

294
Pods/JXPagingView/README.md generated Normal file
View File

@@ -0,0 +1,294 @@
# JXPagingView
类似微博主页、简书主页、QQ联系人页面等效果。多页面嵌套既可以上下滑动也可以左右滑动切换页面。支持HeaderView悬浮、支持下拉刷新、上拉加载更多。
## 功能特点
- 支持OC与Swift;
- 支持列表懒加载,等到列表真正显示的时候才加载,而不是一次性加载所有列表;
- 支持首页下拉刷新、列表视图下拉刷新、列表视图上拉加载更多;
- 支持悬浮SectionHeader的垂直位置调整
- 支持从顶部用力往上滚动,下面的列表会跟着滚动,而不会突然卡主,需要使用`JXPagerSmoothView`类;
- 列表封装简洁,只要遵从`JXPagingViewListViewDelegate`协议即可。UIView、UIViewController等都可以
- 使用JXCategoryView/JXSegmentedView分类控制器几乎支持所有主流效果、高度自定义、可灵活扩展
- 支持横竖屏切换;
- 支持点击状态栏滚动当前列表到顶部;
- 支持列表显示和消失的生命周期方法;
- isListHorizontalScrollEnabled属性控制列表是否可以左右滑动默认YES
- 支持`FDFullscreenPopGesture`等全屏手势兼容处理;
## 预览
| 效果 | 预览图 |
|-------|-------|
| **头图缩放** <br/>参考[ZoomViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Zoom/ZoomViewController.m)类 | ![Zoom](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/Zoom.gif) |
| **主页下拉刷新&列表上拉加载更多** <br/>参考[RefreshViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Refresh/RefreshViewController.m)类 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/Refresh.gif) |
| **列表下拉刷新** <br/>参考[ListRefreshViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Refresh/ListRefreshViewController.m)类 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/ListRefresh.gif) |
| **悬浮sectionHeader位置调整** | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/PinSectionHeaderPosition.gif) |
| **导航栏隐藏** <br/> 参考[NaviBarHiddenViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/NavigationBarHidden/NaviBarHiddenViewController.m)类 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/NaviHidden.gif) |
| **CollectionView列表示例**<br/>参考[CollectionViewViewController.swift](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagingViewExample/JXPagingViewExample/Example/CollectionView/CollectionViewViewController.swift)类 <br/> 只有swift的demo工程有该示例 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/CollectionViewList.gif) |
| **HeaderView更新高度示例**<br/> 参考[HeightChangeAnimationViewController.swift](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagingViewExample/JXPagingViewExample/Example/HeightChange/HeightChangeAnimationViewController.swift)类 <br/> 只有swift demo工程才有该示例 | ![Refresh](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/HeaderViewHeightChange.gif) |
| **PagingView嵌套CategoryView** <br/> 参考[NestViewController](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagerViewExample-OC/JXPagerViewExample-OC/Example/Nest/NestViewController.m)类 <br/> 只有 **OC!OC!OC!** 的demo工程才有该示例 <br/> 操作比较特殊,如果需要此效果,<br/> 请认真参考源码,有问题多试试 <br/> 参考NestViewController.h类 | ![Nest](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/Nest.gif) |
| **CategoryView嵌套PagingView** <br/> 参考[NestViewController.swift](https://github.com/pujiaxin33/JXPagingView/tree/master/Examples/JXPagingViewExample/JXPagingViewExample/Example/CategoryNestPaging/NestViewController.swift)类 <br/> 只有 **Swift!Swift!Swift!** 的demo工程才有该示例 <br/> 操作比较特殊,如果需要此效果,<br/> 请认真参考源码,有问题多试试 <br/> 参考NestViewController.swift类 | ![Nest](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/CategoryNestPaging.gif) |
| **点击状态栏** | ![Zoom](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/StatusBarClicked.gif) |
| **横竖屏旋转** | ![Zoom](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/ScreenRotate.gif) |
| **JXPageListView**<br/> 顶部需要自定义cell的场景类似于电商APP首页滑动到列表最底部才是分类控制器 <br/> 该效果是另一个库,点击查看[JXPageListView](https://github.com/pujiaxin33/JXPageListView) <br/> 该效果是另一个库,点击查看[JXPageListView](https://github.com/pujiaxin33/JXPageListView) <br/> 该效果是另一个库,点击查看[JXPageListView](https://github.com/pujiaxin33/JXPageListView) | ![list](https://github.com/pujiaxin33/JXPageListView/blob/master/JXPageListView/Gif/headerLoading.gif) |
| **JXPagerSmoothView**<br/> 类似淘宝、转转首页 <br/> 从顶部用力往上滚动,下面的列表会继续滚动 | ![smooth](https://github.com/pujiaxin33/JXExampleImages/blob/master/JXPaingView/smooth.gif) |
## 安装
### 手动
**Swift版本** Clone代码拖入JXPagingView-Swift文件夹使用`JXPagingView`类;
**OC版本** Clone代码拖入JXPagerView文件夹使用`JXPagerView`类;
### CocoaPods
- **Swift版本**
支持swift版本5.0+
```ruby
target '<Your Target Name>' do
pod 'JXPagingView/Paging'
end
```
- **OC版本**
```ruby
target '<Your Target Name>' do
pod 'JXPagingView/Pager'
end
```
Swift与OC的仓库地址不一样请注意选择
`pod repo update`然后再`pod install`
## 使用
swift版本使用类似只是类名及相关API更改为`JXPagingView`具体细节请查看Swfit工程。
### 1、初始化`JXCategoryTitleView`和`JXPagerView`
```Objective-C
self.categoryView = [[JXCategoryTitleView alloc] initWithFrame:frame];
//配置categoryView细节参考源码
self.pagerView = [[JXPagerView alloc] initWithDelegate:self];
[self.view addSubview:self.pagerView];
//⚠将pagerView的listContainerView和categoryView.listContainer进行关联这样列表就可以和categoryView联动了。⚠
self.categoryView.listContainer = (id<JXCategoryViewListContainer>)self.pagerView.listContainerView;
```
**Swift版本列表关联代码**
```Swift
//给JXPagingListContainerView添加extension表示遵从JXSegmentedViewListContainer的协议
extension JXPagingListContainerView: JXSegmentedViewListContainer {}
//⚠将pagingView的listContainerView和segmentedView.listContainer进行关联这样列表就可以和categoryView联动了。⚠
segmentedView.listContainer = pagingView.listContainerView
```
### 2、实现`JXPagerViewDelegate`协议
```Objective-C
/**
返回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`类
```Objective-C
/**
返回listView。如果是vc包裹的就是vc.view如果是自定义view包裹的就是自定义view自己。
*/
- (UIView *)listView {
return self;
}
/**
返回listView内部持有的UIScrollView或UITableView或UICollectionView
主要用于mainTableView已经显示了headerlistView的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内部。
```Objective-C
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
!self.scrollCallback ?: self.scrollCallback(scrollView);
}
```
## 实现原理
[实现原理](https://github.com/pujiaxin33/JXPagingView/blob/master/Document/JXPagingView%E5%8E%9F%E7%90%86.md)
## `JXPagerSmoothView`
如果你需要类似于**淘宝**、**转转**首页从顶部header用力往上滚动之后下面的列表会跟着滚动的效果。因为`JXPagerView`的实现原理限制当用户从顶部header的位置用力往上滚动`JXPagerView`会在`JXCategoryView`刚好在顶部的时候突然停住。这个时候就需要使用`JXPagerSmoothView`swift版本叫`JXPagingSmoothView`。
因为与`JXPagerView`的原理完全不同所以各自会有一些特性的区别但是从使用体验来说是完全一致的。具体使用细节请参考demo示例。
实现原理参考[JXPagerSmoothView文章解析](https://juejin.im/post/5ddb2fe4f265da7def5424c7)
## 特殊说明
### JXCategoryView、JXSegmentedView
悬浮的HeaderView用的是我写的[OC版本-JXCategoryView](https://github.com/pujiaxin33/JXCategoryView) 、[Swift版本-JXSegmentedView](https://github.com/pujiaxin33/JXSegmentedView)。几乎实现了所有主流效果,而且非常容易自定义扩展,强烈推荐阅读。
### 头图缩放说明
头图缩放原理,参考这个库:[JXTableViewZoomHeaderImageView](https://github.com/pujiaxin33/JXTableViewZoomHeaderImageView)
### 列表下拉刷新说明
需要使用`JXPagerListRefreshView`类(是`JXPagerView`的子类)
### JXPagerListContainerType说明
UIScrollView优势没有其他副作用。劣势实时的视图内存占用相对大一点因为所有加载之后的列表视图都在视图层级里面。
UICollectionView优势因为列表被添加到cell上实时的视图内存占用更少适合内存要求特别高的场景。劣势因为cell重用机制的问题导致列表被移除屏幕外之后会被放入缓存区而不存在于视图层级中。如果刚好你的列表使用了下拉刷新视图在快速切换过程中就会导致下拉刷新回调不成功的问题。使用MJRefresh会出现此问题一句话概括使用CollectionView的时候就不要让列表使用下拉刷新加载。
### 关于下方列表视图的代理方法`- (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`类的处理方法。即可同一时间只允许左右滚动或者上下滚动。
### 关于列表用UIViewController封装且要支持横竖屏的tips
在列表UIViewController类里面一定要加上下面这段代码(不要问我为什么,我也不知道,谁知道系统内部是怎么操作的,反正加上就没毛病了)
```
- (void)loadView {
self.view = [[UIView alloc] init];
}
```
### `JXPagerSmoothView` header有UITextField或者`UITextView`
详情参考OC版本示例【滚动延续 Header有输入框】
列表自定义子类化`UITableView`或者`UICollectionView`,然后重载`scrollRectToVisible`方法,示例代码如下。
```Object-C
@implementation TestTableView
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated {
[self setContentOffset:CGPointMake(self.contentOffset.x, rect.origin.y) animated:animated];
}
@end
```
### `FDFullscreenPopGesture`等全屏手势兼容处理
[全屏手势兼容处理文档,点击查看 ❗️❗️❗️](https://github.com/pujiaxin33/JXPagingView/blob/master/Document/%E5%85%A8%E5%B1%8F%E6%89%8B%E5%8A%BF%E5%A4%84%E7%90%86.md)
## 迁移指南
- **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;`
- **2.0.0版本**`JXPagerListContainerView`进行了重构,列表拥有了完整的生命周期方法。列表是`UIViewController`类,`viewWillAppear`等生命周期方法将会正确触发。
-
- 删除了collectionView用`scrollView`属性替换。
- 和`CategoryView`的联动绑定代码更新为`self.categoryView.listContainer = (id<JXCategoryViewListContainer>)self.pagerView.listContainerView;`。
- `JXPagerView`新增`- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate listContainerType:(JXPagerListContainerType)type`初始化方法,可以指定列表容器为`UIScrollView`或者`UICollectionView`
## 补充
有不明白的地方建议多看下源码。再有疑问的欢迎提Issue交流🤝

View File

@@ -0,0 +1,128 @@
//
// JXCategoryListScrollView.h
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXPagerListContainerView;
@class JXPagerListContainerScrollView;
@protocol JXPagerViewListViewDelegate <NSObject>
/**
返回listView。如果是vc包裹的就是vc.view如果是自定义view包裹的就是自定义view自己。
@return UIView
*/
- (UIView *)listView;
/**
返回listView内部持有的UIScrollView或UITableView或UICollectionView
主要用于mainTableView已经显示了headerlistView的contentOffset需要重置时内部需要访问到外部传入进来的listView内的scrollView
@return listView内部持有的UIScrollView或UITableView或UICollectionView
*/
- (UIScrollView *)listScrollView;
/**
当listView内部持有的UIScrollView或UITableView或UICollectionView的代理方法`scrollViewDidScroll`回调时需要调用该代理方法传入的callback
@param callback `scrollViewDidScroll`回调时调用的callback
*/
- (void)listViewDidScrollCallback:(void (^)(UIScrollView *scrollView))callback;
@optional
- (void)listScrollViewWillResetContentOffset;
- (void)listWillAppear;
- (void)listDidAppear;
- (void)listWillDisappear;
- (void)listDidDisappear;
@end
/**
列表容器视图的类型
- ScrollView: UIScrollView。优势没有其他副作用。劣势实时的视图内存占用相对大一点因为所有加载之后的列表视图都在视图层级里面。
- CollectionView: 使用UICollectionView。优势因为列表被添加到cell上实时的视图内存占用更少适合内存要求特别高的场景。劣势因为cell重用机制的问题导致列表被移除屏幕外之后会被放入缓存区而不存在于视图层级中。如果刚好你的列表使用了下拉刷新视图在快速切换过程中就会导致下拉刷新回调不成功的问题。一句话概括使用CollectionView的时候就不要让列表使用下拉刷新加载。
*/
typedef NS_ENUM(NSUInteger, JXPagerListContainerType) {
JXPagerListContainerType_ScrollView,
JXPagerListContainerType_CollectionView,
};
@protocol JXPagerListContainerViewDelegate <NSObject>
/**
返回list的数量
@param listContainerView 列表的容器视图
@return list的数量
*/
- (NSInteger)numberOfListsInlistContainerView:(JXPagerListContainerView *)listContainerView;
/**
根据index返回一个对应列表实例需要是遵从`JXPagerViewListViewDelegate`协议的对象。
你可以代理方法调用的时候初始化对应列表,达到懒加载的效果。这也是默认推荐的初始化列表方法。你也可以提前创建好列表,等该代理方法回调的时候再返回也可以,达到预加载的效果。
如果列表是用自定义UIView封装的就让自定义UIView遵从`JXPagerViewListViewDelegate`协议该方法返回自定义UIView即可。
如果列表是用自定义UIViewController封装的就让自定义UIViewController遵从`JXPagerViewListViewDelegate`协议该方法返回自定义UIViewController即可。
@param listContainerView 列表的容器视图
@param index 目标下标
@return 遵从JXPagerViewListViewDelegate协议的list实例
*/
- (id<JXPagerViewListViewDelegate>)listContainerView:(JXPagerListContainerView *)listContainerView initListForIndex:(NSInteger)index;
@optional
/**
返回自定义UIScrollView或UICollectionView的Class
某些特殊情况需要自己处理UIScrollView内部逻辑。比如项目用了FDFullscreenPopGesture需要处理手势相关代理。
@param listContainerView JXPagerListContainerView
@return 自定义UIScrollView实例
*/
- (Class)scrollViewClassInlistContainerView:(JXPagerListContainerView *)listContainerView;
/**
控制能否初始化对应index的列表。有些业务需求需要在某些情况才允许初始化某些列表通过通过该代理实现控制。
*/
- (BOOL)listContainerView:(JXPagerListContainerView *)listContainerView canInitListAtIndex:(NSInteger)index;
- (void)listContainerViewDidScroll:(UIScrollView *)scrollView;
- (void)listContainerViewWillBeginDragging:(JXPagerListContainerView *)listContainerView;
- (void)listContainerViewWDidEndScroll:(JXPagerListContainerView *)listContainerView;
- (void)listContainerView:(JXPagerListContainerView *)listContainerView listDidAppearAtIndex:(NSInteger)index;
@end
@interface JXPagerListContainerView : UIView
@property (nonatomic, assign, readonly) JXPagerListContainerType containerType;
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
@property (nonatomic, strong, readonly) NSDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict; //已经加载过的列表字典。key是indexvalue是对应的列表
@property (nonatomic, strong) UIColor *listCellBackgroundColor; //默认:[UIColor whiteColor]
/**
滚动切换的时候滚动距离超过一页的多少百分比就触发列表的初始化。默认0.01即列表显示了一点就触发加载。范围0~1开区间不包括0和1
*/
@property (nonatomic, assign) CGFloat initListPercent;
///当使用Category嵌套Paging的时候需要设置为YES默认为NO
@property (nonatomic, assign, getter=isCategoryNestPagingEnabled) BOOL categoryNestPagingEnabled;
@property (nonatomic, assign, readonly) NSInteger currentIndex;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithType:(JXPagerListContainerType)type delegate:(id<JXPagerListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@end
@interface JXPagerListContainerView (ListContainer)
- (void)setDefaultSelectedIndex:(NSInteger)index;
- (UIScrollView *)contentScrollView;
- (void)reloadData;
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio selectedIndex:(NSInteger)selectedIndex;
- (void)didClickSelectedItemAtIndex:(NSInteger)index;
@end

View File

@@ -0,0 +1,597 @@
//
// JXPagerListContainerView.m
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXPagerListContainerView.h"
#import <objc/runtime.h>
@interface JXPagerListContainerScrollView: UIScrollView <UIGestureRecognizerDelegate>
@property (nonatomic, assign, getter=isCategoryNestPagingEnabled) BOOL categoryNestPagingEnabled;
@end
@implementation JXPagerListContainerScrollView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.isCategoryNestPagingEnabled) {
if ([gestureRecognizer isMemberOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")]) {
CGFloat velocityX = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:gestureRecognizer.view].x;
//x0
if (velocityX > 0) {
if (self.contentOffset.x == 0) {
return NO;
}
}else if (velocityX < 0) {
//x0
if (self.contentOffset.x + self.bounds.size.width == self.contentSize.width) {
return NO;
}
}
}
}
return YES;
}
@end
@interface JXPagerListContainerCollectionView: UICollectionView <UIGestureRecognizerDelegate>
@property (nonatomic, assign, getter=isCategoryNestPagingEnabled) BOOL categoryNestPagingEnabled;
@end
@implementation JXPagerListContainerCollectionView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.isCategoryNestPagingEnabled) {
if ([gestureRecognizer isMemberOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")]) {
CGFloat velocityX = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:gestureRecognizer.view].x;
//x0
if (velocityX > 0) {
if (self.contentOffset.x == 0) {
return NO;
}
}else if (velocityX < 0) {
//x0
if (self.contentOffset.x + self.bounds.size.width == self.contentSize.width) {
return NO;
}
}
}
}
return YES;
}
@end
@interface JXPagerListContainerViewController : UIViewController
@property (copy) void(^viewWillAppearBlock)(void);
@property (copy) void(^viewDidAppearBlock)(void);
@property (copy) void(^viewWillDisappearBlock)(void);
@property (copy) void(^viewDidDisappearBlock)(void);
@end
@implementation JXPagerListContainerViewController
- (void)dealloc
{
self.viewWillAppearBlock = nil;
self.viewDidAppearBlock = nil;
self.viewWillDisappearBlock = nil;
self.viewDidDisappearBlock = nil;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.viewWillAppearBlock();
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.viewDidAppearBlock();
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.viewWillDisappearBlock();
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
self.viewDidDisappearBlock();
}
- (BOOL)shouldAutomaticallyForwardAppearanceMethods { return NO; }
@end
@interface JXPagerListContainerView () <UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, weak) id<JXPagerListContainerViewDelegate> delegate;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict;
@property (nonatomic, assign) NSInteger willAppearIndex;
@property (nonatomic, assign) NSInteger willDisappearIndex;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) JXPagerListContainerViewController *containerVC;
@end
@implementation JXPagerListContainerView
- (instancetype)initWithType:(JXPagerListContainerType)type delegate:(id<JXPagerListContainerViewDelegate>)delegate{
self = [super initWithFrame:CGRectZero];
if (self) {
_containerType = type;
_delegate = delegate;
_validListDict = [NSMutableDictionary dictionary];
_willAppearIndex = -1;
_willDisappearIndex = -1;
_initListPercent = 0.01;
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_listCellBackgroundColor = [UIColor whiteColor];
_containerVC = [[JXPagerListContainerViewController alloc] init];
self.containerVC.view.backgroundColor = [UIColor clearColor];
[self addSubview:self.containerVC.view];
__weak typeof(self) weakSelf = self;
self.containerVC.viewWillAppearBlock = ^{
[weakSelf listWillAppear:weakSelf.currentIndex];
};
self.containerVC.viewDidAppearBlock = ^{
[weakSelf listDidAppear:weakSelf.currentIndex];
};
self.containerVC.viewWillDisappearBlock = ^{
[weakSelf listWillDisappear:weakSelf.currentIndex];
};
self.containerVC.viewDidDisappearBlock = ^{
[weakSelf listDidDisappear:weakSelf.currentIndex];
};
if (self.containerType == JXPagerListContainerType_ScrollView) {
if (self.delegate &&
[self.delegate respondsToSelector:@selector(scrollViewClassInlistContainerView:)] &&
[[self.delegate scrollViewClassInlistContainerView:self] isKindOfClass:object_getClass([UIScrollView class])]) {
_scrollView = (UIScrollView *)[[[self.delegate scrollViewClassInlistContainerView:self] alloc] init];
}else {
_scrollView = [[JXPagerListContainerScrollView alloc] init];
}
self.scrollView.backgroundColor = [UIColor clearColor];
self.scrollView.delegate = self;
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.scrollsToTop = NO;
self.scrollView.bounces = NO;
if (@available(iOS 11.0, *)) {
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self.containerVC.view addSubview:self.scrollView];
}else {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
if (self.delegate &&
[self.delegate respondsToSelector:@selector(scrollViewClassInlistContainerView:)] &&
[[self.delegate scrollViewClassInlistContainerView:self] isKindOfClass:object_getClass([UICollectionView class])]) {
_collectionView = (UICollectionView *)[[[self.delegate scrollViewClassInlistContainerView:self] alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
}else {
_collectionView = [[JXPagerListContainerCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
}
self.collectionView.backgroundColor = [UIColor clearColor];
self.collectionView.pagingEnabled = YES;
self.collectionView.showsHorizontalScrollIndicator = NO;
self.collectionView.showsVerticalScrollIndicator = NO;
self.collectionView.scrollsToTop = NO;
self.collectionView.bounces = NO;
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
if (@available(iOS 10.0, *)) {
self.collectionView.prefetchingEnabled = NO;
}
if (@available(iOS 11.0, *)) {
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self.containerVC.view addSubview:self.collectionView];
//访scrollView
_scrollView = _collectionView;
}
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
UIResponder *next = newSuperview;
while (next != nil) {
if ([next isKindOfClass:[UIViewController class]]) {
[((UIViewController *)next) addChildViewController:self.containerVC];
break;
}
next = next.nextResponder;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
self.containerVC.view.frame = self.bounds;
if (self.containerType == JXPagerListContainerType_ScrollView) {
if (CGRectEqualToRect(self.scrollView.frame, CGRectZero) || !CGSizeEqualToSize(self.scrollView.bounds.size, self.bounds.size)) {
self.scrollView.frame = self.bounds;
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
[_validListDict enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull index, id<JXPagerViewListViewDelegate> _Nonnull list, BOOL * _Nonnull stop) {
[list listView].frame = CGRectMake(index.intValue*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
}];
self.scrollView.contentOffset = CGPointMake(self.currentIndex*self.scrollView.bounds.size.width, 0);
}else {
self.scrollView.frame = self.bounds;
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
}
}else {
if (CGRectEqualToRect(self.collectionView.frame, CGRectZero) || !CGSizeEqualToSize(self.collectionView.bounds.size, self.bounds.size)) {
self.collectionView.frame = self.bounds;
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView reloadData];
[self.collectionView setContentOffset:CGPointMake(self.collectionView.bounds.size.width*self.currentIndex, 0) animated:NO];
}else {
self.collectionView.frame = self.bounds;
}
}
}
- (void)setinitListPercent:(CGFloat)initListPercent {
_initListPercent = initListPercent;
if (initListPercent <= 0 || initListPercent >= 1) {
NSAssert(NO, @"initListPercent值范围为开区间(0,1)即不包括0和1");
}
}
- (void)setCategoryNestPagingEnabled:(BOOL)categoryNestPagingEnabled {
_categoryNestPagingEnabled = categoryNestPagingEnabled;
if ([self.scrollView isKindOfClass:[JXPagerListContainerScrollView class]]) {
((JXPagerListContainerScrollView *)self.scrollView).categoryNestPagingEnabled = categoryNestPagingEnabled;
}else if ([self.scrollView isKindOfClass:[JXPagerListContainerCollectionView class]]) {
((JXPagerListContainerCollectionView *)self.scrollView).categoryNestPagingEnabled = categoryNestPagingEnabled;
}
}
#pragma mark - UICollectionViewDelegate, UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.delegate numberOfListsInlistContainerView:self];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.contentView.backgroundColor = self.listCellBackgroundColor;
for (UIView *subview in cell.contentView.subviews) {
[subview removeFromSuperview];
}
id<JXPagerViewListViewDelegate> list = _validListDict[@(indexPath.item)];
if (list != nil) {
//fixme:listUIViewControllerframe`[list listView].frame = cell.bounds;`list vc:
//- (void)loadView {
// self.view = [[UIView alloc] init];
//}
//UIViewControllerview使bug
if ([list isKindOfClass:[UIViewController class]]) {
[list listView].frame = cell.contentView.bounds;
} else {
[list listView].frame = cell.bounds;
}
[cell.contentView addSubview:[list listView]];
}
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.bounds.size;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewDidScroll:)]) {
[self.delegate listContainerViewDidScroll:scrollView];
}
if (!scrollView.isDragging && !scrollView.isTracking && !scrollView.isDecelerating) {
return;
}
CGFloat ratio = scrollView.contentOffset.x/scrollView.bounds.size.width;
NSInteger maxCount = round(scrollView.contentSize.width/scrollView.bounds.size.width);
NSInteger leftIndex = floorf(ratio);
leftIndex = MAX(0, MIN(maxCount - 1, leftIndex));
NSInteger rightIndex = leftIndex + 1;
if (ratio < 0 || rightIndex >= maxCount) {
[self listDidAppearOrDisappear:scrollView];
return;
}
CGFloat remainderRatio = ratio - leftIndex;
if (rightIndex == self.currentIndex) {
//
if (self.validListDict[@(leftIndex)] == nil && remainderRatio < (1 - self.initListPercent)) {
[self initListIfNeededAtIndex:leftIndex];
}else if (self.validListDict[@(leftIndex)] != nil) {
if (self.willAppearIndex == -1) {
self.willAppearIndex = leftIndex;
[self listWillAppear:self.willAppearIndex];
}
}
if (self.willDisappearIndex == -1) {
self.willDisappearIndex = rightIndex;
[self listWillDisappear:self.willDisappearIndex];
}
}else {
//
if (self.validListDict[@(rightIndex)] == nil && remainderRatio > self.initListPercent) {
[self initListIfNeededAtIndex:rightIndex];
}else if (self.validListDict[@(rightIndex)] != nil) {
if (self.willAppearIndex == -1) {
self.willAppearIndex = rightIndex;
[self listWillAppear:self.willAppearIndex];
}
}
if (self.willDisappearIndex == -1) {
self.willDisappearIndex = leftIndex;
[self listWillDisappear:self.willDisappearIndex];
}
}
[self listDidAppearOrDisappear:scrollView];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (self.willDisappearIndex != -1) {
[self listWillAppear:self.willDisappearIndex];
[self listWillDisappear:self.willAppearIndex];
[self listDidAppear:self.willDisappearIndex];
[self listDidDisappear:self.willAppearIndex];
self.willDisappearIndex = -1;
self.willAppearIndex = -1;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWDidEndScroll:)]) {
[self.delegate listContainerViewWDidEndScroll:self];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWillBeginDragging:)]) {
[self.delegate listContainerViewWillBeginDragging:self];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWDidEndScroll:)]) {
[self.delegate listContainerViewWDidEndScroll:self];
}
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWDidEndScroll:)]) {
[self.delegate listContainerViewWDidEndScroll:self];
}
}
#pragma mark - JXCategoryViewListContainer
- (UIScrollView *)contentScrollView {
return self.scrollView;
}
- (void)setDefaultSelectedIndex:(NSInteger)index {
self.currentIndex = index;
}
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio selectedIndex:(NSInteger)selectedIndex {
}
- (void)didClickSelectedItemAtIndex:(NSInteger)index {
if (![self checkIndexValid:index]) {
return;
}
self.willAppearIndex = -1;
self.willDisappearIndex = -1;
if (self.currentIndex != index) {
[self listWillDisappear:self.currentIndex];
[self listDidDisappear:self.currentIndex];
[self listWillAppear:index];
[self listDidAppear:index];
}
}
- (void)reloadData {
for (id<JXPagerViewListViewDelegate> list in _validListDict.allValues) {
[[list listView] removeFromSuperview];
if ([list isKindOfClass:[UIViewController class]]) {
[(UIViewController *)list removeFromParentViewController];
}
}
[_validListDict removeAllObjects];
if (self.containerType == JXPagerListContainerType_ScrollView) {
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
}else {
[self.collectionView reloadData];
}
[self listWillAppear:self.currentIndex];
[self listDidAppear:self.currentIndex];
}
#pragma mark - Private
- (void)initListIfNeededAtIndex:(NSInteger)index {
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerView:canInitListAtIndex:)]) {
BOOL canInitList = [self.delegate listContainerView:self canInitListAtIndex:index];
if (!canInitList) {
return;
}
}
id<JXPagerViewListViewDelegate> list = _validListDict[@(index)];
if (list != nil) {
//
return;
}
list = [self.delegate listContainerView:self initListForIndex:index];
if ([list isKindOfClass:[UIViewController class]]) {
[self.containerVC addChildViewController:(UIViewController *)list];
}
_validListDict[@(index)] = list;
switch (self.containerType) {
case JXPagerListContainerType_ScrollView: {
[list listView].frame = CGRectMake(index*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
[self.scrollView addSubview:[list listView]];
break;
}
case JXPagerListContainerType_CollectionView: {
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
if (cell != nil) {
for (UIView *subview in cell.contentView.subviews) {
[subview removeFromSuperview];
}
[list listView].frame = cell.contentView.bounds;
[cell.contentView addSubview:[list listView]];
}
break;
}
}
}
- (void)listWillAppear:(NSInteger)index {
if (![self checkIndexValid:index]) {
return;
}
id<JXPagerViewListViewDelegate> list = _validListDict[@(index)];
if (list != nil) {
if (list && [list respondsToSelector:@selector(listWillAppear)]) {
[list listWillAppear];
}
if ([list isKindOfClass:[UIViewController class]]) {
UIViewController *listVC = (UIViewController *)list;
[listVC beginAppearanceTransition:YES animated:NO];
}
}else {
//listWillAppear
BOOL canInitList = YES;
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerView:canInitListAtIndex:)]) {
canInitList = [self.delegate listContainerView:self canInitListAtIndex:index];
}
if (canInitList) {
id<JXPagerViewListViewDelegate> list = _validListDict[@(index)];
if (list == nil) {
list = [self.delegate listContainerView:self initListForIndex:index];
if ([list isKindOfClass:[UIViewController class]]) {
[self.containerVC addChildViewController:(UIViewController *)list];
}
_validListDict[@(index)] = list;
}
if (self.containerType == JXPagerListContainerType_ScrollView) {
if ([list listView].superview == nil) {
[list listView].frame = CGRectMake(index*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
[self.scrollView addSubview:[list listView]];
if (list && [list respondsToSelector:@selector(listWillAppear)]) {
[list listWillAppear];
}
if ([list isKindOfClass:[UIViewController class]]) {
UIViewController *listVC = (UIViewController *)list;
[listVC beginAppearanceTransition:YES animated:NO];
}
}
}else {
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
for (UIView *subview in cell.contentView.subviews) {
[subview removeFromSuperview];
}
[list listView].frame = cell.contentView.bounds;
[cell.contentView addSubview:[list listView]];
if (list && [list respondsToSelector:@selector(listWillAppear)]) {
[list listWillAppear];
}
if ([list isKindOfClass:[UIViewController class]]) {
UIViewController *listVC = (UIViewController *)list;
[listVC beginAppearanceTransition:YES animated:NO];
}
}
}
}
}
- (void)listDidAppear:(NSInteger)index {
if (![self checkIndexValid:index]) {
return;
}
self.currentIndex = index;
id<JXPagerViewListViewDelegate> list = _validListDict[@(index)];
if (list && [list respondsToSelector:@selector(listDidAppear)]) {
[list listDidAppear];
}
if ([list isKindOfClass:[UIViewController class]]) {
UIViewController *listVC = (UIViewController *)list;
[listVC endAppearanceTransition];
}
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerView:listDidAppearAtIndex:)]) {
[self.delegate listContainerView:self listDidAppearAtIndex:index];
}
}
- (void)listWillDisappear:(NSInteger)index {
if (![self checkIndexValid:index]) {
return;
}
id<JXPagerViewListViewDelegate> list = _validListDict[@(index)];
if (list && [list respondsToSelector:@selector(listWillDisappear)]) {
[list listWillDisappear];
}
if ([list isKindOfClass:[UIViewController class]]) {
UIViewController *listVC = (UIViewController *)list;
[listVC beginAppearanceTransition:NO animated:NO];
}
}
- (void)listDidDisappear:(NSInteger)index {
if (![self checkIndexValid:index]) {
return;
}
id<JXPagerViewListViewDelegate> list = _validListDict[@(index)];
if (list && [list respondsToSelector:@selector(listDidDisappear)]) {
[list listDidDisappear];
}
if ([list isKindOfClass:[UIViewController class]]) {
UIViewController *listVC = (UIViewController *)list;
[listVC endAppearanceTransition];
}
}
- (BOOL)checkIndexValid:(NSInteger)index {
NSUInteger count = [self.delegate numberOfListsInlistContainerView:self];
if (count <= 0 || index >= count) {
return NO;
}
return YES;
}
- (void)listDidAppearOrDisappear:(UIScrollView *)scrollView {
CGFloat currentIndexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width;
if (self.willAppearIndex != -1 || self.willDisappearIndex != -1) {
NSInteger disappearIndex = self.willDisappearIndex;
NSInteger appearIndex = self.willAppearIndex;
if (self.willAppearIndex > self.willDisappearIndex) {
//
if (currentIndexPercent >= self.willAppearIndex) {
self.willDisappearIndex = -1;
self.willAppearIndex = -1;
[self listDidDisappear:disappearIndex];
[self listDidAppear:appearIndex];
}
}else {
//
if (currentIndexPercent <= self.willAppearIndex) {
self.willDisappearIndex = -1;
self.willAppearIndex = -1;
[self listDidDisappear:disappearIndex];
[self listDidAppear:appearIndex];
}
}
}
}
@end

View File

@@ -0,0 +1,14 @@
//
// JXPagingListRefreshView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/28.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXPagerView.h"
@interface JXPagerListRefreshView : JXPagerView
@end

View File

@@ -0,0 +1,109 @@
//
// JXPagerListRefreshView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/28.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXPagerListRefreshView.h"
@interface JXPagerListRefreshView()
@property (nonatomic, assign) CGFloat lastScrollingListViewContentOffsetY;
@end
@implementation JXPagerListRefreshView
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate listContainerType:(JXPagerListContainerType)type {
self = [super initWithDelegate:delegate listContainerType:type];
if (self) {
self.mainTableView.bounces = NO;
}
return self;
}
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView {
BOOL shouldProcess = YES;
if (self.currentScrollingListView.contentOffset.y > self.lastScrollingListViewContentOffsetY) {
//
}else {
//
if (self.mainTableView.contentOffset.y == 0) {
shouldProcess = NO;
}else {
if (self.mainTableView.contentOffset.y < self.mainTableViewMaxContentOffsetY) {
//mainTableViewheaderlistScrollView0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
[self setListScrollViewToMinContentOffsetY:self.currentScrollingListView];
if (self.automaticallyDisplayListVerticalScrollIndicator) {
self.currentScrollingListView.showsVerticalScrollIndicator = NO;
}
}
}
}
if (shouldProcess) {
if (self.mainTableView.contentOffset.y < self.mainTableViewMaxContentOffsetY) {
//scrollView.contentOffset.y0
if (self.currentScrollingListView.contentOffset.y > [self minContentOffsetYInListScrollView:self.currentScrollingListView]) {
//mainTableViewheaderlistScrollView0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
[self setListScrollViewToMinContentOffsetY:self.currentScrollingListView];
if (self.automaticallyDisplayListVerticalScrollIndicator) {
self.currentScrollingListView.showsVerticalScrollIndicator = NO;
}
}
} else {
//mainTableViewheadermainTableViewlistScrollView
self.mainTableView.contentOffset = CGPointMake(0, self.mainTableViewMaxContentOffsetY);
if (self.automaticallyDisplayListVerticalScrollIndicator) {
self.currentScrollingListView.showsVerticalScrollIndicator = YES;
}
}
}
self.lastScrollingListViewContentOffsetY = self.currentScrollingListView.contentOffset.y;
}
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView {
if (self.pinSectionHeaderVerticalOffset != 0) {
if (!(self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > [self minContentOffsetYInListScrollView:self.currentScrollingListView])) {
//listView
if (scrollView.contentOffset.y <= 0) {
self.mainTableView.bounces = NO;
self.mainTableView.contentOffset = CGPointZero;
return;
}else {
self.mainTableView.bounces = YES;
}
}
}
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > [self minContentOffsetYInListScrollView:self.currentScrollingListView]) {
//mainTableViewheaderlistViewmainTableViewcontentOffset
[self setMainTableViewToMaxContentOffsetY];
}
if (scrollView.contentOffset.y < self.mainTableViewMaxContentOffsetY) {
//mainTableViewheaderlistViewcontentOffset
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
//
UIScrollView *listScrollView = [list listScrollView];
if (listScrollView.contentOffset.y > 0) {
if ([list respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[list listScrollViewWillResetContentOffset];
}
[self setListScrollViewToMinContentOffsetY:listScrollView];
}
}
}
if (scrollView.contentOffset.y > self.mainTableViewMaxContentOffsetY && self.currentScrollingListView.contentOffset.y == [self minContentOffsetYInListScrollView:self.currentScrollingListView]) {
//mainTableViewheaderViewlistView
[self setMainTableViewToMaxContentOffsetY];
}
}
@end

View File

@@ -0,0 +1,19 @@
//
// JXPagingMainTableView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol JXPagerMainTableViewGestureDelegate <NSObject>
- (BOOL)mainTableViewGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
@end
@interface JXPagerMainTableView : UITableView
@property (nonatomic, weak) id<JXPagerMainTableViewGestureDelegate> gestureDelegate;
@end

View File

@@ -0,0 +1,25 @@
//
// JXPagerMainTableView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXPagerMainTableView.h"
@interface JXPagerMainTableView ()<UIGestureRecognizerDelegate>
@end
@implementation JXPagerMainTableView
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (self.gestureDelegate && [self.gestureDelegate respondsToSelector:@selector(mainTableViewGestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
return [self.gestureDelegate mainTableViewGestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
}else {
return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}
}
@end

View File

@@ -0,0 +1,91 @@
//
// JXPagerSmoothView.h
// JXPagerViewExample-OC
//
// Created by jiaxin on 2019/11/15.
// Copyright © 2019 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXPagerSmoothView;
@protocol JXPagerSmoothViewListViewDelegate <NSObject>
/**
返回listView。如果是vc包裹的就是vc.view如果是自定义view包裹的就是自定义view自己。
*/
- (UIView *)listView;
/**
返回JXPagerSmoothViewListViewDelegate内部持有的UIScrollView或UITableView或UICollectionView
*/
- (UIScrollView *)listScrollView;
@optional
- (void)listDidAppear;
- (void)listDidDisappear;
@end
@protocol JXPagerSmoothViewDataSource <NSObject>
/**
返回页面header的高度
*/
- (CGFloat)heightForPagerHeaderInPagerView:(JXPagerSmoothView *)pagerView;
/**
返回页面header视图
*/
- (UIView *)viewForPagerHeaderInPagerView:(JXPagerSmoothView *)pagerView;
/**
返回悬浮视图的高度
*/
- (CGFloat)heightForPinHeaderInPagerView:(JXPagerSmoothView *)pagerView;
/**
返回悬浮视图
*/
- (UIView *)viewForPinHeaderInPagerView:(JXPagerSmoothView *)pagerView;
/**
返回列表的数量
*/
- (NSInteger)numberOfListsInPagerView:(JXPagerSmoothView *)pagerView;
/**
根据index初始化一个对应列表实例需要是遵从`JXPagerSmoothViewListViewDelegate`协议的对象。
如果列表是用自定义UIView封装的就让自定义UIView遵从`JXPagerSmoothViewListViewDelegate`协议该方法返回自定义UIView即可。
如果列表是用自定义UIViewController封装的就让自定义UIViewController遵从`JXPagerSmoothViewListViewDelegate`协议该方法返回自定义UIViewController即可。
@param pagerView pagerView description
@param index index description
@return 新生成的列表实例
*/
- (id<JXPagerSmoothViewListViewDelegate>)pagerView:(JXPagerSmoothView *)pagerView initListAtIndex:(NSInteger)index;
@end
@protocol JXPagerSmoothViewDelegate <NSObject>
- (void)pagerSmoothViewDidScroll:(UIScrollView *)scrollView;
@end
@interface JXPagerSmoothView : UIView
/**
当前已经加载过的列表key就是@(index)值value是对应的列表。
*/
@property (nonatomic, strong, readonly) NSDictionary <NSNumber *, id<JXPagerSmoothViewListViewDelegate>> *listDict;
@property (nonatomic, strong, readonly) UICollectionView *listCollectionView;
@property (nonatomic, assign) NSInteger defaultSelectedIndex;
@property (nonatomic, weak) id<JXPagerSmoothViewDelegate> delegate;
- (instancetype)initWithDataSource:(id<JXPagerSmoothViewDataSource>)dataSource NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)reloadData;
@end

View File

@@ -0,0 +1,361 @@
//
// JXPagerSmoothView.m
// JXPagerViewExample-OC
//
// Created by jiaxin on 2019/11/15.
// Copyright © 2019 jiaxin. All rights reserved.
//
#import "JXPagerSmoothView.h"
static NSString *JXPagerSmoothViewCollectionViewCellIdentifier = @"cell";
@interface JXPagerSmoothCollectionView : UICollectionView <UIGestureRecognizerDelegate>
@property (nonatomic, strong) UIView *pagerHeaderContainerView;
@end
@implementation JXPagerSmoothCollectionView
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint point = [touch locationInView:self.pagerHeaderContainerView];
if (CGRectContainsPoint(self.pagerHeaderContainerView.bounds, point)) {
return NO;
}
return YES;
}
@end
@interface JXPagerSmoothView () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, weak) id<JXPagerSmoothViewDataSource> dataSource;
@property (nonatomic, strong) JXPagerSmoothCollectionView *listCollectionView;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXPagerSmoothViewListViewDelegate>> *listDict;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, UIView*> *listHeaderDict;
@property (nonatomic, assign, getter=isSyncListContentOffsetEnabled) BOOL syncListContentOffsetEnabled;
@property (nonatomic, strong) UIView *pagerHeaderContainerView;
@property (nonatomic, assign) CGFloat currentPagerHeaderContainerViewY;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, strong) UIScrollView *currentListScrollView;
@property (nonatomic, assign) CGFloat heightForPagerHeader;
@property (nonatomic, assign) CGFloat heightForPinHeader;
@property (nonatomic, assign) CGFloat heightForPagerHeaderContainerView;
@property (nonatomic, assign) CGFloat currentListInitializeContentOffsetY;
@property (nonatomic, strong) UIScrollView *singleScrollView;
@end
@implementation JXPagerSmoothView
- (void)dealloc
{
for (id<JXPagerSmoothViewListViewDelegate> list in self.listDict.allValues) {
[[list listScrollView] removeObserver:self forKeyPath:@"contentOffset"];
[[list listScrollView] removeObserver:self forKeyPath:@"contentSize"];
}
}
- (instancetype)initWithDataSource:(id<JXPagerSmoothViewDataSource>)dataSource
{
self = [super initWithFrame:CGRectZero];
if (self) {
_dataSource = dataSource;
_listDict = [NSMutableDictionary dictionary];
_listHeaderDict = [NSMutableDictionary dictionary];
[self initializeViews];
}
return self;
}
- (void)initializeViews {
self.pagerHeaderContainerView = [[UIView alloc] init];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
_listCollectionView = [[JXPagerSmoothCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
self.listCollectionView.dataSource = self;
self.listCollectionView.delegate = self;
self.listCollectionView.pagingEnabled = YES;
self.listCollectionView.bounces = NO;
self.listCollectionView.showsHorizontalScrollIndicator = NO;
self.listCollectionView.scrollsToTop = NO;
[self.listCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:JXPagerSmoothViewCollectionViewCellIdentifier];
if (@available(iOS 10.0, *)) {
self.listCollectionView.prefetchingEnabled = NO;
}
if (@available(iOS 11.0, *)) {
self.listCollectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
_listCollectionView.pagerHeaderContainerView = self.pagerHeaderContainerView;
[self addSubview:self.listCollectionView];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.listCollectionView.frame = self.bounds;
if (CGRectEqualToRect(self.pagerHeaderContainerView.frame, CGRectZero)) {
[self reloadData];
}
if (self.singleScrollView != nil) {
self.singleScrollView.frame = self.bounds;
}
}
- (void)reloadData {
self.currentListScrollView = nil;
self.currentIndex = self.defaultSelectedIndex;
self.currentPagerHeaderContainerViewY = 0;
self.syncListContentOffsetEnabled = NO;
[self.listHeaderDict removeAllObjects];
for (id<JXPagerSmoothViewListViewDelegate> list in self.listDict.allValues) {
[[list listScrollView] removeObserver:self forKeyPath:@"contentOffset"];
[[list listScrollView] removeObserver:self forKeyPath:@"contentSize"];
[[list listView] removeFromSuperview];
}
[_listDict removeAllObjects];
self.heightForPagerHeader = [self.dataSource heightForPagerHeaderInPagerView:self];
self.heightForPinHeader = [self.dataSource heightForPinHeaderInPagerView:self];
self.heightForPagerHeaderContainerView = self.heightForPagerHeader + self.heightForPinHeader;
UIView *pagerHeader = [self.dataSource viewForPagerHeaderInPagerView:self];
UIView *pinHeader = [self.dataSource viewForPinHeaderInPagerView:self];
[self.pagerHeaderContainerView addSubview:pagerHeader];
[self.pagerHeaderContainerView addSubview:pinHeader];
self.pagerHeaderContainerView.frame = CGRectMake(0, 0, self.bounds.size.width, self.heightForPagerHeaderContainerView);
pagerHeader.frame = CGRectMake(0, 0, self.bounds.size.width, self.heightForPagerHeader);
pinHeader.frame = CGRectMake(0, self.heightForPagerHeader, self.bounds.size.width, self.heightForPinHeader);
[self.listCollectionView setContentOffset:CGPointMake(self.listCollectionView.bounds.size.width*self.defaultSelectedIndex, 0) animated:NO];
[self.listCollectionView reloadData];
if ([self.dataSource numberOfListsInPagerView:self] == 0) {
self.singleScrollView = [[UIScrollView alloc] init];
[self addSubview:self.singleScrollView];
[self.singleScrollView addSubview:pagerHeader];
self.singleScrollView.contentSize = CGSizeMake(self.bounds.size.width, self.heightForPagerHeader);
}else if (self.singleScrollView != nil) {
[self.singleScrollView removeFromSuperview];
self.singleScrollView = nil;
}
}
#pragma mark - UICollectionViewDataSource & UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.bounds.size;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.dataSource numberOfListsInPagerView:self];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:JXPagerSmoothViewCollectionViewCellIdentifier forIndexPath:indexPath];
id<JXPagerSmoothViewListViewDelegate> list = self.listDict[@(indexPath.item)];
if (list == nil) {
list = [self.dataSource pagerView:self initListAtIndex:indexPath.item];
_listDict[@(indexPath.item)] = list;
[[list listView] setNeedsLayout];
[[list listView] layoutIfNeeded];
UIScrollView *listScrollView = [list listScrollView];
if ([listScrollView isKindOfClass:[UITableView class]]) {
((UITableView *)listScrollView).estimatedRowHeight = 0;
((UITableView *)listScrollView).estimatedSectionFooterHeight = 0;
((UITableView *)listScrollView).estimatedSectionHeaderHeight = 0;
}
if (@available(iOS 11.0, *)) {
listScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
listScrollView.contentInset = UIEdgeInsetsMake(self.heightForPagerHeaderContainerView, 0, 0, 0);
self.currentListInitializeContentOffsetY = -listScrollView.contentInset.top + MIN(-self.currentPagerHeaderContainerViewY, self.heightForPagerHeader);
listScrollView.contentOffset = CGPointMake(0, self.currentListInitializeContentOffsetY);
UIView *listHeader = [[UIView alloc] initWithFrame:CGRectMake(0, -self.heightForPagerHeaderContainerView, self.bounds.size.width, self.heightForPagerHeaderContainerView)];
[listScrollView addSubview:listHeader];
if (self.pagerHeaderContainerView.superview == nil) {
[listHeader addSubview:self.pagerHeaderContainerView];
}
self.listHeaderDict[@(indexPath.item)] = listHeader;
[listScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
[listScrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
}
for (id<JXPagerSmoothViewListViewDelegate> listItem in self.listDict.allValues) {
[listItem listScrollView].scrollsToTop = (listItem == list);
}
UIView *listView = [list listView];
if (listView != nil && listView.superview != cell.contentView) {
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
listView.frame = cell.contentView.bounds;
[cell.contentView addSubview:listView];
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[self listDidAppear:indexPath.item];
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[self listDidDisappear:indexPath.item];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerSmoothViewDidScroll:)]) {
[self.delegate pagerSmoothViewDidScroll:scrollView];
}
CGFloat indexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width;
NSInteger index = floor(indexPercent);
UIScrollView *listScrollView = [self.listDict[@(index)] listScrollView];
if (indexPercent - index == 0 && index != self.currentIndex && !(scrollView.isDragging || scrollView.isDecelerating) && listScrollView.contentOffset.y <= -self.heightForPinHeader) {
[self horizontalScrollDidEndAtIndex:index];
}else {
//listHeaderContainerViewself
if (self.pagerHeaderContainerView.superview != self) {
self.pagerHeaderContainerView.frame = CGRectMake(0, self.currentPagerHeaderContainerViewY, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height);
[self addSubview:self.pagerHeaderContainerView];
}
}
if (index != self.currentIndex) {
self.currentIndex = index;
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
NSInteger index = scrollView.contentOffset.x/scrollView.bounds.size.width;
[self horizontalScrollDidEndAtIndex:index];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSInteger index = scrollView.contentOffset.x/scrollView.bounds.size.width;
[self horizontalScrollDidEndAtIndex:index];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"contentOffset"]) {
UIScrollView *scrollView = (UIScrollView *)object;
if (scrollView != nil) {
[self listDidScroll:scrollView];
}
}else if([keyPath isEqualToString:@"contentSize"]) {
UIScrollView *scrollView = (UIScrollView *)object;
if (scrollView != nil) {
CGFloat minContentSizeHeight = self.bounds.size.height - self.heightForPinHeader;
if (minContentSizeHeight > scrollView.contentSize.height) {
scrollView.contentSize = CGSizeMake(scrollView.contentSize.width, minContentSizeHeight);
//scrollViewcontentOffset
if (_currentListScrollView != nil && scrollView != _currentListScrollView) {
scrollView.contentOffset = CGPointMake(0, self.currentListInitializeContentOffsetY);
}
}
}
}else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark - Event
- (void)listDidScroll:(UIScrollView *)scrollView {
if (self.listCollectionView.isDragging || self.listCollectionView.isDecelerating) {
return;
}
NSInteger listIndex = [self listIndexForListScrollView:scrollView];
if (listIndex != self.currentIndex) {
return;
}
self.currentListScrollView = scrollView;
CGFloat contentOffsetY = scrollView.contentOffset.y + self.heightForPagerHeaderContainerView;
if (contentOffsetY < self.heightForPagerHeader) {
self.syncListContentOffsetEnabled = YES;
self.currentPagerHeaderContainerViewY = -contentOffsetY;
for (id<JXPagerSmoothViewListViewDelegate> list in self.listDict.allValues) {
if ([list listScrollView] != self.currentListScrollView) {
[[list listScrollView] setContentOffset:scrollView.contentOffset animated:NO];
}
}
UIView *listHeader = [self listHeaderForListScrollView:scrollView];
if (self.pagerHeaderContainerView.superview != listHeader) {
self.pagerHeaderContainerView.frame = CGRectMake(0, 0, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height);
[listHeader addSubview:self.pagerHeaderContainerView];
}
}else {
if (self.pagerHeaderContainerView.superview != self) {
self.pagerHeaderContainerView.frame = CGRectMake(0, -self.heightForPagerHeader, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height);
[self addSubview:self.pagerHeaderContainerView];
}
if (self.isSyncListContentOffsetEnabled) {
self.syncListContentOffsetEnabled = NO;
self.currentPagerHeaderContainerViewY = -self.heightForPagerHeader;
for (id<JXPagerSmoothViewListViewDelegate> list in self.listDict.allValues) {
if ([list listScrollView] != scrollView) {
[[list listScrollView] setContentOffset:CGPointMake(0, -self.heightForPinHeader) animated:NO];
}
}
}
}
}
#pragma mark - Private
- (UIView *)listHeaderForListScrollView:(UIScrollView *)scrollView {
for (NSNumber *index in self.listDict) {
if ([self.listDict[index] listScrollView] == scrollView) {
return self.listHeaderDict[index];
}
}
return nil;
}
- (NSInteger)listIndexForListScrollView:(UIScrollView *)scrollView {
for (NSNumber *index in self.listDict) {
if ([self.listDict[index] listScrollView] == scrollView) {
return [index integerValue];
}
}
return 0;
}
- (void)listDidAppear:(NSInteger)index {
NSUInteger count = [self.dataSource numberOfListsInPagerView:self];
if (count <= 0 || index >= count) {
return;
}
id<JXPagerSmoothViewListViewDelegate> list = self.listDict[@(index)];
if (list && [list respondsToSelector:@selector(listDidAppear)]) {
[list listDidAppear];
}
}
- (void)listDidDisappear:(NSInteger)index {
NSUInteger count = [self.dataSource numberOfListsInPagerView:self];
if (count <= 0 || index >= count) {
return;
}
id<JXPagerSmoothViewListViewDelegate> list = self.listDict[@(index)];
if (list && [list respondsToSelector:@selector(listDidDisappear)]) {
[list listDidDisappear];
}
}
/// pagerHeaderContainerViewindex
- (void)horizontalScrollDidEndAtIndex:(NSInteger)index {
self.currentIndex = index;
UIView *listHeader = self.listHeaderDict[@(index)];
UIScrollView *listScrollView = [self.listDict[@(index)] listScrollView];
if (listHeader != nil && listScrollView.contentOffset.y <= -self.heightForPinHeader) {
for (id<JXPagerSmoothViewListViewDelegate> listItem in self.listDict.allValues) {
[listItem listScrollView].scrollsToTop = ([listItem listScrollView] == listScrollView);
}
self.pagerHeaderContainerView.frame = CGRectMake(0, 0, self.pagerHeaderContainerView.bounds.size.width, self.pagerHeaderContainerView.bounds.size.height);
[listHeader addSubview:self.pagerHeaderContainerView];
}
}
@end

View File

@@ -0,0 +1,131 @@
//
// JXPagerView.h
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXPagerMainTableView.h"
#import "JXPagerListContainerView.h"
@class JXPagerView;
@protocol JXPagerViewDelegate <NSObject>
/**
返回tableHeaderView的高度因为内部需要比对判断只能是整型数
*/
- (NSUInteger)tableHeaderViewHeightInPagerView:(JXPagerView *)pagerView;
/**
返回tableHeaderView
*/
- (UIView *)tableHeaderViewInPagerView:(JXPagerView *)pagerView;
/**
返回悬浮HeaderView的高度因为内部需要比对判断只能是整型数
*/
- (NSUInteger)heightForPinSectionHeaderInPagerView:(JXPagerView *)pagerView;
/**
返回悬浮HeaderView。我用的是自己封装的JXCategoryViewGithub:https://github.com/pujiaxin33/JXCategoryView你也可以选择其他的三方库或者自己写
*/
- (UIView *)viewForPinSectionHeaderInPagerView:(JXPagerView *)pagerView;
/**
返回列表的数量
*/
- (NSInteger)numberOfListsInPagerView:(JXPagerView *)pagerView;
/**
根据index初始化一个对应列表实例需要是遵从`JXPagerViewListViewDelegate`协议的对象。
如果列表是用自定义UIView封装的就让自定义UIView遵从`JXPagerViewListViewDelegate`协议该方法返回自定义UIView即可。
如果列表是用自定义UIViewController封装的就让自定义UIViewController遵从`JXPagerViewListViewDelegate`协议该方法返回自定义UIViewController即可。
注意:一定要是新生成的实例!!!
@param pagerView pagerView description
@param index index description
@return 新生成的列表实例
*/
- (id<JXPagerViewListViewDelegate>)pagerView:(JXPagerView *)pagerView initListAtIndex:(NSInteger)index;
@optional
/// 返回对应index的列表唯一标识
/// @param pagerView pagerView description
/// @param index index description
- (NSString *)pagerView:(JXPagerView *)pagerView listIdentifierAtIndex:(NSInteger)index;
- (void)mainTableViewDidScroll:(UIScrollView *)scrollView __attribute__ ((deprecated));
- (void)pagerView:(JXPagerView *)pagerView mainTableViewDidScroll:(UIScrollView *)scrollView;
- (void)pagerView:(JXPagerView *)pagerView mainTableViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)pagerView:(JXPagerView *)pagerView mainTableViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)pagerView:(JXPagerView *)pagerView mainTableViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)pagerView:(JXPagerView *)pagerView mainTableViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
/**
返回自定义UIScrollView或UICollectionView的Class
某些特殊情况需要自己处理列表容器内UIScrollView内部逻辑。比如项目用了FDFullscreenPopGesture需要处理手势相关代理。
@param pagerView JXPagerView
@return 自定义UIScrollView实例
*/
- (Class)scrollViewClassInlistContainerViewInPagerView:(JXPagerView *)pagerView;
@end
@interface JXPagerView : UIView
/**
需要和self.categoryView.defaultSelectedIndex保持一致
*/
@property (nonatomic, assign) NSInteger defaultSelectedIndex;
@property (nonatomic, strong, readonly) JXPagerMainTableView *mainTableView;
@property (nonatomic, strong, readonly) JXPagerListContainerView *listContainerView;
/**
当前已经加载过可用的列表字典key就是index值value是对应的列表。
*/
@property (nonatomic, strong, readonly) NSDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict;
/**
顶部固定sectionHeader的垂直偏移量。数值越大越往下沉。
*/
@property (nonatomic, assign) NSInteger pinSectionHeaderVerticalOffset;
/**
是否允许列表左右滑动。默认YES
*/
@property (nonatomic, assign) BOOL isListHorizontalScrollEnabled;
/**
是否允许当前列表自动显示或隐藏列表是垂直滚动指示器。YES悬浮的headerView滚动到顶部开始滚动列表时就会显示反之隐藏。NO内部不会处理列表的垂直滚动指示器。默认为YES。
*/
@property (nonatomic, assign) BOOL automaticallyDisplayListVerticalScrollIndicator;
/**
当allowsCacheList为true时请务必实现代理方法`- (NSString *)pagerView:(JXPagerView *)pagerView listIdentifierAtIndex:(NSInteger)index`
*/
@property (nonatomic, assign) BOOL allowsCacheList;
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate;
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate listContainerType:(JXPagerListContainerType)type NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)reloadData;
- (void)resizeTableHeaderViewHeightWithAnimatable:(BOOL)animatable duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve;
@end
/**
暴露给子类使用,请勿直接使用相关属性和方法!
*/
@interface JXPagerView (UISubclassingGet)
@property (nonatomic, strong, readonly) UIScrollView *currentScrollingListView;
@property (nonatomic, strong, readonly) id<JXPagerViewListViewDelegate> currentList;
@property (nonatomic, assign, readonly) CGFloat mainTableViewMaxContentOffsetY;
@end
@interface JXPagerView (UISubclassingHooks)
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView;
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView;
- (void)setMainTableViewToMaxContentOffsetY;
- (void)setListScrollViewToMinContentOffsetY:(UIScrollView *)scrollView;
- (CGFloat)minContentOffsetYInListScrollView:(UIScrollView *)scrollView;
@end

View File

@@ -0,0 +1,409 @@
//
// JXPagerView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXPagerView.h"
@class JXPagerListContainerScrollView;
@class JXPagerListContainerCollectionView;
@interface JXPagerView () <UITableViewDataSource, UITableViewDelegate, JXPagerListContainerViewDelegate>
@property (nonatomic, weak) id<JXPagerViewDelegate> delegate;
@property (nonatomic, strong) JXPagerMainTableView *mainTableView;
@property (nonatomic, strong) JXPagerListContainerView *listContainerView;
@property (nonatomic, strong) UIScrollView *currentScrollingListView;
@property (nonatomic, strong) id<JXPagerViewListViewDelegate> currentList;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict;
@property (nonatomic, strong) UIView *tableHeaderContainerView;
@property (nonatomic, strong) NSMutableDictionary<NSString *, id<JXPagerViewListViewDelegate>> *listCache;
@end
@implementation JXPagerView
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate {
return [self initWithDelegate:delegate listContainerType:JXPagerListContainerType_CollectionView];
}
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate listContainerType:(JXPagerListContainerType)type {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
_validListDict = [NSMutableDictionary dictionary];
_automaticallyDisplayListVerticalScrollIndicator = YES;
_isListHorizontalScrollEnabled = YES;
_mainTableView = [[JXPagerMainTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.mainTableView.showsVerticalScrollIndicator = NO;
self.mainTableView.showsHorizontalScrollIndicator = NO;
self.mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.mainTableView.scrollsToTop = NO;
self.mainTableView.dataSource = self;
self.mainTableView.delegate = self;
[self refreshTableHeaderView];
[self.mainTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
if (@available(iOS 11.0, *)) {
self.mainTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
if (@available(iOS 15.0, *)) {
self.mainTableView.sectionHeaderTopPadding = 0;
}
#endif
[self addSubview:self.mainTableView];
_listContainerView = [[JXPagerListContainerView alloc] initWithType:type delegate:self];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (!CGRectEqualToRect(self.bounds, self.mainTableView.frame)) {
self.mainTableView.frame = self.bounds;
[self.mainTableView reloadData];
}
}
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
_defaultSelectedIndex = defaultSelectedIndex;
self.listContainerView.defaultSelectedIndex = defaultSelectedIndex;
}
- (void)setIsListHorizontalScrollEnabled:(BOOL)isListHorizontalScrollEnabled {
_isListHorizontalScrollEnabled = isListHorizontalScrollEnabled;
self.listContainerView.scrollView.scrollEnabled = isListHorizontalScrollEnabled;
}
- (void)reloadData {
self.currentList = nil;
self.currentScrollingListView = nil;
[_validListDict removeAllObjects];
//list
if (self.allowsCacheList) {
NSMutableArray *newListIdentifierArray = [NSMutableArray array];
if (self.delegate && [self.delegate respondsToSelector:@selector(numberOfListsInPagerView:)]) {
NSInteger listCount = [self.delegate numberOfListsInPagerView:self];
for (NSInteger index = 0; index < listCount; index ++) {
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:listIdentifierAtIndex:)]) {
NSString *listIdentifier = [self.delegate pagerView:self listIdentifierAtIndex:index];
[newListIdentifierArray addObject:listIdentifier];
}
}
}
NSArray *existedKeys = self.listCache.allKeys;
for (NSString *listIdentifier in existedKeys) {
if (![newListIdentifierArray containsObject:listIdentifier]) {
[self.listCache removeObjectForKey:listIdentifier];
}
}
}
[self refreshTableHeaderView];
if (self.pinSectionHeaderVerticalOffset != 0 && self.mainTableView.contentOffset.y > self.pinSectionHeaderVerticalOffset) {
self.mainTableView.contentOffset = CGPointZero;
}
[self.mainTableView reloadData];
[self.listContainerView reloadData];
}
- (void)resizeTableHeaderViewHeightWithAnimatable:(BOOL)animatable duration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
if (animatable) {
UIViewAnimationOptions options = UIViewAnimationOptionCurveLinear;
switch (curve) {
case UIViewAnimationCurveEaseIn: options = UIViewAnimationOptionCurveEaseIn; break;
case UIViewAnimationCurveEaseOut: options = UIViewAnimationOptionCurveEaseOut; break;
case UIViewAnimationCurveEaseInOut: options = UIViewAnimationOptionCurveEaseInOut; break;
default: break;
}
[UIView animateWithDuration:duration delay:0 options:options animations:^{
CGRect frame = self.tableHeaderContainerView.bounds;
frame.size.height = [self.delegate tableHeaderViewHeightInPagerView:self];
self.tableHeaderContainerView.frame = frame;
self.mainTableView.tableHeaderView = self.tableHeaderContainerView;
[self.mainTableView setNeedsLayout];
[self.mainTableView layoutIfNeeded];
} completion:^(BOOL finished) { }];
}else {
CGRect frame = self.tableHeaderContainerView.bounds;
frame.size.height = [self.delegate tableHeaderViewHeightInPagerView:self];
self.tableHeaderContainerView.frame = frame;
self.mainTableView.tableHeaderView = self.tableHeaderContainerView;
}
}
#pragma mark - Private
- (void)refreshTableHeaderView {
UIView *tableHeaderView = [self.delegate tableHeaderViewInPagerView:self];
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, [self.delegate tableHeaderViewHeightInPagerView:self])];
[containerView addSubview:tableHeaderView];
tableHeaderView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTop multiplier:1 constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:tableHeaderView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
[containerView addConstraints:@[top, leading, bottom, trailing]];
self.tableHeaderContainerView = containerView;
self.mainTableView.tableHeaderView = containerView;
}
- (void)adjustMainScrollViewToTargetContentInsetIfNeeded:(UIEdgeInsets)insets {
if (UIEdgeInsetsEqualToEdgeInsets(insets, self.mainTableView.contentInset) == NO) {
self.mainTableView.delegate = nil;
self.mainTableView.contentInset = insets;
self.mainTableView.delegate = self;
}
}
- (void)listViewDidScroll:(UIScrollView *)scrollView {
self.currentScrollingListView = scrollView;
[self preferredProcessListViewDidScroll:scrollView];
}
//pinSectionHeaderVerticalOffsetMJRefreshJXPagingViewMJRefreshcontentInsetpinSectionHeaderVerticalOffsetcontentInset.top
//https://github.com/pujiaxin33/JXPagingView/issues/203
- (BOOL)isSetMainScrollViewContentInsetToZeroEnabled:(UIScrollView *)scrollView {
//scrollView.contentInset.top0scrollView.contentInset.toppinSectionHeaderVerticalOffsetpinSectionHeaderVerticalOffsetMJRefreshmj_insetT
BOOL isRefreshing = scrollView.contentInset.top != 0 && scrollView.contentInset.top != self.pinSectionHeaderVerticalOffset;
return !isRefreshing;
}
#pragma mark - UITableViewDataSource, UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return MAX(self.bounds.size.height - [self.delegate heightForPinSectionHeaderInPagerView:self] - self.pinSectionHeaderVerticalOffset, 0);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor clearColor];
if (self.listContainerView.superview != cell.contentView) {
[cell.contentView addSubview:self.listContainerView];
}
if (!CGRectEqualToRect(self.listContainerView.frame, cell.bounds)) {
self.listContainerView.frame = cell.bounds;
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return [self.delegate heightForPinSectionHeaderInPagerView:self];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return [self.delegate viewForPinSectionHeaderInPagerView:self];
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 1;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *footer = [[UIView alloc] initWithFrame:CGRectZero];
footer.backgroundColor = [UIColor clearColor];
return footer;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.pinSectionHeaderVerticalOffset != 0) {
if (!(self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > [self minContentOffsetYInListScrollView:self.currentScrollingListView])) {
//listView
if (scrollView.contentOffset.y >= self.pinSectionHeaderVerticalOffset) {
//contentInset.top
[self adjustMainScrollViewToTargetContentInsetIfNeeded:UIEdgeInsetsMake(self.pinSectionHeaderVerticalOffset, 0, 0, 0)];
}else {
if ([self isSetMainScrollViewContentInsetToZeroEnabled:scrollView]) {
[self adjustMainScrollViewToTargetContentInsetIfNeeded:UIEdgeInsetsZero];
}
}
}
}
[self preferredProcessMainTableViewDidScroll:scrollView];
if (self.delegate && [self.delegate respondsToSelector:@selector(mainTableViewDidScroll:)]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[self.delegate mainTableViewDidScroll:scrollView];
#pragma GCC diagnostic pop
}
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidScroll:)]) {
[self.delegate pagerView:self mainTableViewDidScroll:scrollView];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.listContainerView.scrollView.scrollEnabled = NO;
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewWillBeginDragging:)]) {
[self.delegate pagerView:self mainTableViewWillBeginDragging:scrollView];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (self.isListHorizontalScrollEnabled && !decelerate) {
self.listContainerView.scrollView.scrollEnabled = YES;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidEndDragging:willDecelerate:)]) {
[self.delegate pagerView:self mainTableViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.scrollView.scrollEnabled = YES;
}
if ([self isSetMainScrollViewContentInsetToZeroEnabled:scrollView]) {
if (self.mainTableView.contentInset.top != 0 && self.pinSectionHeaderVerticalOffset != 0) {
[self adjustMainScrollViewToTargetContentInsetIfNeeded:UIEdgeInsetsZero];
}
}
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidEndDecelerating:)]) {
[self.delegate pagerView:self mainTableViewDidEndDecelerating:scrollView];
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.scrollView.scrollEnabled = YES;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(pagerView:mainTableViewDidEndScrollingAnimation:)]) {
[self.delegate pagerView:self mainTableViewDidEndScrollingAnimation:scrollView];
}
}
#pragma mark - JXPagerListContainerViewDelegate
- (NSInteger)numberOfListsInlistContainerView:(JXPagerListContainerView *)listContainerView {
return [self.delegate numberOfListsInPagerView:self];
}
- (id<JXPagerViewListViewDelegate>)listContainerView:(JXPagerListContainerView *)listContainerView initListForIndex:(NSInteger)index {
id<JXPagerViewListViewDelegate> list = self.validListDict[@(index)];
if (list == nil) {
if (self.allowsCacheList && self.delegate && [self.delegate respondsToSelector:@selector(pagerView:listIdentifierAtIndex:)]) {
NSString *listIdentifier = [self.delegate pagerView:self listIdentifierAtIndex:index];
list = self.listCache[listIdentifier];
}
}
if (list == nil) {
list = [self.delegate pagerView:self initListAtIndex:index];
__weak typeof(self)weakSelf = self;
__weak typeof(id<JXPagerViewListViewDelegate>) weakList = list;
[list listViewDidScrollCallback:^(UIScrollView *scrollView) {
weakSelf.currentList = weakList;
[weakSelf listViewDidScroll:scrollView];
}];
_validListDict[@(index)] = list;
if (self.allowsCacheList && self.delegate && [self.delegate respondsToSelector:@selector(pagerView:listIdentifierAtIndex:)]) {
NSString *listIdentifier = [self.delegate pagerView:self listIdentifierAtIndex:index];
self.listCache[listIdentifier] = list;
}
}
return list;
}
- (void)listContainerViewWillBeginDragging:(JXPagerListContainerView *)listContainerView {
self.mainTableView.scrollEnabled = NO;
}
- (void)listContainerViewWDidEndScroll:(JXPagerListContainerView *)listContainerView {
self.mainTableView.scrollEnabled = YES;
}
- (void)listContainerView:(JXPagerListContainerView *)listContainerView listDidAppearAtIndex:(NSInteger)index {
self.currentScrollingListView = [self.validListDict[@(index)] listScrollView];
for (id<JXPagerViewListViewDelegate> listItem in self.validListDict.allValues) {
if (listItem == self.validListDict[@(index)]) {
[listItem listScrollView].scrollsToTop = YES;
}else {
[listItem listScrollView].scrollsToTop = NO;
}
}
}
- (Class)scrollViewClassInlistContainerView:(JXPagerListContainerView *)listContainerView {
if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewClassInlistContainerViewInPagerView:)]) {
return [self.delegate scrollViewClassInlistContainerViewInPagerView:self];
}
return nil;
}
@end
@implementation JXPagerView (UISubclassingGet)
- (CGFloat)mainTableViewMaxContentOffsetY {
return [self.delegate tableHeaderViewHeightInPagerView:self] - self.pinSectionHeaderVerticalOffset;
}
@end
@implementation JXPagerView (UISubclassingHooks)
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView {
if (self.mainTableView.contentOffset.y < self.mainTableViewMaxContentOffsetY) {
//mainTableViewheaderlistScrollView0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
[self setListScrollViewToMinContentOffsetY:scrollView];
if (self.automaticallyDisplayListVerticalScrollIndicator) {
scrollView.showsVerticalScrollIndicator = NO;
}
}else {
//mainTableViewheadermainTableViewlistScrollView
self.mainTableView.contentOffset = CGPointMake(0, self.mainTableViewMaxContentOffsetY);
if (self.automaticallyDisplayListVerticalScrollIndicator) {
scrollView.showsVerticalScrollIndicator = YES;
}
}
}
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView {
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > [self minContentOffsetYInListScrollView:self.currentScrollingListView]) {
//mainTableViewheaderlistViewmainTableViewcontentOffset
[self setMainTableViewToMaxContentOffsetY];
}
if (scrollView.contentOffset.y < self.mainTableViewMaxContentOffsetY) {
//mainTableViewheaderlistViewcontentOffset
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
if ([list respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[list listScrollViewWillResetContentOffset];
}
[self setListScrollViewToMinContentOffsetY:[list listScrollView]];
}
}
if (scrollView.contentOffset.y > self.mainTableViewMaxContentOffsetY && self.currentScrollingListView.contentOffset.y == [self minContentOffsetYInListScrollView:self.currentScrollingListView]) {
//mainTableViewheaderViewlistView
[self setMainTableViewToMaxContentOffsetY];
}
}
- (void)setMainTableViewToMaxContentOffsetY {
self.mainTableView.contentOffset = CGPointMake(0, self.mainTableViewMaxContentOffsetY);
}
- (void)setListScrollViewToMinContentOffsetY:(UIScrollView *)scrollView {
scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, [self minContentOffsetYInListScrollView:scrollView]);
}
- (CGFloat)minContentOffsetYInListScrollView:(UIScrollView *)scrollView {
if (@available(iOS 11.0, *)) {
return -scrollView.adjustedContentInset.top;
}
return -scrollView.contentInset.top;
}
@end

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>