Commit a4f39813 authored by luyueming's avatar luyueming

jxPagerView放到GMBase中

parent 401aef70
//
// JXCategoryBaseCell.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryBaseCellModel.h"
@interface JXCategoryBaseCell : UICollectionViewCell
@property (nonatomic, strong) JXCategoryBaseCellModel *cellModel;
- (void)initializeViews NS_REQUIRES_SUPER;
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel NS_REQUIRES_SUPER;
@end
//
// JXCategoryBaseCell.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryBaseCell.h"
@interface JXCategoryBaseCell ()
@end
@implementation JXCategoryBaseCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initializeViews];
}
return self;
}
- (void)initializeViews
{
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
self.cellModel = cellModel;
}
@end
//
// JXCategoryBaseCellModel.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface JXCategoryBaseCellModel : NSObject
@property (nonatomic, assign) NSUInteger index;
@property (nonatomic, assign) BOOL selected;
@property (nonatomic, assign) CGFloat cellWidth;
@property (nonatomic, assign) CGFloat cellSpacing;
@property (nonatomic, assign) BOOL cellWidthZoomEnabled;
@property (nonatomic, assign) CGFloat cellWidthZoomScale;
@end
//
// JXCategoryBaseCellModel.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryBaseCellModel.h"
@implementation JXCategoryBaseCellModel
- (CGFloat)cellWidth {
if (_cellWidthZoomEnabled) {
return _cellWidth * _cellWidthZoomScale;
}
return _cellWidth;
}
@end
//
// JXCategoryView.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryBaseCell.h"
#import "JXCategoryBaseCellModel.h"
#import "JXCategoryCollectionView.h"
#import "JXCategoryViewDefines.h"
@class JXCategoryBaseView;
@protocol JXCategoryViewDelegate <NSObject>
@optional
//为什么会把选中代理分为三个,因为有时候只关心点击选中的,有时候只关心滚动选中的,有时候只关心选中。所以具体情况,使用对应方法。
/**
点击选择或者滚动选中都会调用该方法,如果外部不关心具体是点击还是滚动选中的,只关心选中这个事件,就实现该方法。
@param categoryView categoryView description
@param index 选中的index
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didSelectedItemAtIndex:(NSInteger)index;
/**
点击选中的情况才会调用该方法
@param categoryView categoryView description
@param index 选中的index
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didClickSelectedItemAtIndex:(NSInteger)index;
/**
滚动选中的情况才会调用该方法
@param categoryView categoryView description
@param index 选中的index
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didScrollSelectedItemAtIndex:(NSInteger)index;
/**
只有点击的切换才会调用!!!
因为用户点击,contentScrollView即将过渡到目标index的配置。内部默认实现`[self.contentScrollView setContentOffset:CGPointMake(targetIndex*self.contentScrollView.bounds.size.width, 0) animated:YES];`。如果实现该代理方法,以自定义实现为准。比如将animated设置为NO,点击切换时无需滚动效果。类似于今日头条APP。
@param categoryView categoryView description
@param index index description
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView didClickedItemContentScrollViewTransitionToIndex:(NSInteger)index;
/**
正在滚动中的回调
@param categoryView categoryView description
@param leftIndex 正在滚动中,相对位置处于左边的index
@param rightIndex 正在滚动中,相对位置处于右边的index
@param ratio 百分比
*/
- (void)categoryView:(JXCategoryBaseView *)categoryView scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio;
@end
@interface JXCategoryBaseView : UIView
@property (nonatomic, strong) JXCategoryCollectionView *collectionView;
@property (nonatomic, strong) NSArray <JXCategoryBaseCellModel *>*dataSource;
@property (nonatomic, weak) id<JXCategoryViewDelegate>delegate;
@property (nonatomic, strong) UIScrollView *contentScrollView; //需要关联的contentScrollView
@property (nonatomic, assign) NSInteger defaultSelectedIndex; //修改初始化的时候默认选择的index
@property (nonatomic, assign, readonly) NSInteger selectedIndex;
@property (nonatomic, assign) CGFloat contentEdgeInsetLeft; //整体内容的左边距,默认JXCategoryViewAutomaticDimension(等于cellSpacing)
@property (nonatomic, assign) CGFloat contentEdgeInsetRight; //整体内容的右边距,默认JXCategoryViewAutomaticDimension(等于cellSpacing)
@property (nonatomic, assign) CGFloat cellWidth; //默认JXCategoryViewAutomaticDimension
@property (nonatomic, assign) CGFloat cellWidthIncrement; //cell宽度补偿。默认:0
@property (nonatomic, assign) CGFloat cellSpacing; //cell之间的间距,默认20
@property (nonatomic, assign) BOOL averageCellSpacingEnabled; //当item内容总宽度小于JXCategoryBaseView的宽度,是否将cellSpacing均分。默认为YES。
//----------------------cellWidthZoomEnabled-----------------------//
//cell宽度的缩放主要是为了腾讯视频、汽车之家效果打造的,一般情况下慎用,不太好控制。
@property (nonatomic, assign) BOOL cellWidthZoomEnabled; //默认为NO
@property (nonatomic, assign) BOOL cellWidthZoomScrollGradientEnabled; //手势滚动中,是否需要更新状态。默认为YES
@property (nonatomic, assign) CGFloat cellWidthZoomScale; //默认1.2,cellWidthZoomEnabled为YES才生效
/**
代码调用选中了目标index的item
@param index 目标index
*/
- (void)selectItemAtIndex:(NSInteger)index;
/**
初始化的时候无需调用。初始化之后更新其他配置属性,需要调用该方法,进行刷新。
*/
- (void)reloadData;
/**
刷新指定的index的cell
@param index 指定cell的index
*/
- (void)reloadCellAtIndex:(NSInteger)index;
#pragma mark - Subclass use
- (CGRect)getTargetCellFrame:(NSInteger)targetIndex;
#pragma mark - Subclass Override
- (void)initializeData NS_REQUIRES_SUPER;
- (void)initializeViews NS_REQUIRES_SUPER;
/**
reloadData方法调用,重新生成数据源赋值到self.dataSource
*/
- (void)refreshDataSource;
/**
reloadData方法调用,根据数据源重新刷新状态;
*/
- (void)refreshState NS_REQUIRES_SUPER;
/**
用户点击了某个item,刷新选中与取消选中的cellModel
@param selectedCellModel 选中的cellModel
@param unselectedCellModel 取消选中的cellModel
*/
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel NS_REQUIRES_SUPER;
/**
关联的contentScrollView的contentOffset发生了改变
@param contentOffset 偏移量
*/
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset NS_REQUIRES_SUPER;
/**
该方法用于子类重载,如果外部要选中某个index,请使用`- (void)selectItemAtIndex:(NSUInteger)index;`
点击某一个item,或者contentScrollView滚动到某一个item的时候调用。根据selectIndex刷新选中状态。
@param index 选中的index
@param isClicked YES:点击选中;NO:滚动选中。
@return 返回值为NO,表示触发内部某些判断(点击了同一个cell),子类无需后续操作。
*/
- (BOOL)selectCellAtIndex:(NSInteger)index isClicked:(BOOL)isClicked NS_REQUIRES_SUPER;
/**
reloadData时,返回每个cell的宽度
@param index 目标index
@return cellWidth
*/
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index;
/**
返回自定义cell的class
@return cell class
*/
- (Class)preferredCellClass;
/**
refreshState时调用,重置cellModel的状态
@param cellModel 待重置的cellModel
@param index 目标index
*/
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index;
@end
//
// JXCategoryCollectionView.h
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryIndicatorProtocol.h"
@interface JXCategoryCollectionView : UICollectionView
@property (nonatomic, strong) NSArray <UIView<JXCategoryIndicatorProtocol> *> *indicators;
@end
//
// JXCategoryCollectionView.m
// UI系列测试
//
// Created by jiaxin on 2018/3/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryCollectionView.h"
@implementation JXCategoryCollectionView
- (void)layoutSubviews
{
[super layoutSubviews];
for (UIView<JXCategoryIndicatorProtocol> *view in self.indicators) {
[self sendSubviewToBack:view];
}
}
@end
//
// 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
//
// 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));
NSLog(@"percent---%.2f", percent);
NSLog(@"from + (to - from)*---%.2f", (from + (to - from)*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
//
// JXCategoryIndicatorParamsModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
指示器不同情况处理时传递的数据模型,不同情况会对不同的属性赋值,根据不同情况的api说明确认。
为什么会通过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) BOOL isClicked; //YES:通过点击选中;NO:通过滚动选中
@end
//
// JXCategoryIndicatorParamsModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorParamsModel.h"
@implementation JXCategoryIndicatorParamsModel
@end
//
// 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>
/**
视图重置状态时调用
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 isClicked YES:点击选中;NO:滚动选中。
@param model 数据模型
*/
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model;
@end
//
// JXCategoryListScrollView.h
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXCategoryListContainerView;
@protocol JXCategoryListContentViewDelegate <NSObject>
/**
如果列表是VC,就返回VC.view
如果列表是View,就返回View自己
@return 返回列表视图
*/
- (UIView *)listView;
@optional
/**
可选实现,列表逻辑层面显示的时候调用
*/
- (void)listDidAppear;
/**
可选实现,列表逻辑层面消失的时候调用
*/
- (void)listDidDisappear;
@end
@protocol JXCategoryListContainerViewDelegate <NSObject>
/**
返回list的数量
@param listContainerView 列表的容器视图
@return list的数量
*/
- (NSInteger)numberOfListsInlistContainerView:(JXCategoryListContainerView *)listContainerView;
/**
根据index初始化对应的遵从JXCategoryListContentViewDelegate协议的list,注意是初始化哟,要new一个新的实例!!!
@param listContainerView 列表的容器视图
@param index 目标下标
@return 新的遵从JXCategoryListContentViewDelegate协议的list实例
*/
- (id<JXCategoryListContentViewDelegate>)listContainerView:(JXCategoryListContainerView *)listContainerView initListForIndex:(NSInteger)index;
@end
@interface JXCategoryListContainerView : UIView
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
/**
滚动切换的时候,滚动距离超过一页的多少百分比,就认为切换了页面。默认0.5(即滚动超过了半屏,就认为翻页了)。范围0~1,开区间不包括0和1
*/
@property (nonatomic, assign) CGFloat didAppearPercent;
/**
需要和self.categoryView.defaultSelectedIndex保持一致
*/
@property (nonatomic, assign) NSInteger defaultSelectedIndex;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
/**
指定的初始化器
@param parentVC 父vc
@param delegate JXCategoryListContainerViewDelegate代理
@return JXCategoryListContainerView实例
*/
- (instancetype)initWithParentVC:(UIViewController *)parentVC delegate:(id<JXCategoryListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (void)reloadData;
//必须调用,请按照demo示例那样调用
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio selectedIndex:(NSInteger)selectedIndex;
//必须调用,请按照demo示例那样调用(注意是是点击选中的回调,不是其他回调)
- (void)didClickSelectedItemAtIndex:(NSInteger)index;
@end
//
// JXCategoryListContainerView.m
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryListContainerView.h"
@interface JXCategoryListContainerView ()
@property (nonatomic, weak) id<JXCategoryListContainerViewDelegate> delegate;
@property (nonatomic, weak) UIViewController *parentViewController;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXCategoryListContentViewDelegate>> *listVCDict;
@property (nonatomic, assign) BOOL isLayoutSubviewsed;
@end
@implementation JXCategoryListContainerView
- (instancetype)initWithParentVC:(UIViewController *)parentVC delegate:(id<JXCategoryListContainerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_didAppearPercent = 0.5;
_parentViewController = parentVC;
_parentViewController.automaticallyAdjustsScrollViewInsets = NO;
_delegate = delegate;
_listVCDict = [NSMutableDictionary dictionary];
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_scrollView = [[UIScrollView alloc] init];
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.scrollsToTop = NO;
self.scrollView.bounces = NO;
if (@available(iOS 11.0, *)) {
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.scrollView];
}
- (void)reloadData {
for (id<JXCategoryListContentViewDelegate> list in self.listVCDict.allValues) {
[list.listView removeFromSuperview];
}
[self.listVCDict removeAllObjects];
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
[self listDidAppear:self.currentIndex];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.scrollView.frame = self.bounds;
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
if (!self.isLayoutSubviewsed) {
self.isLayoutSubviewsed = YES;
//初始化第一次调用
[self listDidAppear:self.currentIndex];
}
}
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
_defaultSelectedIndex = defaultSelectedIndex;
self.currentIndex = defaultSelectedIndex;
}
#pragma mark - JXCategoryBaseView回调
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio selectedIndex:(NSInteger)selectedIndex {
NSInteger targetIndex = -1;
NSInteger disappearIndex = -1;
if (rightIndex == selectedIndex) {
//当前选中的在右边,用户正在从右边往左边滑动
if (ratio < (1 - self.didAppearPercent)) {
targetIndex = leftIndex;
disappearIndex = rightIndex;
}
}else {
//当前选中的在左边,用户正在从左边往右边滑动
if (ratio > self.didAppearPercent) {
targetIndex = rightIndex;
disappearIndex = leftIndex;
}
}
if (targetIndex != -1 && self.currentIndex != targetIndex) {
[self listDidAppear:targetIndex];
[self listDidDisappear:disappearIndex];
}
}
- (void)didClickSelectedItemAtIndex:(NSInteger)index {
[self listDidDisappear:self.currentIndex];
[self listDidAppear:index];
}
#pragma mark - Private
- (void)listDidAppear:(NSInteger)index {
self.currentIndex = index;
id<JXCategoryListContentViewDelegate> list = self.listVCDict[@(index)];
if (list == nil) {
list = [self.delegate listContainerView:self initListForIndex:index];
}
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];
self.listVCDict[@(index)] = list;
}
if (list && [list respondsToSelector:@selector(listDidAppear)]) {
[list listDidAppear];
}
}
- (void)listDidDisappear:(NSInteger)index {
id<JXCategoryListContentViewDelegate> list = self.listVCDict[@(index)];
if (list && [list respondsToSelector:@selector(listDidDisappear)]) {
[list listDidDisappear];
}
}
@end
//
// JXCategoryListScrollView.h
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
此类已不再维护,推荐使用JXCategoryListContainerView类!!!
*/
@interface JXCategoryListVCContainerView : UIView
@property (nonatomic, weak) UIViewController *parentViewController;
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
@property (nonatomic, strong) NSArray <UIViewController *> *listVCArray;
//这个defaultSelectedIndex仅仅用于触发对应index的数据加载,如果要让categoryView和listView都处于对应的index。还应该添加后面这段代码:self.categoryView.defaultSelectedIndex = n
@property (nonatomic, assign) NSInteger defaultSelectedIndex;
- (void)reloadData;
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio;
- (void)didClickSelectedItemAtIndex:(NSInteger)index;
- (void)didScrollSelectedItemAtIndex:(NSInteger)index;
- (void)parentVCWillAppear:(BOOL)animated;
- (void)parentVCDidAppear:(BOOL)animated;
- (void)parentVCWillDisappear:(BOOL)animated;
- (void)parentVCDidDisappear:(BOOL)animated;
@end
//
// JXCategoryListContainerView.m
// JXCategoryView
//
// Created by jiaxin on 2018/9/12.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryListVCContainerView.h"
@interface JXCategoryListVCContainerView () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, assign) NSInteger currentIndex;
@property (nonatomic, assign) NSInteger targetIndex;
@end
@implementation JXCategoryListVCContainerView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initializeViews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_scrollView = [[UIScrollView alloc] init];
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
[self addSubview:self.scrollView];
}
- (void)reloadData {
[self listVCWillAppear:self.defaultSelectedIndex];
[self listVCDidAppear:self.defaultSelectedIndex];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.scrollView.frame = self.bounds;
//根据新的数据源重新添加listView
for (int i = 0; i < self.listVCArray.count; i ++) {
UIViewController *listVC = self.listVCArray[i];
[listVC willMoveToParentViewController:self.parentViewController];
[self.parentViewController addChildViewController:self.parentViewController];
[listVC didMoveToParentViewController:self.parentViewController];
listVC.view.frame = CGRectMake(i*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
[self.scrollView addSubview:listVC.view];
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*self.listVCArray.count, self.scrollView.bounds.size.height);
}
- (void)setListVCArray:(NSArray<UIViewController *> *)listVCArray {
for (UIViewController *vc in self.listVCArray) {
[vc.view removeFromSuperview];
}
_listVCArray = listVCArray;
[self setNeedsLayout];
}
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
_defaultSelectedIndex = defaultSelectedIndex;
self.currentIndex = defaultSelectedIndex;
}
- (void)parentVCWillAppear:(BOOL)animated {
[self listVCWillAppear:self.currentIndex];
}
- (void)parentVCDidAppear:(BOOL)animated {
[self listVCDidAppear:self.currentIndex];
}
- (void)parentVCWillDisappear:(BOOL)animated {
[self listVCWillDisappear:self.currentIndex];
}
- (void)parentVCDidDisappear:(BOOL)animated {
[self listVCDidDisappear:self.currentIndex];
}
- (void)scrollingFromLeftIndex:(NSInteger)leftIndex toRightIndex:(NSInteger)rightIndex ratio:(CGFloat)ratio {
NSInteger targetIndex = 0;
if (ratio > 0.5) {
targetIndex = rightIndex;
}else {
targetIndex = leftIndex;
}
if (self.targetIndex != targetIndex) {
[self listVCWillAppear:targetIndex];
[self listVCDidAppear:targetIndex];
//源代码
//[self listVCWillDisappear:self.currentIndex];
//[self listVCDidDisappear:self.currentIndex];
//add by wuna 解决了部分生命周期问题和标题跟页面正确对应问题
if (ratio > 0.5) {
[self listVCWillDisappear:leftIndex];
[self listVCDidDisappear:leftIndex];
}else{
[self listVCWillDisappear:rightIndex];
[self listVCDidDisappear:rightIndex];
}
}
}
- (void)didScrollSelectedItemAtIndex:(NSInteger)index {
}
- (void)didClickSelectedItemAtIndex:(NSInteger)index {
[self listVCWillDisappear:self.currentIndex];
[self listVCDidDisappear:self.currentIndex];
[self listVCWillAppear:index];
[self listVCDidAppear:index];
}
#pragma mark - Private
- (void)listVCWillAppear:(NSInteger)index {
self.targetIndex = index;
UIViewController *vc = self.listVCArray[index];
[vc beginAppearanceTransition:YES animated:YES];
}
- (void)listVCDidAppear:(NSInteger)index {
self.currentIndex = index;
UIViewController *vc = self.listVCArray[index];
[vc endAppearanceTransition];
}
- (void)listVCWillDisappear:(NSInteger)index {
UIViewController *vc = self.listVCArray[index];
[vc beginAppearanceTransition:NO animated:YES];
}
- (void)listVCDidDisappear:(NSInteger)index {
UIViewController *vc = self.listVCArray[index];
[vc endAppearanceTransition];
}
@end
//
// 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 NS_ENUM(NSUInteger, JXCategoryComponentPosition) {
JXCategoryComponentPosition_Bottom,
JXCategoryComponentPosition_Top,
};
typedef NS_ENUM(NSUInteger, JXCategoryIndicatorScrollStyle) {
JXCategoryIndicatorScrollStyleSimple, //简单滚动,即从当前位置过渡到目标位置
JXCategoryIndicatorScrollStyleSameAsUserScroll, //和用户左右滚动列表时的效果一样
};
//
// 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
//
// 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
//
// JXCategoryDotCell.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCell.h"
@interface JXCategoryDotCell : JXCategoryTitleCell
@end
//
// JXCategoryDotCell.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryDotCell.h"
#import "JXCategoryDotCellModel.h"
@interface JXCategoryDotCell ()
@property (nonatomic, strong) CALayer *dotLayer;
@end
@implementation JXCategoryDotCell
- (void)initializeViews {
[super initializeViews];
_dotLayer = [CALayer layer];
[self.contentView.layer addSublayer:self.dotLayer];
}
- (void)layoutSubviews {
[super layoutSubviews];
[CATransaction begin];
[CATransaction setDisableActions:YES];
JXCategoryDotCellModel *myCellModel = (JXCategoryDotCellModel *)self.cellModel;
self.dotLayer.bounds = CGRectMake(0, 0, myCellModel.dotSize.width, myCellModel.dotSize.height);
switch (myCellModel.relativePosition) {
case JXCategoryDotRelativePosition_TopLeft:
{
self.dotLayer.position = CGPointMake(CGRectGetMinX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
}
break;
case JXCategoryDotRelativePosition_TopRight:
{
self.dotLayer.position = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
}
break;
case JXCategoryDotRelativePosition_BottomLeft:
{
self.dotLayer.position = CGPointMake(CGRectGetMinX(self.titleLabel.frame), CGRectGetMaxY(self.titleLabel.frame));
}
break;
case JXCategoryDotRelativePosition_BottomRight:
{
self.dotLayer.position = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMaxY(self.titleLabel.frame));
}
break;
default:
break;
}
self.dotLayer.position = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
[CATransaction commit];
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryDotCellModel *myCellModel = (JXCategoryDotCellModel *)cellModel;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.dotLayer.hidden = !myCellModel.dotHidden;
self.dotLayer.backgroundColor = myCellModel.dotColor.CGColor;
self.dotLayer.cornerRadius = myCellModel.dotCornerRadius;
[CATransaction commit];
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryDotCellModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
typedef NS_ENUM(NSUInteger, JXCategoryDotRelativePosition) {
JXCategoryDotRelativePosition_TopLeft = 0,
JXCategoryDotRelativePosition_TopRight,
JXCategoryDotRelativePosition_BottomLeft,
JXCategoryDotRelativePosition_BottomRight,
};
@interface JXCategoryDotCellModel : JXCategoryTitleCellModel
@property (nonatomic, assign) BOOL dotHidden;
@property (nonatomic, assign) JXCategoryDotRelativePosition relativePosition;
@property (nonatomic, assign) CGSize dotSize;
@property (nonatomic, assign) CGFloat dotCornerRadius;
@property (nonatomic, strong) UIColor *dotColor;
@end
//
// JXCategoryDotCellModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryDotCellModel.h"
@implementation JXCategoryDotCellModel
@end
//
// JXCategoryDotView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryDotCell.h"
#import "JXCategoryDotCellModel.h"
@interface JXCategoryDotView : JXCategoryTitleView
@property (nonatomic, assign) JXCategoryDotRelativePosition relativePosition; //相对于titleLabel的位置,默认:JXCategoryDotRelativePosition_TopRight
@property (nonatomic, strong) NSArray <NSNumber *> *dotStates; //@(布尔值),控制红点是否显示
@property (nonatomic, assign) CGSize dotSize; //默认:CGSizeMake(10, 10)
@property (nonatomic, assign) CGFloat dotCornerRadius; //默认:JXCategoryViewAutomaticDimension(self.dotSize.height/2)
@property (nonatomic, strong) UIColor *dotColor; //默认:[UIColor redColor]
@end
//
// JXCategoryDotView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryDotView.h"
@implementation JXCategoryDotView
- (void)initializeData {
[super initializeData];
_relativePosition = JXCategoryDotRelativePosition_TopRight;
_dotSize = CGSizeMake(10, 10);
_dotCornerRadius = JXCategoryViewAutomaticDimension;
_dotColor = [UIColor redColor];
}
- (Class)preferredCellClass {
return [JXCategoryDotCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryDotCellModel *cellModel = [[JXCategoryDotCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryDotCellModel *myCellModel = (JXCategoryDotCellModel *)cellModel;
myCellModel.dotHidden = [self.dotStates[index] boolValue];
myCellModel.relativePosition = self.relativePosition;
myCellModel.dotSize = self.dotSize;
myCellModel.dotColor = self.dotColor;
if (self.dotCornerRadius == JXCategoryViewAutomaticDimension) {
myCellModel.dotCornerRadius = self.dotSize.height/2;
}else {
myCellModel.dotCornerRadius = self.dotCornerRadius;
}
}
@end
//
// JXCategoryImageCell.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCell.h"
@interface JXCategoryImageCell : JXCategoryIndicatorCell
@property (nonatomic, strong) UIImageView *imageView;
@end
//
// JXCategoryImageCell.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryImageCell.h"
#import "JXCategoryImageCellModel.h"
@implementation JXCategoryImageCell
- (void)initializeViews {
[super initializeViews];
_imageView = [[UIImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView.layer.masksToBounds = YES;
[self.contentView addSubview:_imageView];
}
- (void)layoutSubviews {
[super layoutSubviews];
JXCategoryImageCellModel *myCellModel = (JXCategoryImageCellModel *)self.cellModel;
self.imageView.bounds = CGRectMake(0, 0, myCellModel.imageSize.width, myCellModel.imageSize.height);
self.imageView.center = self.contentView.center;
self.imageView.layer.cornerRadius = myCellModel.imageCornerRadius;
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryImageCellModel *myCellModel = (JXCategoryImageCellModel *)cellModel;
if (myCellModel.imageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.imageName];
}else if (myCellModel.imageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.imageURL);
}
}
if (myCellModel.selected) {
if (myCellModel.selectedImageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.selectedImageName];
}else if (myCellModel.selectedImageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.selectedImageURL);
}
}
}
if (myCellModel.imageZoomEnabled) {
self.imageView.transform = CGAffineTransformMakeScale(myCellModel.imageZoomScale, myCellModel.imageZoomScale);
}else {
self.imageView.transform = CGAffineTransformIdentity;
}
}
@end
//
// JXCategoryImageCellModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCellModel.h"
@interface JXCategoryImageCellModel : JXCategoryIndicatorCellModel
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL);
@property (nonatomic, copy) NSString *imageName; //加载bundle内的图片
@property (nonatomic, strong) NSURL *imageURL; //图片URL
@property (nonatomic, copy) NSString *selectedImageName;
@property (nonatomic, strong) NSURL *selectedImageURL;
@property (nonatomic, assign) CGSize imageSize;
@property (nonatomic, assign) CGFloat imageCornerRadius;
@property (nonatomic, assign) BOOL imageZoomEnabled;
@property (nonatomic, assign) CGFloat imageZoomScale;
@end
//
// JXCategoryImageCellModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryImageCellModel.h"
@implementation JXCategoryImageCellModel
@end
//
// JXCategoryImageView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorView.h"
#import "JXCategoryImageCell.h"
#import "JXCategoryImageCellModel.h"
@interface JXCategoryImageView : JXCategoryIndicatorView
@property (nonatomic, strong) NSArray <NSString *>*imageNames;
@property (nonatomic, strong) NSArray <NSURL *>*imageURLs;
@property (nonatomic, strong) NSArray <NSString *>*selectedImageNames;
@property (nonatomic, strong) NSArray <NSURL *>*selectedImageURLs;
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL); //使用imageURL从远端下载图片进行加载,建议使用SDWebImage等第三方库进行下载。
@property (nonatomic, assign) CGSize imageSize; //默认CGSizeMake(20, 20)
@property (nonatomic, assign) CGFloat imageCornerRadius; //图片圆角
@property (nonatomic, assign) BOOL imageZoomEnabled; //默认为NO
@property (nonatomic, assign) CGFloat imageZoomScale; //默认1.2,imageZoomEnabled为YES才生效
@end
//
// JXCategoryImageView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/20.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryImageView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryImageView
- (void)dealloc
{
self.loadImageCallback = nil;
}
- (void)initializeData {
[super initializeData];
_imageSize = CGSizeMake(20, 20);
_imageZoomEnabled = NO;
_imageZoomScale = 1.2;
_imageCornerRadius = 0;
}
- (Class)preferredCellClass {
return [JXCategoryImageCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
NSUInteger count = (self.imageNames.count > 0) ? self.imageNames.count : (self.imageURLs.count > 0 ? self.imageURLs.count : 0);
for (int i = 0; i < count; i++) {
JXCategoryImageCellModel *cellModel = [[JXCategoryImageCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryImageCellModel *myUnselectedCellModel = (JXCategoryImageCellModel *)unselectedCellModel;
myUnselectedCellModel.imageZoomScale = 1.0;
JXCategoryImageCellModel *myselectedCellModel = (JXCategoryImageCellModel *)selectedCellModel;
myselectedCellModel.imageZoomScale = self.imageZoomScale;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryImageCellModel *myCellModel = (JXCategoryImageCellModel *)cellModel;
myCellModel.loadImageCallback = self.loadImageCallback;
myCellModel.imageSize = self.imageSize;
myCellModel.imageCornerRadius = self.imageCornerRadius;
if (self.imageNames != nil) {
myCellModel.imageName = self.imageNames[index];
}else if (self.imageURLs != nil) {
myCellModel.imageURL = self.imageURLs[index];
}
if (self.selectedImageNames != nil) {
myCellModel.selectedImageName = self.selectedImageNames[index];
}else if (self.selectedImageURLs != nil) {
myCellModel.selectedImageURL = self.selectedImageURLs[index];
}
myCellModel.imageZoomEnabled = self.imageZoomEnabled;
myCellModel.imageZoomScale = 1.0;
if (index == self.selectedIndex) {
myCellModel.imageZoomScale = self.imageZoomScale;
}
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
[super refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:ratio];
JXCategoryImageCellModel *leftModel = (JXCategoryImageCellModel *)leftCellModel;
JXCategoryImageCellModel *rightModel = (JXCategoryImageCellModel *)rightCellModel;
if (self.imageZoomEnabled) {
leftModel.imageZoomScale = [JXCategoryFactory interpolationFrom:self.imageZoomScale to:1.0 percent:ratio];
rightModel.imageZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.imageZoomScale percent:ratio];
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
return self.imageSize.width;
}
@end
//
// JXCategoryIndicatorBackgroundView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorBackgroundView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) CGFloat backgroundViewWidth; //默认JXCategoryViewAutomaticDimension(与cellWidth相等)
@property (nonatomic, assign) CGFloat backgroundViewWidthIncrement; //宽度增量补偿,因为backgroundEllipseLayer一般会比实际内容大一些。默认10
@property (nonatomic, assign) CGFloat backgroundViewHeight; //默认JXCategoryViewAutomaticDimension(与cell高度相等)
@property (nonatomic, assign) CGFloat backgroundViewCornerRadius; //默认JXCategoryViewAutomaticDimension(即backgroundViewHeight/2)
@property (nonatomic, strong) UIColor *backgroundViewColor; //默认为[UIColor redColor]
@end
//
// JXCategoryIndicatorBackgroundView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorBackgroundView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_backgroundViewWidth = JXCategoryViewAutomaticDimension;
_backgroundViewHeight = JXCategoryViewAutomaticDimension;
_backgroundViewCornerRadius = JXCategoryViewAutomaticDimension;
_backgroundViewColor = [UIColor lightGrayColor];
_backgroundViewWidthIncrement = 10;
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
self.layer.cornerRadius = [self getBackgroundViewCornerRadius:model.selectedCellFrame];
self.backgroundColor = self.backgroundViewColor;
CGFloat width = [self getBackgroundViewWidth:model.selectedCellFrame];
CGFloat height = [self getBackgroundViewHeight:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
CGFloat y = (model.selectedCellFrame.size.height - height)/2;
self.frame = CGRectMake(x, y, width, height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetX = 0;
CGFloat targetWidth = [self getBackgroundViewWidth:leftCellFrame];
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftWidth = targetWidth;
CGFloat rightWidth = [self getBackgroundViewWidth:rightCellFrame];
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
if (self.backgroundViewWidth == JXCategoryViewAutomaticDimension) {
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:rightWidth percent:percent];
}
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGFloat height = [self getBackgroundViewHeight:leftCellFrame];
CGFloat y = (leftCellFrame.size.height - height)/2;
self.frame = CGRectMake(targetX, y, targetWidth, height);
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGFloat width = [self getBackgroundViewWidth:model.selectedCellFrame];
CGFloat height = [self getBackgroundViewHeight:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
CGFloat y = (model.selectedCellFrame.size.height - height)/2;
CGRect toFrame = CGRectMake(x, y, width, height);
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
}else {
self.frame = toFrame;
}
}
#pragma mark - Private
- (CGFloat)getBackgroundViewWidth:(CGRect)cellFrame
{
if (self.backgroundViewWidth == JXCategoryViewAutomaticDimension) {
return cellFrame.size.width + self.backgroundViewWidthIncrement;
}
return self.backgroundViewWidth + self.backgroundViewWidthIncrement;
}
- (CGFloat)getBackgroundViewHeight:(CGRect)cellFrame
{
if (self.backgroundViewHeight == JXCategoryViewAutomaticDimension) {
return cellFrame.size.height;
}
return self.backgroundViewHeight;
}
- (CGFloat)getBackgroundViewCornerRadius:(CGRect)cellFrame {
if (self.backgroundViewCornerRadius == JXCategoryViewAutomaticDimension) {
return [self getBackgroundViewHeight:cellFrame]/2;
}
return self.backgroundViewCornerRadius;
}
@end
//
// JXCategoryIndicatorBallView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorBallView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) CGSize ballViewSize; //默认:CGSizeMake(15, 15)
@property (nonatomic, assign) CGFloat ballScrollOffsetX; //小红点的偏移量 默认:20
@property (nonatomic, strong) UIColor *ballViewColor; //默认为[UIColor redColor]
@end
//
// JXCategoryIndicatorBallView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/21.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorBallView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorBallView ()
@property (nonatomic, strong) UIView *smallBall;
@property (nonatomic, strong) UIView *bigBall;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@end
@implementation JXCategoryIndicatorBallView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_ballViewSize = CGSizeMake(15, 15);
_ballScrollOffsetX = 20;
_ballViewColor = [UIColor redColor];
_smallBall = [[UIView alloc] init];
[self addSubview:self.smallBall];
_bigBall = [[UIView alloc] init];
[self addSubview:self.bigBall];
_shapeLayer = [CAShapeLayer layer];
[self.layer addSublayer:self.shapeLayer];
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.shapeLayer.fillColor = self.ballViewColor.CGColor;
[CATransaction commit];
self.smallBall.backgroundColor = self.ballViewColor;
self.smallBall.layer.cornerRadius = self.ballViewSize.height/2;
self.bigBall.backgroundColor = self.ballViewColor;
self.bigBall.layer.cornerRadius = self.ballViewSize.height/2;
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.ballViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.smallBall.frame = CGRectMake(x, y, self.ballViewSize.width, self.ballViewSize.height);
self.bigBall.frame = CGRectMake(x, y, self.ballViewSize.width, self.ballViewSize.height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetXOfBigBall = 0;
CGFloat targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat targetWidthOfSmallBall = self.ballViewSize.width;
if (percent == 0) {
targetXOfBigBall = leftCellFrame.origin.x + (leftCellFrame.size.width - self.ballViewSize.width)/2.0;
targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidthOfSmallBall)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - self.ballViewSize.width)/2;
//前50%,移动bigBall的x,缩小smallBall;后50%,移动bigBall的x,缩小smallBall,移动smallBall的x
if (percent <= 0.5) {
targetXOfBigBall = [JXCategoryFactory interpolationFrom:leftX to:(rightX - self.ballScrollOffsetX) percent:percent*2];
targetWidthOfSmallBall = [JXCategoryFactory interpolationFrom:self.ballViewSize.width to:self.ballViewSize.width/2 percent:percent*2];
}else {
targetXOfBigBall = [JXCategoryFactory interpolationFrom:(rightX - self.ballScrollOffsetX) to:rightX percent:(percent - 0.5)*2];
targetWidthOfSmallBall = [JXCategoryFactory interpolationFrom:self.ballViewSize.width/2 to:0 percent:(percent - 0.5)*2];
targetXOfSmallBall = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
}
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGRect bigBallFrame = self.bigBall.frame;
bigBallFrame.origin.x = targetXOfBigBall;
self.bigBall.frame = bigBallFrame;
self.bigBall.layer.cornerRadius = bigBallFrame.size.height/2;
CGFloat targetYOfSmallBall = self.superview.bounds.size.height - self.ballViewSize.height/2 - targetWidthOfSmallBall/2 - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
targetYOfSmallBall = self.ballViewSize.height/2 - targetWidthOfSmallBall/2 + self.verticalMargin;
}
self.smallBall.frame = CGRectMake(targetXOfSmallBall, targetYOfSmallBall, targetWidthOfSmallBall, targetWidthOfSmallBall);
self.smallBall.layer.cornerRadius = targetWidthOfSmallBall/2;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.shapeLayer.path = [self getBezierPathWithSmallCir:self.smallBall andBigCir:self.bigBall].CGPath;
[CATransaction commit];
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.ballViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.ballViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
CGRect toFrame = CGRectMake(x, y, self.ballViewSize.width, self.ballViewSize.height);
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.smallBall.frame = toFrame;
self.bigBall.frame = toFrame;
self.smallBall.layer.cornerRadius = self.ballViewSize.height/2;
self.bigBall.layer.cornerRadius = self.ballViewSize.height/2;
} completion:^(BOOL finished) {
}];
}else {
self.smallBall.frame = toFrame;
self.bigBall.frame = toFrame;
self.smallBall.layer.cornerRadius = self.ballViewSize.height/2;
self.bigBall.layer.cornerRadius = self.ballViewSize.height/2;
}
}
- (UIBezierPath *)getBezierPathWithSmallCir:(UIView *)smallCir andBigCir:(UIView *)bigCir{
// 获取最小的圆
if (bigCir.frame.size.width < smallCir.frame.size.width) {
UIView *view = bigCir;
bigCir = smallCir;
smallCir = view;
}
// 获取小圆的信息
CGFloat d = self.bigBall.center.x - self.smallBall.center.x;
if (d == 0) {
return nil;
}
CGFloat x1 = smallCir.center.x;
CGFloat y1 = smallCir.center.y;
CGFloat r1 = smallCir.bounds.size.width/2;
// 获取大圆的信息
CGFloat x2 = bigCir.center.x;
CGFloat y2 = bigCir.center.y;
CGFloat r2 = bigCir.bounds.size.width/2;
// 获取三角函数
CGFloat sinA = (y2 - y1)/d;
CGFloat cosA = (x2 - x1)/d;
// 获取矩形四个点
CGPoint pointA = CGPointMake(x1 - sinA*r1, y1 + cosA * r1);
CGPoint pointB = CGPointMake(x1 + sinA*r1, y1 - cosA * r1);
CGPoint pointC = CGPointMake(x2 + sinA*r2, y2 - cosA * r2);
CGPoint pointD = CGPointMake(x2 - sinA*r2, y2 + cosA * r2);
// 获取控制点,以便画出曲线
CGPoint pointO = CGPointMake(pointA.x + d / 2 * cosA , pointA.y + d / 2 * sinA);
CGPoint pointP = CGPointMake(pointB.x + d / 2 * cosA , pointB.y + d / 2 * sinA);
// 创建路径
UIBezierPath *path =[UIBezierPath bezierPath];
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
[path addQuadCurveToPoint:pointC controlPoint:pointP];
[path addLineToPoint:pointD];
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
@end
//
// JXCategoryComponentBaseView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryIndicatorProtocol.h"
#import "JXCategoryViewDefines.h"
@interface JXCategoryIndicatorComponentView : UIView <JXCategoryIndicatorProtocol>
/**
指示器的位置。底部或者顶部
*/
@property (nonatomic, assign) JXCategoryComponentPosition componentPosition;
/**
默认JXCategoryViewAutomaticDimension(与cell的宽度相等)。内部通过`- (CGFloat)indicatorWidthValue:(CGRect)cellFrame`方法获取实际的值
*/
@property (nonatomic, assign) CGFloat indicatorWidth;
/**
指示器的宽度增量。比如需求是指示器宽度比cell宽度多10 point。就可以将该属性赋值为10。最终指示器的宽度=indicatorWidth+indicatorWidthIncrement
*/
@property (nonatomic, assign) CGFloat indicatorWidthIncrement;
/**
默认:3。内部通过`- (CGFloat)indicatorHeightValue:(CGRect)cellFrame`方法获取实际的值
*/
@property (nonatomic, assign) CGFloat indicatorHeight;
/**
默认JXCategoryViewAutomaticDimension (等于indicatorHeight/2)。内部通过`- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame`方法获取实际的值
*/
@property (nonatomic, assign) CGFloat indicatorCornerRadius;
/**
指示器的颜色
*/
@property (nonatomic, strong) UIColor *indicatorColor;
/**
垂直方向偏移。数值越大越靠近中心。默认:0。
*/
@property (nonatomic, assign) CGFloat verticalMargin;
/**
手势滚动、点击切换的时候,是否允许滚动,默认YES
*/
@property (nonatomic, assign, getter=isScrollEnabled) BOOL scrollEnabled;
/**
手势滚动、点击切换的时候,如果允许滚动,分为简单滚动和复杂滚动。默认为:JXCategoryIndicatorScrollStyleSimple
目前仅JXCategoryIndicatorLineView、JXCategoryIndicatorDotLineView支持,其他子类暂不支持。
*/
@property (nonatomic, assign) JXCategoryIndicatorScrollStyle scrollStyle;
/**
滚动动画的时间。默认0.25
*/
@property (nonatomic, assign) NSTimeInterval scrollAnimationDuration;
/**
传入cellFrame获取指示器的最终宽度
@param cellFrame cellFrame
@return 指示器的最终宽度
*/
- (CGFloat)indicatorWidthValue:(CGRect)cellFrame;
/**
传入cellFrame获取指示器的最终高度
@param cellFrame cellFrame
@return 指示器的最终高度
*/
- (CGFloat)indicatorHeightValue:(CGRect)cellFrame;
/**
传入cellFrame获取指示器的最终圆角
@param cellFrame cellFrame
@return 指示器的最终圆角
*/
- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame;
@end
//
// JXCategoryComponentBaseView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@implementation JXCategoryIndicatorComponentView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_componentPosition = JXCategoryComponentPosition_Bottom;
_scrollEnabled = YES;
_verticalMargin = 0;
_scrollAnimationDuration = 0.25;
_indicatorWidth = JXCategoryViewAutomaticDimension;
_indicatorWidthIncrement = 0;
_indicatorHeight = 3;
_indicatorCornerRadius = JXCategoryViewAutomaticDimension;
_indicatorColor = [UIColor redColor];
_scrollStyle = JXCategoryIndicatorScrollStyleSimple;
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
NSAssert(NO, @"Use initWithFrame");
}
return self;
}
- (CGFloat)indicatorWidthValue:(CGRect)cellFrame {
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
return cellFrame.size.width + self.indicatorWidthIncrement;
}
return self.indicatorWidth + self.indicatorWidthIncrement;
}
- (CGFloat)indicatorHeightValue:(CGRect)cellFrame {
if (self.indicatorHeight == JXCategoryViewAutomaticDimension) {
return cellFrame.size.height;
}
return self.indicatorHeight;
}
- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame {
if (self.indicatorCornerRadius == JXCategoryViewAutomaticDimension) {
return [self indicatorHeightValue:cellFrame]/2;
}
return self.indicatorCornerRadius;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
}
@end
//
// JXCategoryIndicatorImageView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorImageView : JXCategoryIndicatorComponentView
@property (nonatomic, strong, readonly) UIImageView *indicatorImageView;
@property (nonatomic, assign) BOOL indicatorImageViewRollEnabled; //默认NO
@property (nonatomic, assign) CGSize indicatorImageViewSize; //默认:CGSizeMake(30, 20)
@end
//
// JXCategoryIndicatorImageView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorImageView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorImageView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_indicatorImageViewSize = CGSizeMake(30, 20);
_indicatorImageViewRollEnabled = NO;
_indicatorImageView = [[UIImageView alloc] init];
self.indicatorImageView.frame = CGRectMake(0, 0, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
self.indicatorImageView.contentMode = UIViewContentModeScaleAspectFit;
[self addSubview:self.indicatorImageView];
}
return self;
}
- (void)setIndicatorImageViewSize:(CGSize)indicatorImageViewSize {
_indicatorImageViewSize = indicatorImageViewSize;
self.indicatorImageView.frame = CGRectMake(0, 0, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.indicatorImageViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.indicatorImageViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, self.indicatorImageViewSize.width, self.indicatorImageViewSize.height);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetWidth = self.indicatorImageViewSize.width;
CGFloat targetX = 0;
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - targetWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
self.frame = frame;
if (self.indicatorImageViewRollEnabled) {
self.indicatorImageView.transform = CGAffineTransformMakeRotation(M_PI*2*percent);
}
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect toFrame = self.frame;
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.indicatorImageViewSize.width)/2;
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
if (self.indicatorImageViewRollEnabled && model.isClicked) {
[self.indicatorImageView.layer removeAnimationForKey:@"rotate"];
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
if (model.selectedIndex > model.lastSelectedIndex) {
rotateAnimation.fromValue = @(0);
rotateAnimation.toValue = @(M_PI*2);
}else {
rotateAnimation.fromValue = @(M_PI*2);
rotateAnimation.toValue = @(0);
}
rotateAnimation.fillMode = kCAFillModeBackwards;
rotateAnimation.removedOnCompletion = YES;
rotateAnimation.duration = 0.25;
[self.indicatorImageView.layer addAnimation:rotateAnimation forKey:@"rotate"];
}
}else {
self.frame = toFrame;
}
}
@end
//
// JXCategoryIndicatorLineView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
typedef NS_ENUM(NSUInteger, JXCategoryIndicatorLineStyle) {
JXCategoryIndicatorLineStyle_Normal = 0,
JXCategoryIndicatorLineStyle_Lengthen = 1,
JXCategoryIndicatorLineStyle_LengthenOffset = 2,
};
@interface JXCategoryIndicatorLineView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) JXCategoryIndicatorLineStyle lineStyle;
/**
line滚动时x的偏移量,默认为10;
lineStyle为JXCategoryIndicatorLineStyle_LengthenOffset有用;
*/
@property (nonatomic, assign) CGFloat lineScrollOffsetX;
@end
//
// JXCategoryIndicatorLineView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorLineView.h"
#import "JXCategoryFactory.h"
#import "JXCategoryViewDefines.h"
#import "JXCategoryViewAnimator.h"
@interface JXCategoryIndicatorLineView ()
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
@end
@implementation JXCategoryIndicatorLineView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_lineStyle = JXCategoryIndicatorLineStyle_Normal;
_lineScrollOffsetX = 10;
self.indicatorHeight = 3;
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
self.backgroundColor = self.indicatorColor;
self.layer.cornerRadius = [self indicatorCornerRadiusValue:model.selectedCellFrame];
CGFloat selectedLineWidth = [self indicatorWidthValue:model.selectedCellFrame];
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - selectedLineWidth)/2;
CGFloat y = self.superview.bounds.size.height - [self indicatorHeightValue:model.selectedCellFrame] - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, selectedLineWidth, [self indicatorHeightValue:model.selectedCellFrame]);
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetX = leftCellFrame.origin.x;
CGFloat targetWidth = [self indicatorWidthValue:leftCellFrame];
CGFloat leftWidth = targetWidth;
CGFloat rightWidth = [self indicatorWidthValue:rightCellFrame];
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
if (self.lineStyle == JXCategoryIndicatorLineStyle_Normal) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:0];
NSLog(@"targetX--%.2f", targetX);
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:rightWidth percent:percent];
NSLog(@"targetWidth--%.2f", targetX);
}
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_Lengthen) {
CGFloat maxWidth = rightX - leftX + rightWidth;
//前50%,只增加width;后50%,移动x并减小width
if (percent <= 0.5) {
targetX = leftX;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
NSLog(@"targetX--%.2f", targetX);
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
NSLog(@"targetWidth--%.2f", targetWidth);
}
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_LengthenOffset) {
//前50%,增加width,并少量移动x;后50%,少量移动x并减小width
CGFloat offsetX = self.lineScrollOffsetX;//x的少量偏移量
CGFloat maxWidth = rightX - leftX + rightWidth - offsetX*2;
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:leftX + offsetX percent:percent*2];;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:(leftX + offsetX) to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
frame.size.width = targetWidth;
self.frame = frame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect targetIndicatorFrame = self.frame;
CGFloat targetIndicatorWidth = [self indicatorWidthValue:model.selectedCellFrame];
targetIndicatorFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - targetIndicatorWidth)/2.0;
targetIndicatorFrame.size.width = targetIndicatorWidth;
if (self.isScrollEnabled) {
if (self.scrollStyle == JXCategoryIndicatorScrollStyleSameAsUserScroll) {
if (self.animator.isExecuting) {
[self.animator invalid];
self.animator = nil;
}
CGFloat leftX = 0;
CGFloat rightX = 0;
CGFloat leftWidth = 0;
CGFloat rightWidth = 0;
BOOL isNeedReversePercent = NO;
if (self.frame.origin.x > model.selectedCellFrame.origin.x) {
leftWidth = [self indicatorWidthValue:model.selectedCellFrame];
rightWidth = self.frame.size.width;
leftX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - leftWidth)/2;;
rightX = self.frame.origin.x;
isNeedReversePercent = YES;
}else {
leftWidth = self.frame.size.width;
rightWidth = [self indicatorWidthValue:model.selectedCellFrame];
leftX = self.frame.origin.x;
rightX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - rightWidth)/2;
}
__weak typeof(self) weakSelf = self;
if (self.lineStyle == JXCategoryIndicatorLineStyle_Normal) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = targetIndicatorFrame;
} completion: nil];
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_Lengthen) {
CGFloat maxWidth = rightX - leftX + rightWidth;
//前50%,只增加width;后50%,移动x并减小width
self.animator = [[JXCategoryViewAnimator alloc] init];
self.animator.progressCallback = ^(CGFloat percent) {
if (isNeedReversePercent) {
percent = 1 - percent;
}
CGFloat targetX = 0;
CGFloat targetWidth = 0;
if (percent <= 0.5) {
targetX = leftX;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
CGRect toFrame = weakSelf.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
weakSelf.frame = toFrame;
};
[self.animator start];
}else if (self.lineStyle == JXCategoryIndicatorLineStyle_LengthenOffset) {
//前50%,增加width,并少量移动x;后50%,少量移动x并减小width
CGFloat offsetX = self.lineScrollOffsetX;//x的少量偏移量
CGFloat maxWidth = rightX - leftX + rightWidth - offsetX*2;
self.animator = [[JXCategoryViewAnimator alloc] init];
self.animator.progressCallback = ^(CGFloat percent) {
if (isNeedReversePercent) {
percent = 1 - percent;
}
CGFloat targetX = 0;
CGFloat targetWidth = 0;
if (percent <= 0.5) {
targetX = [JXCategoryFactory interpolationFrom:leftX to:leftX + offsetX percent:percent*2];;
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:maxWidth percent:percent*2];
}else {
targetX = [JXCategoryFactory interpolationFrom:(leftX + offsetX) to:rightX percent:(percent - 0.5)*2];
targetWidth = [JXCategoryFactory interpolationFrom:maxWidth to:rightWidth percent:(percent - 0.5)*2];
}
CGRect toFrame = weakSelf.frame;
toFrame.origin.x = targetX;
toFrame.size.width = targetWidth;
weakSelf.frame = toFrame;
};
[self.animator start];
}
}else if (self.scrollStyle == JXCategoryIndicatorScrollStyleSimple) {
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.frame = targetIndicatorFrame;
} completion: nil];
}
}else {
self.frame = targetIndicatorFrame;
}
}
@end
//
// JXCategoryIndicatorRainbowLineView.h
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorLineView.h"
NS_ASSUME_NONNULL_BEGIN
/**
会无视JXCategoryIndicatorLineView的indicatorLineViewColor属性,以indicatorColors为准
*/
@interface JXCategoryIndicatorRainbowLineView : JXCategoryIndicatorLineView
@property (nonatomic, strong) NSArray <UIColor *> *indicatorColors; //数量需要与cell的数量相等。没有提供默认值,必须要赋值该属性。categoryView在reloadData的时候,也要一并更新该属性,不然会出现数组越界。
@end
NS_ASSUME_NONNULL_END
//
// JXCategoryIndicatorRainbowLineView.m
// JXCategoryView
//
// Created by jiaxin on 2018/12/13.
// Copyright © 2018 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorRainbowLineView.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryIndicatorRainbowLineView
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
[super jx_refreshState:model];
UIColor *color = self.indicatorColors[model.selectedIndex];
self.backgroundColor = color;
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
[super jx_contentScrollViewDidScroll:model];
UIColor *leftColor = self.indicatorColors[model.leftIndex];
UIColor *rightColor = self.indicatorColors[model.rightIndex];
UIColor *color = [JXCategoryFactory interpolationColorFrom:leftColor to:rightColor percent:model.percent];
self.backgroundColor = color;
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
[super jx_selectedCell:model];
UIColor *color = self.indicatorColors[model.selectedIndex];
self.backgroundColor = color;
}
@end
//
// JXCategoryIndicatorTriangleView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorComponentView.h"
@interface JXCategoryIndicatorTriangleView : JXCategoryIndicatorComponentView
@property (nonatomic, assign) CGSize triangleViewSize; //默认:CGSizeMake(14, 10)
@property (nonatomic, strong) UIColor *triangleViewColor; //默认:[UIColor redColor]
@end
//
// JXCategoryIndicatorTriangleView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/17.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorTriangleView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorTriangleView ()
@property (nonatomic, strong) CAShapeLayer *triangleLayer;
@end
@implementation JXCategoryIndicatorTriangleView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_triangleViewSize = CGSizeMake(14, 10);
_triangleViewColor = [UIColor redColor];
_triangleLayer = [CAShapeLayer layer];
[self.layer addSublayer:self.triangleLayer];
}
return self;
}
#pragma mark - JXCategoryIndicatorProtocol
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.triangleViewSize.width)/2;
CGFloat y = self.superview.bounds.size.height - self.triangleViewSize.height - self.verticalMargin;
if (self.componentPosition == JXCategoryComponentPosition_Top) {
y = self.verticalMargin;
}
self.frame = CGRectMake(x, y, self.triangleViewSize.width, self.triangleViewSize.height);
[CATransaction begin];
[CATransaction setDisableActions:NO];
self.triangleLayer.fillColor = self.triangleViewColor.CGColor;
self.triangleLayer.frame = self.bounds;
UIBezierPath *path = [UIBezierPath bezierPath];
if (self.componentPosition == JXCategoryComponentPosition_Bottom) {
[path moveToPoint:CGPointMake(self.bounds.size.width/2, 0)];
[path addLineToPoint:CGPointMake(0, self.bounds.size.height)];
[path addLineToPoint:CGPointMake(self.bounds.size.width, self.bounds.size.height)];
}else {
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(self.bounds.size.width, 0)];
[path addLineToPoint:CGPointMake(self.bounds.size.width/2, self.bounds.size.height)];
}
[path closePath];
self.triangleLayer.path = path.CGPath;
[CATransaction commit];
}
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
CGRect rightCellFrame = model.rightCellFrame;
CGRect leftCellFrame = model.leftCellFrame;
CGFloat percent = model.percent;
CGFloat targetWidth = self.triangleViewSize.width;
CGFloat targetX = 0;
if (percent == 0) {
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
}else {
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2;
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - targetWidth)/2;
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
}
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
if (self.scrollEnabled == YES || (self.scrollEnabled == NO && percent == 0)) {
CGRect frame = self.frame;
frame.origin.x = targetX;
self.frame = frame;
}
}
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
CGRect toFrame = self.frame;
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - self.triangleViewSize.width)/2;
if (self.scrollEnabled) {
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.frame = toFrame;
} completion:^(BOOL finished) {
}];
}else {
self.frame = toFrame;
}
}
@end
//
// 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
//
// 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
- (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;
}
- (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;
}
- (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
//
// JXCategoryComponetCell.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryBaseCell.h"
@interface JXCategoryIndicatorCell : JXCategoryBaseCell
@end
//
// JXCategoryComponetCell.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorCell.h"
#import "JXCategoryIndicatorCellModel.h"
@interface JXCategoryIndicatorCell ()
@property (nonatomic, strong) UIView *separatorLine;
@end
@implementation JXCategoryIndicatorCell
- (void)initializeViews
{
[super initializeViews];
self.separatorLine = [[UIView alloc] init];
self.separatorLine.hidden = YES;
[self.contentView addSubview:self.separatorLine];
}
- (void)layoutSubviews
{
[super layoutSubviews];
JXCategoryIndicatorCellModel *model = (JXCategoryIndicatorCellModel *)self.cellModel;
CGFloat lineWidth = model.separatorLineSize.width;
CGFloat lineHeight = model.separatorLineSize.height;
self.separatorLine.frame = CGRectMake(self.bounds.size.width - lineWidth + self.cellModel.cellSpacing/2, (self.bounds.size.height - lineHeight)/2.0, lineWidth, lineHeight);
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryIndicatorCellModel *model = (JXCategoryIndicatorCellModel *)cellModel;
self.separatorLine.backgroundColor = model.separatorLineColor;
self.separatorLine.hidden = !model.sepratorLineShowEnabled;
if (model.cellBackgroundColorGradientEnabled) {
if (model.selected) {
self.contentView.backgroundColor = model.cellBackgroundSelectedColor;
}else {
self.contentView.backgroundColor = model.cellBackgroundUnselectedColor;
}
}
}
@end
//
// JXCategoryComponentCellModel.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXCategoryBaseCellModel.h"
@interface JXCategoryIndicatorCellModel : JXCategoryBaseCellModel
@property (nonatomic, assign) BOOL sepratorLineShowEnabled;
@property (nonatomic, strong) UIColor *separatorLineColor;
@property (nonatomic, assign) CGSize separatorLineSize;
@property (nonatomic, assign) CGRect backgroundViewMaskFrame;
@property (nonatomic, assign) BOOL cellBackgroundColorGradientEnabled;
@property (nonatomic, strong) UIColor *cellBackgroundUnselectedColor;
@property (nonatomic, strong) UIColor *cellBackgroundSelectedColor;
@end
//
// JXCategoryComponentCellModel.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorCellModel.h"
@implementation JXCategoryIndicatorCellModel
@end
//
// JXCategoryComponentView.h
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryBaseView.h"
#import "JXCategoryIndicatorCell.h"
#import "JXCategoryIndicatorCellModel.h"
#import "JXCategoryIndicatorProtocol.h"
@interface JXCategoryIndicatorView : JXCategoryBaseView
@property (nonatomic, strong) NSArray <UIView<JXCategoryIndicatorProtocol> *> *indicators;
//----------------------ellBackgroundColor-----------------------//
@property (nonatomic, assign) BOOL cellBackgroundColorGradientEnabled; //默认:NO
@property (nonatomic, strong) UIColor *cellBackgroundUnselectedColor; //默认:[UIColor clearColor]
@property (nonatomic, strong) UIColor *cellBackgroundSelectedColor; //默认:[UIColor grayColor]
//----------------------separatorLine-----------------------//
@property (nonatomic, assign) BOOL separatorLineShowEnabled; //默认为NO
@property (nonatomic, strong) UIColor *separatorLineColor; //默认为[UIColor lightGrayColor]
@property (nonatomic, assign) CGSize separatorLineSize; //默认为CGSizeMake(1/[UIScreen mainScreen].scale, 20)
/**
当contentScrollView滚动时候,处理跟随手势的过渡效果。
根据cellModel的左右位置、是否选中、ratio进行过滤数据计算。
@param leftCellModel 左边的cellModel
@param rightCellModel 右边的cellModel
@param ratio 从左往右方向计算的百分比
*/
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio NS_REQUIRES_SUPER;
@end
#import "JXCategoryBaseView.h"
#import "JXCategoryIndicatorView.h"
#import "JXCategoryTitleView.h"
#import "JXCategoryImageView.h"
#import "JXCategoryTitleImageView.h"
#import "JXCategoryNumberView.h"
#import "JXCategoryDotView.h"
#import "JXCategoryFactory.h"
#import "JXCategoryIndicatorProtocol.h"
#import "JXCategoryViewDefines.h"
#import "JXCategoryListVCContainerView.h"
#import "JXCategoryListContainerView.h"
#import "JXCategoryIndicatorComponentView.h"
#import "JXCategoryIndicatorLineView.h"
#import "JXCategoryIndicatorTriangleView.h"
#import "JXCategoryIndicatorImageView.h"
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryIndicatorBallView.h"
#import "JXCategoryIndicatorRainbowLineView.h"
//
// JXCategoryNumberCell.h
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryTitleCell.h"
@interface JXCategoryNumberCell : JXCategoryTitleCell
@property (nonatomic, strong) UILabel *numberLabel;
@end
//
// JXCategoryNumberCell.m
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryNumberCell.h"
#import "JXCategoryNumberCellModel.h"
@interface JXCategoryNumberCell ()
@end
@implementation JXCategoryNumberCell
- (void)initializeViews {
[super initializeViews];
self.numberLabel = ({
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.layer.masksToBounds = YES;
label;
});
[self.contentView addSubview:self.numberLabel];
}
- (void)layoutSubviews {
[super layoutSubviews];
[self.numberLabel sizeToFit];
JXCategoryNumberCellModel *myCellModel = (JXCategoryNumberCellModel *)self.cellModel;
self.numberLabel.bounds = CGRectMake(0, 0, self.numberLabel.bounds.size.width + myCellModel.numberLabelWidthIncrement, myCellModel.numberLabelHeight);
self.numberLabel.layer.cornerRadius = myCellModel.numberLabelHeight/2.0;
self.numberLabel.center = CGPointMake(CGRectGetMaxX(self.titleLabel.frame), CGRectGetMinY(self.titleLabel.frame));
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryNumberCellModel *myCellModel = (JXCategoryNumberCellModel *)cellModel;
self.numberLabel.hidden = myCellModel.count == 0;
self.numberLabel.backgroundColor = myCellModel.numberBackgroundColor;
self.numberLabel.font = myCellModel.numberLabelFont;
self.numberLabel.textColor = myCellModel.numberTitleColor;
self.numberLabel.text = [NSString stringWithFormat:@"%ld", (long)myCellModel.count];
if (myCellModel.count >= 1000) {
self.numberLabel.text = @"999+";
}
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryNumberCellModel.h
// DQGuess
//
// Created by jiaxin on 2018/4/24.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
@interface JXCategoryNumberCellModel : JXCategoryTitleCellModel
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) UIColor *numberBackgroundColor;
@property (nonatomic, strong) UIColor *numberTitleColor;
@property (nonatomic, assign) CGFloat numberLabelWidthIncrement;
@property (nonatomic, assign) CGFloat numberLabelHeight;
@property (nonatomic, strong) UIFont *numberLabelFont;
@end
//
// JXCategoryNumberCellModel.m
// DQGuess
//
// Created by jiaxin on 2018/4/24.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryNumberCellModel.h"
@implementation JXCategoryNumberCellModel
@end
//
// JXCategoryNumberView.h
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryNumberCell.h"
#import "JXCategoryNumberCellModel.h"
@interface JXCategoryNumberView : JXCategoryTitleView
/**
需要与titles的count对应
*/
@property (nonatomic, strong) NSArray <NSNumber *> *counts;
/**
numberLabel的font,默认:[UIFont systemFontOfSize:11]
*/
@property (nonatomic, strong) UIFont *numberLabelFont;
/**
数字的背景色,默认:[UIColor colorWithRed:241/255.0 green:147/255.0 blue:95/255.0 alpha:1]
*/
@property (nonatomic, strong) UIColor *numberBackgroundColor;
/**
数字的title颜色,默认:[UIColor whiteColor]
*/
@property (nonatomic, strong) UIColor *numberTitleColor;
/**
numberLabel的宽度补偿,label真实的宽度是文字内容的宽度加上补偿的宽度,默认:10
*/
@property (nonatomic, assign) CGFloat numberLabelWidthIncrement;
/**
numberLabel的高度,默认:14
*/
@property (nonatomic, assign) CGFloat numberLabelHeight;
@end
//
// JXCategoryNumberView.m
// DQGuess
//
// Created by jiaxin on 2018/4/9.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryNumberView.h"
@implementation JXCategoryNumberView
- (void)initializeData {
[super initializeData];
self.cellSpacing = 25;
_numberTitleColor = [UIColor whiteColor];
_numberBackgroundColor = [UIColor colorWithRed:241/255.0 green:147/255.0 blue:95/255.0 alpha:1];
_numberLabelHeight = 14;
_numberLabelWidthIncrement = 10;
_numberLabelFont = [UIFont systemFontOfSize:11];
}
- (Class)preferredCellClass {
return [JXCategoryNumberCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryNumberCellModel *cellModel = [[JXCategoryNumberCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryNumberCellModel *myCellModel = (JXCategoryNumberCellModel *)cellModel;
myCellModel.count = [self.counts[index] integerValue];
myCellModel.numberBackgroundColor = self.numberBackgroundColor;
myCellModel.numberTitleColor = self.numberTitleColor;
myCellModel.numberLabelHeight = self.numberLabelHeight;
myCellModel.numberLabelWidthIncrement = self.numberLabelWidthIncrement;
myCellModel.numberLabelFont = self.numberLabelFont;
}
@end
//
// JXCategoryTitleCell.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCell.h"
@interface JXCategoryTitleCell : JXCategoryIndicatorCell
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *maskTitleLabel;
@end
//
// JXCategoryTitleCell.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCell.h"
#import "JXCategoryTitleCellModel.h"
@interface JXCategoryTitleCell ()
@property (nonatomic, strong) CALayer *maskLayer;
@end
@implementation JXCategoryTitleCell
- (void)initializeViews
{
[super initializeViews];
_titleLabel = [[UILabel alloc] init];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:self.titleLabel];
_maskTitleLabel = [[UILabel alloc] init];
_maskTitleLabel.hidden = YES;
self.maskTitleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:self.maskTitleLabel];
_maskLayer = [CALayer layer];
self.maskLayer.backgroundColor = [UIColor redColor].CGColor;
self.maskTitleLabel.layer.mask = self.maskLayer;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.titleLabel.center = self.contentView.center;
self.maskTitleLabel.center = self.contentView.center;
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryTitleCellModel *myCellModel = (JXCategoryTitleCellModel *)cellModel;
CGFloat pointSize = myCellModel.titleFont.pointSize;
UIFontDescriptor *fontDescriptor = myCellModel.titleFont.fontDescriptor;
if (myCellModel.selected) {
fontDescriptor = myCellModel.titleSelectedFont.fontDescriptor;
pointSize = myCellModel.titleSelectedFont.pointSize;
}
if (myCellModel.titleLabelZoomEnabled) {
self.titleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize*myCellModel.titleLabelZoomScale];
self.maskTitleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize*myCellModel.titleLabelZoomScale];
}else {
self.titleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize];
self.maskTitleLabel.font = [UIFont fontWithDescriptor:fontDescriptor size:pointSize];
}
NSString *titleString = myCellModel.title ? myCellModel.title : @"";
NSMutableAttributedString *attriString = [[NSMutableAttributedString alloc] initWithString:titleString];
if (myCellModel.titleLabelStrokeWidthEnabled) {
[attriString addAttribute:NSStrokeWidthAttributeName value:@(myCellModel.titleLabelSelectedStrokeWidth) range:NSMakeRange(0, myCellModel.title.length)];
}
self.maskTitleLabel.hidden = !myCellModel.titleLabelMaskEnabled;
if (myCellModel.titleLabelMaskEnabled) {
self.titleLabel.textColor = myCellModel.titleColor;
self.maskTitleLabel.font = myCellModel.titleFont;
self.maskTitleLabel.textColor = myCellModel.titleSelectedColor;
self.maskTitleLabel.attributedText = attriString;
[self.maskTitleLabel sizeToFit];
CGRect frame = myCellModel.backgroundViewMaskFrame;
frame.origin.x -= (self.contentView.bounds.size.width - self.maskTitleLabel.bounds.size.width)/2;
frame.origin.y = 0;
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.maskLayer.frame = frame;
[CATransaction commit];
}else {
if (myCellModel.selected) {
self.titleLabel.textColor = myCellModel.titleSelectedColor;
}else {
self.titleLabel.textColor = myCellModel.titleColor;
}
}
self.titleLabel.attributedText = attriString;
[self.titleLabel sizeToFit];
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryTitleCellModel.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorCellModel.h"
#import <UIKit/UIKit.h>
@interface JXCategoryTitleCellModel : JXCategoryIndicatorCellModel
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) UIColor *titleColor;
@property (nonatomic, strong) UIColor *titleSelectedColor;
@property (nonatomic, strong) UIFont *titleFont;
@property (nonatomic, strong) UIFont *titleSelectedFont;
@property (nonatomic, assign) BOOL titleLabelMaskEnabled;
@property (nonatomic, strong) CALayer *backgroundEllipseLayer;
@property (nonatomic, assign) BOOL titleLabelZoomEnabled;
@property (nonatomic, assign) CGFloat titleLabelZoomScale;
@property (nonatomic, assign) CGFloat titleLabelStrokeWidthEnabled;
@property (nonatomic, assign) CGFloat titleLabelSelectedStrokeWidth;
@end
//
// JXCategoryTitleCellModel.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
@implementation JXCategoryTitleCellModel
@end
//
// JXCategoryView.h
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryIndicatorView.h"
#import "JXCategoryTitleCell.h"
#import "JXCategoryTitleCellModel.h"
@interface JXCategoryTitleView : JXCategoryIndicatorView
@property (nonatomic, strong) NSArray <NSString *>*titles;
@property (nonatomic, strong) UIColor *titleColor; //默认:[UIColor blackColor]
@property (nonatomic, strong) UIColor *titleSelectedColor; //默认:[UIColor redColor]
@property (nonatomic, strong) UIFont *titleFont; //默认:[UIFont systemFontOfSize:15]
@property (nonatomic, strong) UIFont *titleSelectedFont; //文字被选中的字体。默认:与titleFont一样
@property (nonatomic, assign) BOOL titleColorGradientEnabled; //默认:NO,title的颜色是否渐变过渡
@property (nonatomic, assign) BOOL titleLabelMaskEnabled; //默认:NO,titleLabel是否遮罩过滤。(需要backgroundEllipseLayerShowEnabled = YES)
//----------------------titleLabelZoomEnabled-----------------------//
@property (nonatomic, assign) BOOL titleLabelZoomEnabled; //默认为NO
@property (nonatomic, assign) BOOL titleLabelZoomScrollGradientEnabled; //手势滚动中,是否需要更新状态。默认为YES
@property (nonatomic, assign) CGFloat titleLabelZoomScale; //默认1.2,titleLabelZoomEnabled为YES才生效
//----------------------titleLabelStrokeWidth-----------------------//
@property (nonatomic, assign) CGFloat titleLabelStrokeWidthEnabled; //默认:NO
@property (nonatomic, assign) CGFloat titleLabelSelectedStrokeWidth; //默认:-3,用于控制字体的粗细(底层通过NSStrokeWidthAttributeName实现)。使用该属性,务必让titleFont和titleSelectedFont设置为一样的!!!
@end
//
// JXCategoryView.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryTitleView ()
@end
@implementation JXCategoryTitleView
- (void)initializeData
{
[super initializeData];
_titleLabelZoomEnabled = NO;
_titleLabelZoomScale = 1.2;
_titleColor = [UIColor blackColor];
_titleSelectedColor = [UIColor redColor];
_titleFont = [UIFont systemFontOfSize:15];
_titleColorGradientEnabled = NO;
_titleLabelMaskEnabled = NO;
_titleLabelZoomScrollGradientEnabled = YES;
_titleLabelStrokeWidthEnabled = NO;
_titleLabelSelectedStrokeWidth = -3;
}
- (UIFont *)titleSelectedFont {
if (_titleSelectedFont != nil) {
return _titleSelectedFont;
}
return self.titleFont;
}
#pragma mark - Override
- (Class)preferredCellClass {
return [JXCategoryTitleCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryTitleCellModel *cellModel = [[JXCategoryTitleCellModel alloc] init];
[tempArray addObject:cellModel];
}
self.dataSource = tempArray;
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryTitleCellModel *myUnselectedCellModel = (JXCategoryTitleCellModel *)unselectedCellModel;
myUnselectedCellModel.titleColor = self.titleColor;
myUnselectedCellModel.titleSelectedColor = self.titleSelectedColor;
myUnselectedCellModel.titleLabelZoomScale = 1.0;
myUnselectedCellModel.titleLabelSelectedStrokeWidth = 0;
JXCategoryTitleCellModel *myselectedCellModel = (JXCategoryTitleCellModel *)selectedCellModel;
myselectedCellModel.titleColor = self.titleColor;
myselectedCellModel.titleSelectedColor = self.titleSelectedColor;
myselectedCellModel.titleLabelZoomScale = self.titleLabelZoomScale;
myselectedCellModel.titleLabelSelectedStrokeWidth = self.titleLabelSelectedStrokeWidth;
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
[super refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:ratio];
JXCategoryTitleCellModel *leftModel = (JXCategoryTitleCellModel *)leftCellModel;
JXCategoryTitleCellModel *rightModel = (JXCategoryTitleCellModel *)rightCellModel;
if (self.titleLabelZoomEnabled && self.titleLabelZoomScrollGradientEnabled) {
leftModel.titleLabelZoomScale = [JXCategoryFactory interpolationFrom:self.titleLabelZoomScale to:1.0 percent:ratio];
rightModel.titleLabelZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.titleLabelZoomScale percent:ratio];
}
if (self.titleLabelStrokeWidthEnabled) {
leftModel.titleLabelSelectedStrokeWidth = [JXCategoryFactory interpolationFrom:self.titleLabelSelectedStrokeWidth to:0 percent:ratio];
rightModel.titleLabelSelectedStrokeWidth = [JXCategoryFactory interpolationFrom:0 to:self.titleLabelSelectedStrokeWidth percent:ratio];
}
if (self.titleColorGradientEnabled) {
//处理颜色渐变
if (leftModel.selected) {
leftModel.titleSelectedColor = [JXCategoryFactory interpolationColorFrom:self.titleSelectedColor to:self.titleColor percent:ratio];
leftModel.titleColor = self.titleColor;
}else {
leftModel.titleColor = [JXCategoryFactory interpolationColorFrom:self.titleSelectedColor to:self.titleColor percent:ratio];
leftModel.titleSelectedColor = self.titleSelectedColor;
}
if (rightModel.selected) {
rightModel.titleSelectedColor = [JXCategoryFactory interpolationColorFrom:self.titleColor to:self.titleSelectedColor percent:ratio];
rightModel.titleColor = self.titleColor;
}else {
rightModel.titleColor = [JXCategoryFactory interpolationColorFrom:self.titleColor to:self.titleSelectedColor percent:ratio];
rightModel.titleSelectedColor = self.titleSelectedColor;
}
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
if (self.cellWidth == JXCategoryViewAutomaticDimension) {
return ceilf([self.titles[index] boundingRectWithSize:CGSizeMake(MAXFLOAT, self.bounds.size.height) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : self.titleFont} context:nil].size.width);
}else {
return self.cellWidth;
}
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryTitleCellModel *model = (JXCategoryTitleCellModel *)cellModel;
model.titleFont = self.titleFont;
model.titleSelectedFont = self.titleSelectedFont;
model.titleColor = self.titleColor;
model.titleSelectedColor = self.titleSelectedColor;
model.title = self.titles[index];
model.titleLabelMaskEnabled = self.titleLabelMaskEnabled;
model.titleLabelZoomEnabled = self.titleLabelZoomEnabled;
model.titleLabelZoomScale = 1.0;
model.titleLabelStrokeWidthEnabled = self.titleLabelStrokeWidthEnabled;
model.titleLabelSelectedStrokeWidth = 0;
if (index == self.selectedIndex) {
model.titleLabelZoomScale = self.titleLabelZoomScale;
model.titleLabelSelectedStrokeWidth = self.titleLabelSelectedStrokeWidth;
}
}
@end
//
// JXCategoryImageCell.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCell.h"
@interface JXCategoryTitleImageCell : JXCategoryTitleCell
@property (nonatomic, strong) UIImageView *imageView;
@end
//
// JXCategoryImageCell.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleImageCell.h"
#import "JXCategoryTitleImageCellModel.h"
@interface JXCategoryTitleImageCell()
@end
@implementation JXCategoryTitleImageCell
- (void)initializeViews {
[super initializeViews];
_imageView = [[UIImageView alloc] init];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:_imageView];
}
- (void)layoutSubviews {
[super layoutSubviews];
JXCategoryTitleImageCellModel *myCellModel = (JXCategoryTitleImageCellModel *)self.cellModel;
self.titleLabel.hidden = NO;
self.imageView.hidden = NO;
CGSize imageSize = myCellModel.imageSize;
self.imageView.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
switch (myCellModel.imageType) {
case JXCategoryTitleImageType_TopImage:
{
CGFloat contentHeight = imageSize.height + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.height;
self.imageView.center = CGPointMake(self.contentView.center.x, (self.contentView.bounds.size.height - contentHeight)/2 + imageSize.height/2);
self.titleLabel.center = CGPointMake(self.contentView.center.x, CGRectGetMaxY(self.imageView.frame) + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.height/2);
}
break;
case JXCategoryTitleImageType_LeftImage:
{
CGFloat contentWidth = imageSize.width + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.width;
self.imageView.center = CGPointMake((self.contentView.bounds.size.width - contentWidth)/2 + imageSize.width/2, self.contentView.center.y);
self.titleLabel.center = CGPointMake(CGRectGetMaxX(self.imageView.frame) + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.width/2, self.contentView.center.y);
}
break;
case JXCategoryTitleImageType_BottomImage:
{
CGFloat contentHeight = imageSize.height + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.height;
self.titleLabel.center = CGPointMake(self.contentView.center.x, (self.contentView.bounds.size.height - contentHeight)/2 + self.titleLabel.bounds.size.height/2);
self.imageView.center = CGPointMake(self.contentView.center.x, CGRectGetMaxY(self.titleLabel.frame) + myCellModel.titleImageSpacing + imageSize.height/2);
}
break;
case JXCategoryTitleImageType_RightImage:
{
CGFloat contentWidth = imageSize.width + myCellModel.titleImageSpacing + self.titleLabel.bounds.size.width;
self.titleLabel.center = CGPointMake((self.contentView.bounds.size.width - contentWidth)/2 + self.titleLabel.bounds.size.width/2, self.contentView.center.y);
self.imageView.center = CGPointMake(CGRectGetMaxX(self.titleLabel.frame) + myCellModel.titleImageSpacing + imageSize.width/2, self.contentView.center.y);
}
break;
case JXCategoryTitleImageType_OnlyImage:
{
self.titleLabel.hidden = YES;
self.imageView.center = self.contentView.center;
}
break;
case JXCategoryTitleImageType_OnlyTitle:
{
self.imageView.hidden = YES;
self.titleLabel.center = self.contentView.center;
}
break;
default:
break;
}
}
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
[super reloadData:cellModel];
JXCategoryTitleImageCellModel *myCellModel = (JXCategoryTitleImageCellModel *)cellModel;
if (myCellModel.imageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.imageName];
}else if (myCellModel.imageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.imageURL);
}
}
if (myCellModel.selected) {
if (myCellModel.selectedImageName != nil) {
self.imageView.image = [UIImage imageNamed:myCellModel.selectedImageName];
}else if (myCellModel.selectedImageURL != nil) {
if (myCellModel.loadImageCallback != nil) {
myCellModel.loadImageCallback(self.imageView, myCellModel.selectedImageURL);
}
}
}
if (myCellModel.imageZoomEnabled) {
self.imageView.transform = CGAffineTransformMakeScale(myCellModel.imageZoomScale, myCellModel.imageZoomScale);
}else {
self.imageView.transform = CGAffineTransformIdentity;
}
[self setNeedsLayout];
[self layoutIfNeeded];
}
@end
//
// JXCategoryTitleImageCellModel.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleCellModel.h"
typedef NS_ENUM(NSUInteger, JXCategoryTitleImageType) {
JXCategoryTitleImageType_TopImage = 0,
JXCategoryTitleImageType_LeftImage,
JXCategoryTitleImageType_BottomImage,
JXCategoryTitleImageType_RightImage,
JXCategoryTitleImageType_OnlyImage,
JXCategoryTitleImageType_OnlyTitle,
};
@interface JXCategoryTitleImageCellModel : JXCategoryTitleCellModel
@property (nonatomic, assign) JXCategoryTitleImageType imageType;
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL);
@property (nonatomic, copy) NSString *imageName; //加载bundle内的图片
@property (nonatomic, strong) NSURL *imageURL; //图片URL
@property (nonatomic, copy) NSString *selectedImageName;
@property (nonatomic, strong) NSURL *selectedImageURL;
@property (nonatomic, assign) CGSize imageSize; //默认CGSizeMake(20, 20)
@property (nonatomic, assign) CGFloat titleImageSpacing; //titleLabel和ImageView的间距,默认5
@property (nonatomic, assign) BOOL imageZoomEnabled;
@property (nonatomic, assign) CGFloat imageZoomScale;
@end
//
// JXCategoryTitleImageCellModel.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleImageCellModel.h"
@implementation JXCategoryTitleImageCellModel
@end
//
// JXCategoryImageView.h
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleView.h"
#import "JXCategoryTitleImageCell.h"
#import "JXCategoryTitleImageCellModel.h"
@interface JXCategoryTitleImageView : JXCategoryTitleView
@property (nonatomic, strong) NSArray <NSString *>*imageNames;
@property (nonatomic, strong) NSArray <NSURL *>*imageURLs;
@property (nonatomic, strong) NSArray <NSString *>*selectedImageNames;
@property (nonatomic, strong) NSArray <NSURL *>*selectedImageURLs;
@property (nonatomic, strong) NSArray <NSNumber *> *imageTypes; //默认JXCategoryTitleImageType_LeftImage
@property (nonatomic, copy) void(^loadImageCallback)(UIImageView *imageView, NSURL *imageURL); //使用imageURL从远端下载图片进行加载,建议使用SDWebImage等第三方库进行下载。
@property (nonatomic, assign) CGSize imageSize; //默认CGSizeMake(20, 20)
@property (nonatomic, assign) CGFloat titleImageSpacing; //titleLabel和ImageView的间距,默认5
@property (nonatomic, assign) BOOL imageZoomEnabled; //默认为NO
@property (nonatomic, assign) CGFloat imageZoomScale; //默认1.2,imageZoomEnabled为YES才生效
@end
//
// JXCategoryImageView.m
// JXCategoryView
//
// Created by jiaxin on 2018/8/8.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryTitleImageView.h"
#import "JXCategoryTitleImageCell.h"
#import "JXCategoryTitleImageCellModel.h"
#import "JXCategoryFactory.h"
@implementation JXCategoryTitleImageView
- (void)dealloc
{
self.loadImageCallback = nil;
}
- (void)initializeData {
[super initializeData];
_imageSize = CGSizeMake(20, 20);
_titleImageSpacing = 5;
_imageZoomEnabled = NO;
_imageZoomScale = 1.2;
}
- (Class)preferredCellClass {
return [JXCategoryTitleImageCell class];
}
- (void)refreshDataSource {
NSMutableArray *tempArray = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
JXCategoryTitleImageCellModel *cellModel = [[JXCategoryTitleImageCellModel alloc] init];
[tempArray addObject:cellModel];
}
if (self.imageTypes == nil || self.imageTypes.count == 0) {
NSMutableArray *types = [NSMutableArray array];
for (int i = 0; i < self.titles.count; i++) {
[types addObject:@(JXCategoryTitleImageType_LeftImage)];
}
self.imageTypes = types;
}
self.dataSource = tempArray;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
[super refreshCellModel:cellModel index:index];
JXCategoryTitleImageCellModel *myCellModel = (JXCategoryTitleImageCellModel *)cellModel;
myCellModel.loadImageCallback = self.loadImageCallback;
myCellModel.imageType = [self.imageTypes[index] integerValue];
myCellModel.imageSize = self.imageSize;
myCellModel.titleImageSpacing = self.titleImageSpacing;
if (self.imageNames != nil) {
myCellModel.imageName = self.imageNames[index];
}else if (self.imageURLs != nil) {
myCellModel.imageURL = self.imageURLs[index];
}
if (self.selectedImageNames != nil) {
myCellModel.selectedImageName = self.selectedImageNames[index];
}else if (self.selectedImageURLs != nil) {
myCellModel.selectedImageURL = self.selectedImageURLs[index];
}
myCellModel.imageZoomEnabled = self.imageZoomEnabled;
myCellModel.imageZoomScale = 1.0;
if (index == self.selectedIndex) {
myCellModel.imageZoomScale = self.imageZoomScale;
}
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryTitleImageCellModel *myUnselectedCellModel = (JXCategoryTitleImageCellModel *)unselectedCellModel;
myUnselectedCellModel.imageZoomScale = 1.0;
JXCategoryTitleImageCellModel *myselectedCellModel = (JXCategoryTitleImageCellModel *)selectedCellModel;
myselectedCellModel.imageZoomScale = self.imageZoomScale;
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
[super refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:ratio];
JXCategoryTitleImageCellModel *leftModel = (JXCategoryTitleImageCellModel *)leftCellModel;
JXCategoryTitleImageCellModel *rightModel = (JXCategoryTitleImageCellModel *)rightCellModel;
if (self.imageZoomEnabled) {
leftModel.imageZoomScale = [JXCategoryFactory interpolationFrom:self.imageZoomScale to:1.0 percent:ratio];
rightModel.imageZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.imageZoomScale percent:ratio];
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
CGFloat titleWidth = [super preferredCellWidthAtIndex:index];
JXCategoryTitleImageType type = [self.imageTypes[index] integerValue];
CGFloat cellWidth = 0;
switch (type) {
case JXCategoryTitleImageType_OnlyTitle:
cellWidth = titleWidth;
break;
case JXCategoryTitleImageType_OnlyImage:
cellWidth = self.imageSize.width;
break;
case JXCategoryTitleImageType_LeftImage:
case JXCategoryTitleImageType_RightImage:
cellWidth = titleWidth + self.titleImageSpacing + self.imageSize.width;
break;
case JXCategoryTitleImageType_TopImage:
case JXCategoryTitleImageType_BottomImage:
cellWidth = MAX(titleWidth, self.imageSize.width);
break;
}
return cellWidth;
}
@end
//
// JXPagerCollectionViewCell.h
// Gengmei
//
// Created by 卢悦明 on 2019/5/15.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface JXPagerCollectionViewCell : UICollectionViewCell
@property (nonatomic, strong) UIView *backView;
@end
NS_ASSUME_NONNULL_END
//
// JXPagerCollectionViewCell.m
// Gengmei
//
// Created by 卢悦明 on 2019/5/15.
// Copyright © 2019 更美互动信息科技有限公司. All rights reserved.
//
#import "JXPagerCollectionViewCell.h"
@implementation JXPagerCollectionViewCell
- (void)setBackView:(UIView *)backView
{
if (backView!=_backView) {
[_backView removeFromSuperview];
_backView = backView;
[self addSubview:_backView];
}
}
- (void)layoutSubviews {
self.backView.frame = self.bounds;
}
@end
//
// JXPagingListContainerView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXPagerMainTableView;
@class JXPagerListContainerView;
@class JXPagerListContainerCollectionView;
@protocol JXPagerListContainerCollectionViewGestureDelegate <NSObject>
- (BOOL)pagerListContainerCollectionViewGestureRecognizerShouldBegin:(JXPagerListContainerCollectionView *)collectionView gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;
@end
@interface JXPagerListContainerCollectionView: UICollectionView<UIGestureRecognizerDelegate>
@property (nonatomic, assign) BOOL isNestEnabled;
@property (nonatomic, weak) id<JXPagerListContainerCollectionViewGestureDelegate> gestureDelegate;
@end
@protocol JXPagerListContainerViewDelegate <NSObject>
- (NSInteger)numberOfRowsInListContainerView:(JXPagerListContainerView *)listContainerView;
- (UIView *)listContainerView:(JXPagerListContainerView *)listContainerView listViewInRow:(NSInteger)row;
- (void)listContainerView:(JXPagerListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row;
- (void)listContainerView:(JXPagerListContainerView *)listContainerView didScroll:(NSInteger)row;
@end
@interface JXPagerListContainerView : UIView
@property (nonatomic, strong, readonly) JXPagerListContainerCollectionView *collectionView;
@property (nonatomic, weak) id<JXPagerListContainerViewDelegate> delegate;
@property (nonatomic, weak) JXPagerMainTableView *mainTableView;
- (instancetype)initWithDelegate:(id<JXPagerListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)reloadData;
@end
//
// JXPagerListContainerView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPagerListContainerView.h"
#import "JXPagerMainTableView.h"
@import GMKit;
@interface JXPagerListContainerView() <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) JXPagerListContainerCollectionView *collectionView;
@property (nonatomic, strong) UICollectionViewFlowLayout *layout;
@end
@implementation JXPagerListContainerView
-(instancetype)initWithDelegate:(id<JXPagerListContainerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
[self initializeViews];
}
return self;
}
- (void)initializeViews {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.layout = layout;
_collectionView = [[JXPagerListContainerCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout];
self.collectionView.showsHorizontalScrollIndicator = NO;
self.collectionView.showsVerticalScrollIndicator = NO;
self.collectionView.pagingEnabled = YES;
self.collectionView.scrollsToTop = NO;
self.collectionView.bounces = NO;
self.collectionView.delegate = self;
self.collectionView.dataSource = 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 addSubview:self.collectionView];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.collectionView.frame = self.bounds;
self.layout.itemSize = self.bounds.size;
}
- (void)reloadData {
[self.collectionView reloadData];
}
#pragma mark - UICollectionViewDataSource, UICollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.delegate numberOfRowsInListContainerView:self];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
UIView *listView = [self.delegate listContainerView:self listViewInRow:indexPath.item];
listView.frame = cell.contentView.bounds;
[cell.contentView addSubview:listView];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[self.delegate listContainerView:self willDisplayCellAtRow:indexPath.item];
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
return false;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
if ([self.delegate respondsToSelector:@selector(listContainerView:didScroll:)]) {
NSInteger index = (self.collectionView.contentOffset.x / MAINSCREEN_WIDTH + 0.5);
[self.delegate listContainerView:self didScroll:index];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if ([self.delegate respondsToSelector:@selector(listContainerView:didScroll:)]) {
NSInteger index = (self.collectionView.contentOffset.x / MAINSCREEN_WIDTH + 0.5);
[self.delegate listContainerView:self didScroll:index];
}
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.isTracking || scrollView.isDecelerating) {
self.mainTableView.scrollEnabled = NO;
}
}
@end
@interface JXPagerListContainerCollectionView ()
@end
@implementation JXPagerListContainerCollectionView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.gestureDelegate) {
return [self.gestureDelegate pagerListContainerCollectionViewGestureRecognizerShouldBegin:self gestureRecognizer:gestureRecognizer];
}else {
if (self.isNestEnabled) {
if ([gestureRecognizer isMemberOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")]) {
CGFloat velocityX = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:gestureRecognizer.view].x;
//x大于0就是往右滑
if (velocityX > 0) {
if (self.contentOffset.x == 0) {
return NO;
}
}else if (velocityX < 0) {
//x小于0就是往左滑
if (self.contentOffset.x + self.bounds.size.width == self.contentSize.width) {
return NO;
}
}
}
}
}
return YES;
}
@end
//
// 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
//
// 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
- (void)initializeViews {
[super initializeViews];
self.mainTableView.bounces = NO;
}
- (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.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView的header还没有消失,让listScrollView一直为0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
self.currentScrollingListView.contentOffset = CGPointZero;
self.currentScrollingListView.showsVerticalScrollIndicator = false;
}
}
}
if (shouldProcess) {
if (self.mainTableView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//处于下拉刷新的状态,scrollView.contentOffset.y为负数,就重置为0
if (self.currentScrollingListView.contentOffset.y > 0) {
//mainTableView的header还没有消失,让listScrollView一直为0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
self.currentScrollingListView.contentOffset = CGPointZero;
self.currentScrollingListView.showsVerticalScrollIndicator = false;
}
} else {
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
self.currentScrollingListView.showsVerticalScrollIndicator = true;
}
}
self.lastScrollingListViewContentOffsetY = self.currentScrollingListView.contentOffset.y;
}
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView {
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
//mainTableView的header已经滚动不见,开始滚动某一个listView,那么固定mainTableView的contentOffset,让其不动
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
}
if (scrollView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView已经显示了header,listView的contentOffset需要重置
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
//正在下拉刷新时,不需要重置
UIScrollView *listScrollView = [list listScrollView];
if (listScrollView.contentOffset.y > 0) {
if ([list respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[list listScrollViewWillResetContentOffset];
}
listScrollView.contentOffset = CGPointZero;
}
}
}
}
@end
//
// 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
//
// 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
//
// 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;
/**
该协议主要用于mainTableView已经显示了header,listView的contentOffset需要重置时,内部需要访问到外部传入进来的listView内的scrollView
*/
@protocol JXPagerViewListViewDelegate <NSObject>
/**
返回listView。如果是vc包裹的就是vc.view;如果是自定义view包裹的,就是自定义view自己。
@return UIView
*/
- (UIView *)listView;
/**
返回listView内部持有的UIScrollView或UITableView或UICollectionView
主要用于mainTableView已经显示了header,listView的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
/**
将要重置listScrollView的contentOffset
*/
- (void)listScrollViewWillResetContentOffset;
@end
@protocol JXPagerViewDelegate <NSObject>
/**
返回tableHeaderView的高度,因为内部需要比对判断,只能是整型数
@param pagerView pagerView description
@return return tableHeaderView的高度
*/
- (NSUInteger)tableHeaderViewHeightInPagerView:(JXPagerView *)pagerView;
/**
返回tableHeaderView
@param pagerView pagerView description
@return tableHeaderView
*/
- (UIView *)tableHeaderViewInPagerView:(JXPagerView *)pagerView;
/**
返回悬浮HeaderView的高度,因为内部需要比对判断,只能是整型数
@param pagerView pagerView description
@return 悬浮HeaderView的高度
*/
- (NSUInteger)heightForPinSectionHeaderInPagerView:(JXPagerView *)pagerView;
/**
返回悬浮HeaderView。我用的是自己封装的JXCategoryView(Github:https://github.com/pujiaxin33/JXCategoryView),你也可以选择其他的三方库或者自己写
@param pagerView pagerView description
@return 悬浮HeaderView
*/
- (UIView *)viewForPinSectionHeaderInPagerView:(JXPagerView *)pagerView;
/**
返回列表的数量
@param pagerView pagerView description
@return 列表的数量
*/
- (NSInteger)numberOfListsInPagerView:(JXPagerView *)pagerView;
/**
根据index初始化一个对应列表实例。注意:一定要是新生成的实例!!!
只要遵循JXPagerViewListViewDelegate即可,无论你返回的是UIView还是UIViewController都可以。
@param pagerView pagerView description
@param index index description
@return 新生成的列表实例
*/
- (id<JXPagerViewListViewDelegate>)pagerView:(JXPagerView *)pagerView initListAtIndex:(NSInteger)index;
- (void)pagerView:(JXPagerView *)pagerView didScroll:(NSInteger)row;
@optional
/**
返回tableHeaderView 距离pagerView顶部的距离 默认为 0
*/
- (CGFloat)tableViewSectionHeaderViewContentYInPagerView:(JXPagerView *)pagerView;
/**
mainTableView的滚动回调,用于实现头图跟随缩放
@param scrollView mainTableView
*/
- (void)mainTableViewDidScroll:(UIScrollView *)scrollView;
- (void)mainTableViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)mainTableViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)mainTableViewWillBeginDragging:(UIScrollView *)scrollView;
/**
@param 由于 mainTableViewDidScroll 滚动偏移量返回的最大值为headerView的高度, 这个偏移量加上子ScrollView的偏移量。
*/
- (void)listAndMainScrollViewDidScroll:(CGFloat)contentOffsetY;
@end
@interface JXPagerView : UIView
@property (nonatomic, weak) id<JXPagerViewDelegate> delegate;
@property (nonatomic, strong) JXPagerMainTableView *mainTableView;
@property (nonatomic, strong, readonly) JXPagerListContainerView *listContainerView;
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@property (nonatomic, assign) BOOL isListHorizontalScrollEnabled; //是否允许列表左右滑动。默认:YES
@property (nonatomic, assign, readonly) CGFloat mainViewScrollOffsetY;
@property (nonatomic, assign, readonly) BOOL isListScroll;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)initializeViews NS_REQUIRES_SUPER;
- (void)reloadData;
- (void)refreshTableHeaderView;
#pragma mark - Subclass
@property (nonatomic, strong, readonly) UIScrollView *currentScrollingListView; //暴露给子类使用,请勿直接使用该属性!
@property (nonatomic, weak, readonly) id<JXPagerViewListViewDelegate> currentList; //暴露给子类使用,请勿直接使用该属性!
@property (nonatomic, strong, readonly) NSDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict; //暴露给子类使用,请勿直接使用该属性!当前已经加载过可用的列表字典,key就是index值,value是对应的列表。
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView;
- (void)preferredProcessMainTableViewDidScroll:(UIScrollView *)scrollView;
@end
This diff is collapsed.
//
// JXPagingListContainerView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@class JXPageListMainTableView;
@class JXPageListContainerView;
@protocol JXPageListContainerViewDelegate <NSObject>
- (NSInteger)numberOfRowsInListContainerView:(JXPageListContainerView *)listContainerView;
- (UIView *)listContainerView:(JXPageListContainerView *)listContainerView listViewInRow:(NSInteger)row;
- (void)listContainerView:(JXPageListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row;
@end
@interface JXPageListContainerView : UIView
@property (nonatomic, strong, readonly) UICollectionView *collectionView;
@property (nonatomic, weak) id<JXPageListContainerViewDelegate> delegate;
@property (nonatomic, weak) JXPageListMainTableView *mainTableView;
- (instancetype)initWithDelegate:(id<JXPageListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (void)reloadData;
@end
//
// JXPagerListContainerView.m
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPageListContainerView.h"
#import "JXPageListMainTableView.h"
@interface JXPageListContainerView() <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@end
@implementation JXPageListContainerView
- (instancetype)initWithDelegate:(id<JXPageListContainerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
[self initializeViews];
}
return self;
}
- (void)initializeViews {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
self.collectionView.showsHorizontalScrollIndicator = NO;
self.collectionView.showsVerticalScrollIndicator = NO;
self.collectionView.pagingEnabled = YES;
self.collectionView.bounces = NO;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
[self addSubview:self.collectionView];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.collectionView.frame = self.bounds;
}
- (void)reloadData {
[self.collectionView reloadData];
}
#pragma mark - UICollectionViewDataSource, UICollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.delegate numberOfRowsInListContainerView:self];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
UIView *listView = [self.delegate listContainerView:self listViewInRow:indexPath.item];
listView.frame = cell.contentView.bounds;
[cell.contentView addSubview:listView];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[self.delegate listContainerView:self willDisplayCellAtRow:indexPath.item];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
self.mainTableView.scrollEnabled = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.isTracking || scrollView.isDecelerating) {
self.mainTableView.scrollEnabled = NO;
}
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.bounds.size;
}
@end
//
// JXPagingMainTableView.h
// JXPagingView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface JXPageListMainTableView : UITableView
@end
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment