This commit is contained in:
2025-11-06 19:19:12 +08:00
parent a75afbe4c1
commit 6ba1339c0b
195 changed files with 13443 additions and 2729 deletions

View File

@@ -0,0 +1,24 @@
//
// JXCategoryCollectionView.h
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryIndicatorProtocol.h"
@class JXCategoryCollectionView;
@protocol JXCategoryCollectionViewGestureDelegate <NSObject>
@optional
- (BOOL)categoryCollectionView:(JXCategoryCollectionView *)collectionView gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
- (BOOL)categoryCollectionView:(JXCategoryCollectionView *)collectionView gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
@end
@interface JXCategoryCollectionView : UICollectionView
@property (nonatomic, strong) NSArray <UIView<JXCategoryIndicatorProtocol> *> *indicators;
@property (nonatomic, weak) id<JXCategoryCollectionViewGestureDelegate> gestureDelegate;
@end

View File

@@ -0,0 +1,52 @@
//
// JXCategoryCollectionView.m
// UI
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryCollectionView.h"
@interface JXCategoryCollectionView ()<UIGestureRecognizerDelegate>
@end
@implementation JXCategoryCollectionView
- (void)setIndicators:(NSArray<UIView<JXCategoryIndicatorProtocol> *> *)indicators {
for (UIView *indicator in _indicators) {
//indicator
[indicator removeFromSuperview];
}
_indicators = indicators;
for (UIView *indicator in indicators) {
[self addSubview:indicator];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
for (UIView<JXCategoryIndicatorProtocol> *view in self.indicators) {
[self sendSubviewToBack:view];
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.gestureDelegate && [self.gestureDelegate respondsToSelector:@selector(categoryCollectionView:gestureRecognizerShouldBegin:)]) {
return [self.gestureDelegate categoryCollectionView:self gestureRecognizerShouldBegin:gestureRecognizer];
}
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (self.gestureDelegate && [self.gestureDelegate respondsToSelector:@selector(categoryCollectionView:gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
return [self.gestureDelegate categoryCollectionView:self gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
}
return NO;
}
@end

View File

@@ -0,0 +1,18 @@
//
// JXCategoryFactory.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface JXCategoryFactory : NSObject
+ (CGFloat)interpolationFrom:(CGFloat)from to:(CGFloat)to percent:(CGFloat)percent;
+ (UIColor *)interpolationColorFrom:(UIColor *)fromColor to:(UIColor *)toColor percent:(CGFloat)percent;
@end

View File

@@ -0,0 +1,29 @@
//
// JXCategoryFactory.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryFactory.h"
#import "UIColor+JXAdd.h"
@implementation JXCategoryFactory
+ (CGFloat)interpolationFrom:(CGFloat)from to:(CGFloat)to percent:(CGFloat)percent
{
percent = MAX(0, MIN(1, percent));
return from + (to - from)*percent;
}
+ (UIColor *)interpolationColorFrom:(UIColor *)fromColor to:(UIColor *)toColor percent:(CGFloat)percent
{
CGFloat red = [self interpolationFrom:fromColor.jx_red to:toColor.jx_red percent:percent];
CGFloat green = [self interpolationFrom:fromColor.jx_green to:toColor.jx_green percent:percent];
CGFloat blue = [self interpolationFrom:fromColor.jx_blue to:toColor.jx_blue percent:percent];
CGFloat alpha = [self interpolationFrom:fromColor.jx_alpha to:toColor.jx_alpha percent:percent];
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
@end

View File

@@ -0,0 +1,31 @@
//
// JXCategoryIndicatorParamsModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "JXCategoryViewDefines.h"
/**
指示器不同情况处理时传递的数据模型,不同情况会对不同的属性赋值,根据不同情况的 api 说明确认。
FAQ: 为什么会通过 model 传递数据?
因为指示器处理逻辑以后会扩展不同的使用场景,会新增参数,如果不通过 model 传递,就会在 api 新增参数,一旦修改 api 改的地方就特别多了,而且会影响到之前自定义实现的开发者。
*/
@interface JXCategoryIndicatorParamsModel : NSObject
@property (nonatomic, assign) NSInteger selectedIndex; // 当前选中的 index
@property (nonatomic, assign) CGRect selectedCellFrame; // 当前选中的 cellFrame
@property (nonatomic, assign) NSInteger leftIndex; // 正在过渡中的两个 cell相对位置在左边的 cell 的 index
@property (nonatomic, assign) CGRect leftCellFrame; // 正在过渡中的两个 cell相对位置在左边的 cell 的 frame
@property (nonatomic, assign) NSInteger rightIndex; // 正在过渡中的两个 cell相对位置在右边的 cell 的 index
@property (nonatomic, assign) CGRect rightCellFrame; // 正在过渡中的两个 cell相对位置在右边的 cell 的 frame
@property (nonatomic, assign) CGFloat percent; // 正在过渡中的两个 cell从左到右的百分比
@property (nonatomic, assign) NSInteger lastSelectedIndex; // 之前选中的 index
@property (nonatomic, assign) JXCategoryCellSelectedType selectedType; //cell 被选中类型
@end

View File

@@ -0,0 +1,13 @@
//
// JXCategoryIndicatorParamsModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorParamsModel.h"
@implementation JXCategoryIndicatorParamsModel
@end

View File

@@ -0,0 +1,49 @@
//
// JXCategoryIndicatorProtocol.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "JXCategoryViewDefines.h"
#import "JXCategoryIndicatorParamsModel.h"
@protocol JXCategoryIndicatorProtocol <NSObject>
/**
categoryView 重置状态时调用
param selectedIndex 当前选中的 index
param selectedCellFrame 当前选中的 cellFrame
@param model 数据模型
*/
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model;
/**
contentScrollView在进行手势滑动时处理指示器跟随手势变化UI逻辑
param selectedIndex 当前选中的index
param leftIndex 正在过渡中的两个cell相对位置在左边的cell的index
param leftCellFrame 正在过渡中的两个cell相对位置在左边的cell的frame
param rightIndex 正在过渡中的两个cell相对位置在右边的cell的index
param rightCellFrame 正在过渡中的两个cell相对位置在右边的cell的frame
param percent 过渡百分比
@param model 数据模型
*/
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model;
/**
选中了某一个cell
param lastSelectedIndex 之前选中的index
param selectedIndex 选中的index
param selectedCellFrame 选中的cellFrame
param selectedType cell被选中类型
@param model 数据模型
*/
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model;
@end

View File

@@ -0,0 +1,16 @@
//
// JXCategoryListContainerRTLCell.h
// JXCategoryView
//
// Created by jiaxin on 2020/7/3.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JXCategoryListContainerRTLCell : UICollectionViewCell
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,23 @@
//
// JXCategoryListContainerRTLCell.m
// JXCategoryView
//
// Created by jiaxin on 2020/7/3.
//
#import "JXCategoryListContainerRTLCell.h"
#import "RTLManager.h"
@implementation JXCategoryListContainerRTLCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[RTLManager horizontalFlipViewIfNeeded:self];
[RTLManager horizontalFlipViewIfNeeded:self.contentView];
}
return self;
}
@end

View File

@@ -0,0 +1,122 @@
//
// JXCategoryListScrollView.h
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryViewDefines.h"
#import "JXCategoryBaseView.h"
@class JXCategoryListContainerView;
/**
列表容器视图的类型
- ScrollView: UIScrollView。优势没有其他副作用。劣势视图内存占用相对大一点。
- CollectionView: 使用UICollectionView。优势因为列表被添加到cell上视图的内存占用更少适合内存要求特别高的场景。劣势因为cell重用机制的问题导致列表下拉刷新视图会因为被removeFromSuperview而被隐藏。需要参考`LoadDataListCollectionListViewController`类做特殊处理。
*/
typedef NS_ENUM(NSUInteger, JXCategoryListContainerType) {
JXCategoryListContainerType_ScrollView,
JXCategoryListContainerType_CollectionView,
};
@protocol JXCategoryListContentViewDelegate <NSObject>
/**
如果列表是VC就返回VC.view
如果列表是View就返回View自己
@return 返回列表视图
*/
- (UIView *)listView;
@optional
/**
可选实现,列表将要显示的时候调用
*/
- (void)listWillAppear;
/**
可选实现,列表显示的时候调用
*/
- (void)listDidAppear;
/**
可选实现,列表将要消失的时候调用
*/
- (void)listWillDisappear;
/**
可选实现,列表消失的时候调用
*/
- (void)listDidDisappear;
@end
@protocol JXCategoryListContainerViewDelegate <NSObject>
/**
返回list的数量
@param listContainerView 列表的容器视图
@return list的数量
*/
- (NSInteger)numberOfListsInlistContainerView:(JXCategoryListContainerView *)listContainerView;
/**
根据index返回一个对应列表实例需要是遵从`JXCategoryListContentViewDelegate`协议的对象。
你可以代理方法调用的时候初始化对应列表,达到懒加载的效果。这也是默认推荐的初始化列表方法。你也可以提前创建好列表,等该代理方法回调的时候再返回也可以,达到预加载的效果。
如果列表是用自定义UIView封装的就让自定义UIView遵从`JXCategoryListContentViewDelegate`协议该方法返回自定义UIView即可。
如果列表是用自定义UIViewController封装的就让自定义UIViewController遵从`JXCategoryListContentViewDelegate`协议该方法返回自定义UIViewController即可。
@param listContainerView 列表的容器视图
@param index 目标下标
@return 遵从JXCategoryListContentViewDelegate协议的list实例
*/
- (id<JXCategoryListContentViewDelegate>)listContainerView:(JXCategoryListContainerView *)listContainerView initListForIndex:(NSInteger)index;
@optional
/**
返回自定义UIScrollView或UICollectionView的Class
某些特殊情况需要自己处理UIScrollView内部逻辑。比如项目用了FDFullscreenPopGesture需要处理手势相关代理。
@param listContainerView JXCategoryListContainerView
@return 自定义UIScrollView实例
*/
- (Class)scrollViewClassInlistContainerView:(JXCategoryListContainerView *)listContainerView;
/**
控制能否初始化对应index的列表。有些业务需求需要在某些情况才允许初始化某些列表通过通过该代理实现控制。
*/
- (BOOL)listContainerView:(JXCategoryListContainerView *)listContainerView canInitListAtIndex:(NSInteger)index;
- (void)listContainerViewDidScroll:(UIScrollView *)scrollView;
- (void)listContainerViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)listContainerViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)listContainerViewWillBeginDecelerating:(UIScrollView *)scrollView;
- (void)listContainerViewDidEndDecelerating:(UIScrollView *)scrollView;
@end
@interface JXCategoryListContainerView : UIView <JXCategoryViewListContainer>
@property (nonatomic, assign, readonly) JXCategoryListContainerType containerType;
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
@property (nonatomic, strong, readonly) NSDictionary <NSNumber *, id<JXCategoryListContentViewDelegate>> *validListDict; //已经加载过的列表字典。key是indexvalue是对应的列表
@property (nonatomic, strong) UIColor *listCellBackgroundColor; //默认:[UIColor whiteColor]
/**
滚动切换的时候滚动距离超过一页的多少百分比就触发列表的初始化。默认0.01即列表显示了一点就触发加载。范围0~1开区间不包括0和1
*/
@property (nonatomic, assign) CGFloat initListPercent;
@property (nonatomic, assign) BOOL bounces; //默认NO
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithType:(JXCategoryListContainerType)type delegate:(id<JXCategoryListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -0,0 +1,547 @@
//
// JXCategoryListContainerView.m
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryListContainerView.h"
#import <objc/runtime.h>
#import "RTLManager.h"
@interface JXCategoryListContainerViewController : UIViewController
@property (copy) void(^viewWillAppearBlock)(void);
@property (copy) void(^viewDidAppearBlock)(void);
@property (copy) void(^viewWillDisappearBlock)(void);
@property (copy) void(^viewDidDisappearBlock)(void);
@end
@implementation JXCategoryListContainerViewController
- (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 JXCategoryListContainerView () <UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, weak) id<JXCategoryListContainerViewDelegate> delegate;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXCategoryListContentViewDelegate>> *validListDict;
@property (nonatomic, assign) NSInteger willAppearIndex;
@property (nonatomic, assign) NSInteger willDisappearIndex;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) JXCategoryListContainerViewController *containerVC;
@end
@implementation JXCategoryListContainerView
- (instancetype)initWithType:(JXCategoryListContainerType)type delegate:(id<JXCategoryListContainerViewDelegate>)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 = [[JXCategoryListContainerViewController 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 == JXCategoryListContainerType_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 = [[UIScrollView alloc] init];
}
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, *)) {
if ([self.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
[RTLManager horizontalFlipViewIfNeeded:self.scrollView];
[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 = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
}
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, *)) {
if ([self.collectionView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
if ([RTLManager supportRTL]) {
self.collectionView.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
[RTLManager horizontalFlipView:self.collectionView];
}
[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 == JXCategoryListContainerType_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<JXCategoryListContentViewDelegate> _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);
}];
CGPoint scrollViewContentOffset = self.scrollView.contentOffset;
scrollViewContentOffset.x = self.currentIndex*self.scrollView.bounds.size.width;
self.scrollView.contentOffset = scrollViewContentOffset;
}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.collectionViewLayout invalidateLayout];
self.collectionView.frame = self.bounds;
[self.collectionView reloadData];
CGPoint collectionViewContentOffset = self.collectionView.contentOffset;
collectionViewContentOffset.x =
self.collectionView.bounds.size.width*self.currentIndex;
[self.collectionView setContentOffset:collectionViewContentOffset 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)setBounces:(BOOL)bounces {
_bounces = bounces;
self.scrollView.bounces = bounces;
}
#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;
UIView* listView = nil;
id<JXCategoryListContentViewDelegate> 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
listView = [list listView];
if ([list isKindOfClass:[UIViewController class]]) {
listView.frame = cell.contentView.bounds;
} else {
listView.frame = cell.bounds;
}
}
BOOL isAdded = NO;
for (UIView *subview in cell.contentView.subviews) {
if( listView != subview ) {
[subview removeFromSuperview];
} else {
isAdded = YES;
}
}
if( !isAdded && listView ) {
[cell.contentView addSubview:listView];
}
// RTL
if ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute]
== UIUserInterfaceLayoutDirectionRightToLeft) {
cell.contentView.transform = CGAffineTransformMakeScale(-1, 1);
}
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(listContainerViewDidEndDecelerating:)]) {
[self.delegate listContainerViewDidEndDecelerating:scrollView];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWillBeginDragging:)]) {
[self.delegate listContainerViewWillBeginDragging:scrollView];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewDidEndDragging:willDecelerate:)]) {
[self.delegate listContainerViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWillBeginDecelerating:)]) {
[self.delegate listContainerViewWillBeginDecelerating:scrollView];
}
}
#pragma mark - JXCategoryViewListContainer
- (UIScrollView *)contentScrollView {
return self.scrollView;
}
- (void)setDefaultSelectedIndex:(NSInteger)index {
self.currentIndex = index;
}
- (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<JXCategoryListContentViewDelegate> list in _validListDict.allValues) {
[[list listView] removeFromSuperview];
if ([list isKindOfClass:[UIViewController class]]) {
[(UIViewController *)list removeFromParentViewController];
}
}
[_validListDict removeAllObjects];
if (self.containerType == JXCategoryListContainerType_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<JXCategoryListContentViewDelegate> 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;
if (self.containerType == JXCategoryListContainerType_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]];
[RTLManager horizontalFlipViewIfNeeded:[list listView]];
}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]];
}
}
- (void)listWillAppear:(NSInteger)index {
if (![self checkIndexValid:index]) {
return;
}
id<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
if (list == nil) {
//listWillAppear
BOOL canInitList = YES;
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerView:canInitListAtIndex:)]) {
canInitList = [self.delegate listContainerView:self canInitListAtIndex:index];
}
if (!canInitList) {
return;
}
list = [self.delegate listContainerView:self initListForIndex:index];
if ([list isKindOfClass:[UIViewController class]]) {
[self.containerVC addChildViewController:(UIViewController *)list];
}
_validListDict[@(index)] = list;
if (self.containerType == JXCategoryListContainerType_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]];
[RTLManager horizontalFlipViewIfNeeded:[list listView]];
}
}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<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
if (list && [list respondsToSelector:@selector(listDidAppear)]) {
[list listDidAppear];
}
if ([list isKindOfClass:[UIViewController class]]) {
UIViewController *listVC = (UIViewController *)list;
[listVC endAppearanceTransition];
}
}
- (void)listWillDisappear:(NSInteger)index {
if (![self checkIndexValid:index]) {
return;
}
id<JXCategoryListContentViewDelegate> 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<JXCategoryListContentViewDelegate> 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,24 @@
//
// JXCategoryViewAnimator.h
// JXCategoryView
//
// Created by jiaxin on 2019/1/24.
// Copyright © 2019 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface JXCategoryViewAnimator : NSObject
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, copy) void(^progressCallback)(CGFloat percent);
@property (nonatomic, copy) void(^completeCallback)(void);
@property (readonly, getter=isExecuting) BOOL executing;
- (void)start;
- (void)stop;
- (void)invalid;
@end

View File

@@ -0,0 +1,75 @@
//
// JXCategoryViewAnimator.m
// JXCategoryView
//
// Created by jiaxin on 2019/1/24.
// Copyright © 2019 jiaxin. All rights reserved.
//
#import "JXCategoryViewAnimator.h"
@interface JXCategoryViewAnimator ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) CFTimeInterval firstTimestamp;
@property (readwrite, getter=isExecuting) BOOL executing;
@end
@implementation JXCategoryViewAnimator
#pragma mark - Initialize
- (void)dealloc {
self.progressCallback = nil;
self.completeCallback = nil;
}
- (instancetype)init {
self = [super init];
if (self) {
_executing = NO;
_duration = 0.25;
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(processDisplayLink:)];
}
return self;
}
#pragma mark - Public
- (void)start {
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
self.executing = YES;
}
- (void)stop {
!self.progressCallback ?: self.progressCallback(1);
[self.displayLink invalidate];
!self.completeCallback ?: self.completeCallback();
self.executing = NO;
}
- (void)invalid {
[self.displayLink invalidate];
!self.completeCallback ?: self.completeCallback();
self.executing = NO;
}
#pragma mark - Actions
- (void)processDisplayLink:(CADisplayLink *)sender {
if (self.firstTimestamp == 0) {
self.firstTimestamp = sender.timestamp;
return;
}
CGFloat percent = (sender.timestamp - self.firstTimestamp)/self.duration;
if (percent >= 1) {
!self.progressCallback ?: self.progressCallback(percent);
[self.displayLink invalidate];
!self.completeCallback ?: self.completeCallback();
self.executing = NO;
}else {
!self.progressCallback ?: self.progressCallback(percent);
self.executing = YES;
}
}
@end

View File

@@ -0,0 +1,43 @@
//
// JXCategoryViewDefines.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
static const CGFloat JXCategoryViewAutomaticDimension = -1;
typedef void(^JXCategoryCellSelectedAnimationBlock)(CGFloat percent);
// 指示器的位置
typedef NS_ENUM(NSUInteger, JXCategoryComponentPosition) {
JXCategoryComponentPosition_Bottom,
JXCategoryComponentPosition_Top
};
// cell 被选中的类型
typedef NS_ENUM(NSUInteger, JXCategoryCellSelectedType) {
JXCategoryCellSelectedTypeUnknown, // 未知不是选中cellForRow方法里面、两个cell过渡时
JXCategoryCellSelectedTypeClick, // 点击选中
JXCategoryCellSelectedTypeCode, // 调用方法 selectItemAtIndex: 选中
JXCategoryCellSelectedTypeScroll // 通过滚动到某个 cell 选中
};
// cell 标题锚点位置
typedef NS_ENUM(NSUInteger, JXCategoryTitleLabelAnchorPointStyle) {
JXCategoryTitleLabelAnchorPointStyleCenter,
JXCategoryTitleLabelAnchorPointStyleTop,
JXCategoryTitleLabelAnchorPointStyleBottom
};
// 指示器滚动样式
typedef NS_ENUM(NSUInteger, JXCategoryIndicatorScrollStyle) {
JXCategoryIndicatorScrollStyleSimple, // 简单滚动,即从当前位置过渡到目标位置
JXCategoryIndicatorScrollStyleSameAsUserScroll // 和用户左右滚动列表时的效果一样
};
#define JXCategoryViewDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead)

View File

@@ -0,0 +1,18 @@
//
// UIColor+JXAdd.h
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIColor (JXAdd)
@property (nonatomic, assign, readonly) CGFloat jx_red;
@property (nonatomic, assign, readonly) CGFloat jx_green;
@property (nonatomic, assign, readonly) CGFloat jx_blue;
@property (nonatomic, assign, readonly) CGFloat jx_alpha;
@end

View File

@@ -0,0 +1,35 @@
//
// UIColor+JXAdd.m
// UI
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "UIColor+JXAdd.h"
@implementation UIColor (JXAdd)
- (CGFloat)jx_red {
CGFloat r = 0, g, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return r;
}
- (CGFloat)jx_green {
CGFloat r, g = 0, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return g;
}
- (CGFloat)jx_blue {
CGFloat r, g, b = 0, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return b;
}
- (CGFloat)jx_alpha {
return CGColorGetAlpha(self.CGColor);
}
@end