Commit f3583eb0 authored by luyueming's avatar luyueming

删除文件

parent 1826794b
//
// GMBaseWebViewController.h
// ZhengXing
//
// Created by wangyang on 3/5/19.
// Copyright (c) 2019 Wanmei Creative. All rights reserved.
// 后期替代WMBaseViewController&WMBrowserViewController
#define kWebPhantom @"phantom"// 常用于H5新页面
#define kWebHybrid @"hybrid" // 常用于H5老页面
#import "GMShareView.h"
#import "WMShareObject.h"
#import "GMDiaryShareView.h"
#import "GMClientH5Object.h"
#import "GMWebViewModel.h"
typedef NS_ENUM(NSInteger, GMWebPageType) {
GMWebPageTypeNomal = 0,
GMWebPageTypeSign = 1, // 签到H5页面
GMWebPageTypeTopicDetail = 2,// 日记贴
GMWebPageTypeAnswer = 3,// 回答
GMWebPageTypeArticle = 4,// 话题
GMWebPageTypeSpecial = 5,// 专栏
GMWebPageTypeUserTopic = 6,// 用户贴
GMWebPageTypeQuestion = 7,// 回答 7.20 灰度用
GMWebPageTypeHospitalRank = 8,// 医院榜单
GMWebPageTypeDoctorRank = 9,// 医生榜单
};
@import GMKit;
@class GMWebViewComponent;
@interface GMBaseWebViewController : WMBaseViewController<GMClientH5BridgeDelegate> {
/**
* @brief 下面都是从js中得到的数据。其中_jsGlobalObject字典包含其它数据
*/
NSDictionary *_jsGlobalObject;
// 是否显示了分享
BOOL _isShowShare;
// 是否收藏
BOOL _isFavored;
// 是否是自己发的贴
BOOL _isPrivate;
// 是否是签到,签到导航栏透明,返回按钮、title颜色、rightIcon单独设置
BOOL _isSign;
}
@property (nonatomic, strong) GMWebViewComponent *webCompent;
/// web上数据处理
@property (nonatomic, strong) GMWebViewModel *viewModel;
/**
* @brief 要请求的 H5 url 除了 http://backend.dev.gengmei.cc/hybrid" 这部分后的path。比如一个h5是 http://backend.dev.gengmei.cc/hybrid/topic/3332214,那么path指需要赋值@"/topic/3332214"即可.
@note 在viewDidLoad前配置好。必须以"/"开头。重写moreQueryParameters可以添加额外query
*/
@property (nonatomic, strong) NSString *path;
/**
* @brief 对于从服务器那儿获取到的完整H5 url,使用该属性。
@note 在viewDidLoad前配置好。会覆盖path属性。重写moreQueryParameters可以添加额外query
*/
@property (nonatomic, strong) NSString *fullURL;
/// 通过webPageType 页面不同设置不同的差异性
@property (nonatomic, assign) GMWebPageType webPageType;
#pragma mark - 分享
@property (nonatomic, strong) GMShareView *shareView;
@property (nonatomic, strong) GMDiaryShareView *diaryShareView;
/*** @brief 收藏url**/
@property (nonatomic,strong) NSString *favUrl;
/*** @brief 从_jsGlobalObject中获取的分享Model */
@property (nonatomic, strong) WMShareObject *shareObject;
/*** @brief 日记分享Model数组 */
@property (nonatomic, strong) NSMutableArray *diaryShareArr;
/*** @brief 微信分享Model */
@property (nonatomic, strong) WMShareObject *weixinShareObject;
/*** @brief 分享的配置 */
@property (nonatomic, strong) GMShareConfigObject *shareConfigObject;
@property (nonatomic , strong) NSString *favorType;
/**
设置打开的权限类型
*/
@property (nonatomic, assign) NSInteger settingType;
@property (nonatomic, assign) BOOL backNeedRefresh;
/**
* @brief 重写该方法,为fullURL在请求前添加更多参数。必须以 & 开头
* 原方法只返回了空字符串,不需要call super
*
* @return 要额外添加的参数。如:&key1=value&key2=value
*/
- (NSString *)moreQueryParameters;
/**
调用该方法以重新构造URL并且[_webView loadRequest:request];
*/
- (void)reloadURL;
/**
处理请求URL
*/
- (void)handleURL;
// 一般情况下 页面刷新时候调用
- (void)refreshWebView;
// 本地获取通用数据
- (NSString *)nativeCommonParam;
@end
//
// WMDiaryAndTopicBaseController.m
// ZhengXing
//
// Created by wangyang on 3/5/15.
// Copyright (c) 2015 Wanmei Creative. All rights reserved.
//
#import "GMBaseWebViewController.h"
#import "AppDelegate+AppInit.h"
#import "Gengmei-Swift.h"
#import "UIViewController+UrlScheme.h"
#import "WMBaseViewController+OCNavigationBar.h"
#import <mach/mach_time.h>
#import "GMCookieManager.h"
#import "GMWebViewUrlProtocol.h"
#import "GMWeixinShareView.h"
#import "WMShareObject.h"
@interface GMBaseWebViewController ()<MFMessageComposeViewControllerDelegate, GMCommonAlertViewDelegate,GMPhotoPickDismissDelegate,WKWebViewDelegate,GMShareViewDelegate, GMDiaryShareViewDelegate>
{
// 是否显示微信快照和朋友圈快照
BOOL _isShowScreenshot;
GMAddressManager *_manager;
// 是否需要回调权限打开状态
BOOL _isSettingCallback;
// 表示要上传的照片是可以公开的还是具体保护性的
BOOL _isPrivatePhoto;
// 接受h5的参数,图片上传成功后,以这个参数为key,把图片Url返回给h5
NSString *_requestCoder;
// 是否显示了来自h5设置的rightButton
BOOL _isJsRightButton;
BOOL _isJsNearRightButton;
// 导航栏是否透明,YES透明,NO不透明
BOOL _isNavigationAlpha;
// 导航栏是否隐藏
BOOL _isNavigationHidden;
}
@property (nonatomic, assign) CGFloat animationDistance;
@property (nonatomic, strong) GMPlayerView *playBackView;
@property (nonatomic, assign) BOOL goToAppSettingsPage;// 跳转到系统设置页面
@end
@implementation GMBaseWebViewController
#pragma mark - init
- (void)initController {
[super initController];
self.viewModel = [GMWebViewModel new];
self.goToAppSettingsPage = NO;
self.webPageType = GMWebPageTypeNomal;
// 短信链接唤起app埋点 @author 李震(WMBrowserViewController)
if ([AppDelegate shareInstance].extraParam.isNonEmpty) {
self.extraParam = [AppDelegate shareInstance].extraParam;
[AppDelegate shareInstance].extraParam = @"";
}
}
#pragma mark - view lifeCycle
- (void)viewDidLoad {
[super viewDidLoad];
[self initReferer];
[self initRefererLink];
[self initReferrerTabName];
self.navigationBar.isShowShadow = NO;
self.backNeedRefresh = NO;
CGFloat top = OCNavigationBar.barHeight;
[self.webCompent mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(0);
make.top.mas_equalTo(top);
make.bottom.mas_equalTo(0);
}];
// 设置Agent,关键字是最后的那个Gengmei,这样服务端就知道是通过app内部加载网页。
NSDictionary *dictionary = USER_AGENT_DIC;
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
// 加载URL
[self reloadURL];
// 添加通知监听
[self addActiveObserver];
// 导航栏渐变
[self prepareNavigation];
}
/// 通过webPageType 页面不同设置不同的差异性
/// @param webPageType webPageType
- (void)setWebPageType:(GMWebPageType)webPageType {
_webPageType = webPageType;
switch (webPageType) {
case GMWebPageTypeSign:
self.pageName = @"new_sign";
break;
case GMWebPageTypeQuestion:
{
self.pageName = @"question_answer_detail";
}
break;
default:
break;
}
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self nativeAppearToCallJS];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.backNeedRefresh) {
[self refreshWebView];
self.backNeedRefresh = NO;
}
[self nativeDisAppearToCallJS];
}
#pragma mark - Load Request
- (void)reloadURL {
[self handleURL];
[self webViewLoadRequest];
}
- (void)webViewLoadRequest {
if (self.fullURL == nil || [self.fullURL isEqualToString:@""]) {
return;
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.webCompent.fullUrl = self.fullURL;
// self.webCompent.fullUrl = @"http://172.30.8.83:3000/phantom/question/3482021";
[self.webCompent webviewLoad:self];
}
// 一般情况下 页面刷新时候调用
- (void)refreshWebView {
//自己本地调用webview刷新时候,绑定关系还存在,不需要重新绑定GMClient和window上的方法.
[self.webCompent reload];
}
#pragma mark - Handle URL
/**
不允许重写
*/
- (void)handleURL {
if (_fullURL.length == 0) {
// 使用path拼接,并且加上query
_fullURL = [self wrapWebUrlWithPath];
} else {
// 为fullURL加上默认query
_fullURL = [self wrapWebUrlWithFullURL];
}
if (![_fullURL isNonEmpty]) return;
// 额外的参数
_fullURL = [_fullURL stringByAppendingString:[self moreQueryParameters]];
/// 添加referrer_link
_fullURL = [self addReferrerLinkWithFullURL];
self.webCompent.fullUrl = _fullURL;
}
/**
不允许重写
*/
- (NSString *)wrapWebUrlWithPath {
if (![_path isNonEmpty]) {
return @"";
}
NSString *path = self.path;
NSString *parames = [NSString stringWithString:GMNetworking.urlCommonParameters];
if ([path containsString:@"?"]) {
parames = [parames stringByReplacingOccurrencesOfString:@"?" withString:@"&"];
}
NSString *wrapedUrl = [NSString stringWithFormat:@"%@%@%@", GMServerDomains.apiHost, path,parames];
// 兼容新老页面,在此认为新的没有host域名的h5页面默认携带phantom,而不进行拼接
NSArray *pathArray = [path componentsSeparatedByString:@"/"];
if (![pathArray containsObject:kWebPhantom] && ![pathArray containsObject:kWebHybrid]) {
wrapedUrl = [NSString stringWithFormat:@"%@/%@%@%@", GMServerDomains.apiHost, kWebHybrid, path, parames];
}
return wrapedUrl;
}
/**
不允许重写
*/
- (NSString *)wrapWebUrlWithFullURL {
// 因为有的 fullURL 带了一部分 query,而将要拼接的urlCommonParameters还带了一个问号,所以要做判断
NSString *defaultQuery = GMNetworking.urlCommonParameters;
if ([_fullURL rangeOfString:@"?"].length > 0) {
defaultQuery = [defaultQuery stringByReplacingOccurrencesOfString:@"?" withString:@"&"];
}
NSString *wrapedUrl = [NSString stringWithFormat:@"%@%@", _fullURL, defaultQuery];
return wrapedUrl;
}
/// 添加referrer_link 不允许重写
- (NSString *)addReferrerLinkWithFullURL {
NSString *referrerLink = @"";
NSDictionary *param = [_fullURL urlQueryToDictionary];
if (![param.allKeys containsObject:@"referrer_link"]) {
BOOL aiConsultGray = GMLaunchManager.shareManager.appConfigObject.aiConsultGray;
NSString *referrerLinkString = [self.referrerLink componentsJoinedByString:@","];
referrerLink = [NSString stringWithFormat:@"&ai_consult_gray=%@&referrer_link=%@",(aiConsultGray ? @"1" : @"0"),referrerLinkString];
}
return [NSString stringWithFormat:@"%@%@", _fullURL, referrerLink];
}
- (NSString *)moreQueryParameters {
return @"";
}
- (void)setFullURL:(NSString *)fullURL {
_fullURL = fullURL;
debugLog(@"%@", _fullURL);
NSDictionary *params = [fullURL urlQueryToDictionary];
if ([params.allKeys containsObject:@"referrer"]) {
self.referer = params[@"referrer"];
}
// cpc 相关
if ([params.allKeys containsObject:@"cpc_referer"]) {
self.cpc_referer = [params[@"cpc_referer"] integerValue];
}
if ([params.allKeys containsObject:@"is_cpc"]) {
self.is_cpc = [params[@"is_cpc"] boolValue];
}
// navigation_color_clear: 1透明,0不透明
if ([params[@"navigation_color_clear"] intValue]) {
_isNavigationAlpha = YES;
// referer: push:表示来源为推送;pop_ad:表示来源开屏小广告(没有倒计时);big_ad:表示来源开屏大广告(有倒计时)
self.navigationBar.leftIcon = @"back_circle_white";
self.navigationBar.buttonMargin = 10;
}
// 设置导航栏是否隐藏
_isNavigationHidden = [params[@"navigation_hidden"] boolValue];
}
#pragma mark - 分享 GMShareViewDelegate
- (void)willShowShareView:(GMShareView *)shareView {
shareView.hasFav = _isFavored;
shareView.showDelete = _isPrivate;
shareView.showScreenshot = _isShowScreenshot;
// 连续签到,补签分享,隐藏复制链接、举报、刷新
shareView.showReport = ![_shareConfigObject.hideReport boolValue];
shareView.showCopyLink = ![_shareConfigObject.hideCopyLink boolValue];
shareView.showRefresh = ![_shareConfigObject.hideRefresh boolValue];
if ([_shareConfigObject.hideReport boolValue] && [_shareConfigObject.hideCopyLink boolValue] && [_shareConfigObject.hideRefresh boolValue]) {
shareView.topLineView.hidden = YES;
} else {
shareView.topLineView.hidden = NO;
}
shareView.taskType = _shareConfigObject.taskType;
shareView.isReportShareResult = _shareConfigObject.isReportShareResult;
shareView.shareObject = _shareObject;
}
- (NSString *)shareViewNeedBusinessId {
return self.businessId;
}
- (void)shareResponseSuccess {
[self.webCompent.webView evaluateJavaScript:_shareConfigObject.shareCallback completionHandler:nil];
}
/**@brief 获取分享内容的协议方法需在子类实现,因为需要定制,删除和收藏的协议方法在子类实现即可*/
- (void)shareView:(GMShareView *)shareView favriteContentWithIndexPath:(NSIndexPath *)indexPath {
if ([[GMLoginManager shareInstance] showLoginViewIfNeeded]){
return;
}
GMHTTPMethod method = _isFavored ? GMHTTPMethodDelete : GMHTTPMethodPost;
__weak __typeof(self)weakSelf = self;
[GMNetworking requestOCWithApi:self.favUrl method:method parameters:nil completion:^(GMResponseOC * _Nonnull responseObject) {
if (responseObject.isSuccess) {
_isFavored = !_isFavored;
shareView.hasFav = _isFavored;
NSString *action;
if (_isFavored) {
action = @"do";
[weakSelf showComplete:@"收藏成功"];
}else{
action = @"undo";
[weakSelf showComplete:@"取消收藏成功"];
}
// 分享的埋点
NSDictionary *dic = @{@"action": action,
@"type": weakSelf.favorType,
@"business_id": weakSelf.businessId};
[Phobos track:@"favor" attributes:dic];
[shareView.shareCollectionView reloadItemsAtIndexPaths:@[indexPath]];
} else {
[weakSelf showWarning:responseObject.message];
}
}];
}
#pragma mark - GMShareViewDelegate
- (void)copyShareUrl{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
if ([_shareObject.url isNonEmpty]) {
pasteboard.string = _shareObject.url;
[self toast:@"链接已复制到系统粘帖板"];
}
}
- (NSMutableDictionary *)fetchSharePublishContent:(GMSharePlatform)shareType{
//这里时间是native,所以只能弹普通的大分享
id shareImage = nil;
GMShareContentType contentType = GMShareContentTypeAuto;
WMShareBasicObject * object = [WMShareBasicObject new];
NSString *image = _shareObject.image;
if (image.length != 0) {
shareImage = [GMShareSDK compressWithUrl:image];
}else{
// 使用app logo做图标
shareImage = [UIImage imageNamed:@"icon"];
}
// 微信会话分享且微信小程序内容不为空的时候,认为是微信小程序分享
if (shareType == GMSharePlatformWechatSession && [_shareObject.wechatmini.path isNonEmpty]) {
shareType = GMSharePlatformWechatMiniProgram;
}
NSString *channel = @"";
switch (shareType) {
case GMSharePlatformWechatSession:
object = _shareObject.wechat;
channel = @"wechat";
break;
case GMSharePlatformWechatTimeline:
object = _shareObject.wechatline;
channel = @"wechatline";
break;
case GMSharePlatformQQFriend:
object = _shareObject.qq;
channel = @"qq";
break;
case GMSharePlatformQQSpace:
object = _shareObject.qq;
channel = @"qzone";
break;
case GMSharePlatformSinaWeibo:
object = _shareObject.weibo;
channel = @"tsina";
break;
case GMSharePlatformWechatSessionSnapShot:
object = _shareObject.wechatScreenshot;
contentType = GMShareContentTypeImage;
//这里不能压缩图片,要保证质量分享
shareImage = _shareObject.wechatScreenshot.content;
break;
case GMSharePlatformWechatTimelineSnapShot:
object = _shareObject.wechatlineScreenshot;
shareImage = _shareObject.wechatlineScreenshot.content;
contentType = GMShareContentTypeImage;
break;
default:
break;
}
if (_isSign) {
if ([_shareObject.url containsString:@"?"]) {
_shareObject.url = [NSString stringWithFormat:@"%@&referrer=%@",_shareObject.url, channel];
} else {
_shareObject.url = [NSString stringWithFormat:@"%@?referrer=%@",_shareObject.url, channel];
}
}
NSMutableDictionary *shareParams = [NSMutableDictionary dictionary];
if (shareType == GMSharePlatformWechatMiniProgram){
[shareParams shareSetupMiniProgramShareParamsByUrl:_shareObject.url
userName:_shareObject.wechatmini.userName
path:_shareObject.wechatmini.path
title:_shareObject.wechatmini.title
description:_shareObject.wechatmini.desc
thumbImage:_shareObject.wechatmini.thumbImageUrl
hdImageData:_shareObject.wechatmini.hdImageUrl];
} else {
[shareParams shareSetupShareParamsByText:object.content
images:shareImage
url:[NSURL URLWithString:_shareObject.url]
title:object.title
type:contentType];
}
return shareParams;
}
#pragma mark - GMClientH5BridgeDelegate
/**
* @brief pageData
*/
- (void)jsGlobalDataLoaded:(NSString *)jsonString {
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
_jsGlobalObject = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
if (_jsGlobalObject == nil) {
return;
}
// 从H5中取得全局变量
@try {
// JIRA 上曾有一个关于此处的bug上报,因为无法查明原因,所以加try catch
[self.webCompent.webView evaluateJavaScript:@"window.GLOBAL.pagedata" completionHandler:nil];
// 当前版本对h5返回businessid和pageName的处理,只在commonwebview中处理,basewebview中不处理
} @catch (NSException *exception) {
// [Bugly reportException:[NSException exceptionWithName:@"_jsGlobalObject获取异常" reason:exception.reason userInfo:@{@"exceptionInfo": exception.userInfo, @"url": self.fullURL}]];
}
if (_jsGlobalObject[@"page_name"]) {
self.pageName = _jsGlobalObject[@"page_name"];
}
if ([_jsGlobalObject[@"business_id"] isNonEmpty]) {
self.businessId = SafeString(_jsGlobalObject[@"business_id"]);
}
if ([_jsGlobalObject.allValues containsObject:@"back_need_refresh"]) {
_backNeedRefresh = [_jsGlobalObject[@"back_need_refresh"] boolValue];
}
// 是否收藏
_isFavored = [_jsGlobalObject[@"is_favord"] boolValue];
// 是否是自己的帖子
_isPrivate = [_jsGlobalObject[@"is_private"] boolValue];
//分享数据源
_shareObject = [[WMShareObject alloc] initWithDictionary:_jsGlobalObject[@"share_data"] error:nil];
_shareConfigObject = [[GMShareConfigObject alloc] initWithDictionary:_jsGlobalObject[@"share_config"] error:nil];
//同时不为空的时候才显示微信快照和朋友圈快照
_isShowScreenshot = [_shareObject.wechatScreenshot.content isNonEmpty] && [_shareObject.wechatScreenshot.content isNonEmpty];
// 是否隐藏分享按钮
if ([_jsGlobalObject.allKeys containsObject:@"hide_share"] &&
![_jsGlobalObject[@"hide_share"] boolValue] && _shareObject) {
_isShowShare = YES;
} else {
_isShowShare = NO;
}
_isSign = [_jsGlobalObject[@"navigation"][@"nav_type"] isEqualToString:@"sign"];
// 连续补签,分享日记
if ([_jsGlobalObject[@"topic_share_data"] count]) {
self.diaryShareArr = [NSMutableArray array];
[self.diaryShareArr addObjectsFromArray:[GMDiaryShareObject arrayOfModelsFromDictionaries:_jsGlobalObject[@"topic_share_data"] error:NULL]];
}
// 处理导航栏数据
[self dealNavigationData];
}
#pragma mark - Deal Navigation
// 导航栏隐藏显示
- (void)jsControlTitleBarVisible:(NSString *)jsonString {
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *showDic = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableLeaves
error:nil];
if ([showDic[@"show"] boolValue]) {
if (!_isSign) {
self.navigationBar.backgroundColor = [UIColor whiteColor];
self.navigationBar.isShowShadow = YES;
} else {
self.navigationBar.backgroundColor = [UIColor clearColor];
}
} else {
self.navigationBar.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.6];
self.navigationBar.isShowShadow = NO;
}
}
// 设置导航数据
- (void)dealNavigationData {
NSDictionary *navDict = _jsGlobalObject[@"navigation"];
if (navDict) {
// 右侧图标
NSString *rightIcon = navDict[@"right_button"];
if (rightIcon.isNonEmpty) {
self.navigationBar.rightIconURL = rightIcon;
}
// 右侧文案
NSString *rightText = navDict[@"right_text"];
if (rightText.isNonEmpty) {
self.navigationBar.rightTitle = rightText;
}
self.navigationBar.rightButton.hidden = _isNavigationAlpha;
// 签到
if (_isSign) {
[self.navigationBar.rightButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(0);
}];
}
// 设置导航栏是否透明
if ([navDict.allKeys containsObject:@"navigation_color_clear"]) {
_isNavigationAlpha = [navDict[@"navigation_color_clear"] intValue] ? YES : NO;
}
self.animationDistance = [navDict[@"header_height"] intValue];
// 设置导航栏是否隐藏
_isNavigationHidden = [navDict[@"navigation_hidden"] boolValue];
// 设置侧滑手势禁用
self.fd_interactivePopDisabled = [navDict[@"pop_disabled"] boolValue];
}
// 设置导航栏
[self prepareNavigation];
// 设置导航栏Title
[self setupNavigateTitle];
}
/// 设置导航栏Title
- (void)setupNavigateTitle {
// 设置导航栏
NSString *title = [_jsGlobalObject[@"name"] isNonEmpty] ? _jsGlobalObject[@"name"] : self.title;
if (_isShowShare) {
[self setNavigateTitle:title rightButtonOption:OCBarButtonImageTypeShare];
} else {
[self setNavigateTitle:title rightButtonOption:OCBarButtonImageTypeDefault];
}
}
/// 设置导航栏是否隐藏
- (void)prepareNavigation {
// navigation_color_clear: 1透明,0不透明
CGFloat top = (_isNavigationAlpha || _isNavigationHidden)?0:OCNavigationBar.barHeight;
if (_isNavigationAlpha) {
self.navigationBar.leftIcon = @"back_circle_white";
self.navigationBar.buttonMargin = 10;
self.webCompent.webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
self.webCompent.webView.scrollView.delegate = self;
[self scrollViewDidScroll:self.webCompent.webView.scrollView];
}
[self.webCompent mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(top);
}];
self.navigationBar.hidden = _isNavigationHidden;
}
/// 前端调用客户端方法进行返回操作
- (void)jsPopViewControllerAnimated:(BOOL)animated {
if ([self.parentViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigation = (UINavigationController *)self.parentViewController;
NSArray *controllers = navigation.viewControllers;
if (self.presentingViewController && controllers.count == 1) {
[self dismissViewControllerAnimated:YES completion:nil];
} else {
[self.navigationController popViewControllerAnimated:YES];
}
} else {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
#pragma mark - Deal Gload
/**
* @brief js调用该方法以hideLoading
*/
- (void)loadDomFinished {
[self hideLoading];
// 因为此时_jsGlobalObject还不能从H5中获取,所以只先设置一个title,与数据相关的分享功能先不展示
// self.title = [self.webCompent.webView evaluateJavaScript:@"document.title" completionHandler:nil];
[self.webCompent.webView evaluateJavaScript:@"document.title" completionHandler:^(id resonse, NSError * _Nullable error) {
NSLog(@"%@",resonse);
}];
}
- (void)jsShowLoading{
[self showLoading:nil];
}
- (void)jsHideLoading{
[self hideLoading];
}
- (void)jsLogout {
[[GMLoginManager shareInstance] doLogoutAndShowLoginView];
}
// 显示空白页面
- (void)jsShowNativeEmptyView {
[self showEmptyView:GMEmptyViewTypeException];
}
- (void)emptyViewDidClickReload {
[Phobos track:@"on_click_button" attributes:@{@"page_name": SafeString(self.pageName),
@"button_name": @"click_reload"}];
[self hideEmptyView];
[self refreshWebView];
}
// 网络状态 2: 其他 1:wifi 0:无网络
- (NSInteger)jsCheckNetworkState {
NSInteger networkState = 0;
if ([AFNetworkReachabilityManager sharedManager].isReachable) { // 联网状态
if ([AFNetworkReachabilityManager sharedManager].reachableViaWiFi == NO) { // 非WiFi
networkState = 2;
} else {
networkState = 1;
}
}
#warning h5
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('jsCheckNetworkStateCallback',%zd)",networkState];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
return networkState;
}
- (void)jsLocationShowAlertIfNotAllowed {
[GMLocationManager showAlertIfNotAllowed];
}
#pragma mark - ShareView
- (void)jsObjShowShareViewWithJSONString:(NSString *)JSONString{
if ([JSONString isNonEmpty]) {
_shareObject = [[WMShareObject alloc] initWithString:JSONString error:nil];
}
[self.shareView showWithShareUrl:_shareObject.url];
[self addShareTopView:self.shareView.topView];
}
- (void)jsObjShowDiaryShareViewWithJSONString:(NSString *)JSONString {
if ([JSONString isNonEmpty]) {
self.diaryShareArr = [NSMutableArray array];
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
NSArray *shareArr = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
[self.diaryShareArr addObjectsFromArray:[GMDiaryShareObject arrayOfModelsFromDictionaries:shareArr error:NULL]];
}
[self.diaryShareView showWithShareArr:self.diaryShareArr];
[self addShareTopView:self.diaryShareView.topView];
}
- (void)addShareTopView:(UIView *)topView {
// 连续签到,补签标题
[(GMShareTopView *)topView setName:_shareConfigObject.shareTitle title:_shareConfigObject.shareInfo subTitle:_shareConfigObject.shareTip];
if ([_shareConfigObject.shareTitle isNonEmpty] || [_shareConfigObject.shareInfo isNonEmpty] || [_shareConfigObject.shareTip isNonEmpty]) {
_shareView.topView.hidden = NO;
} else {
_shareView.topView.hidden = YES;
}
}
- (void)jsShowWXShareViewWithJSONString:(NSString *)JSONString{
if ([JSONString isNonEmpty]) {
_weixinShareObject = [[WMShareObject alloc] initWithString:JSONString error:nil];
}
if (_weixinShareObject != nil) {
GMWeixinShareView * weiXinShareView = [GMWeixinShareView shareView];
weiXinShareView.weixinShareObject = _weixinShareObject;
weiXinShareView.sourceController = self;
[self.view addSubview:weiXinShareView];
[weiXinShareView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view).insets(UIEdgeInsetsZero);
}];
[weiXinShareView layoutIfNeeded];
[weiXinShareView showShareView:nil];
}
}
// 根据type 展示跳转指定分享平台(V7.16.1)
- (void)jsSharewithType:(NSDictionary *)data {
NSString *type = data[@"type"]?:@"Wechat";
self.shareObject = [[WMShareObject alloc] initWithDictionary:data[@"share_data"] error:nil];
GMSharePlatform shareType = GMSharePlatformWechatTimeline;
if ([type isEqualToString:@"QZone"]) {
shareType = GMSharePlatformQQSpace;
}else if ([type isEqualToString:@"QQ"]) {
shareType = GMSharePlatformQQFriend;
}else if ([type isEqualToString:@"Wechat"]) {
shareType = GMSharePlatformWechatSession;
}else if ([type isEqualToString:@"WechatMoments"]) {
shareType = GMSharePlatformWechatTimeline;
}else if ([type isEqualToString:@"SinaWeibo"]) {
shareType = GMSharePlatformSinaWeibo;
}
self.shareView.shareUrl = self.shareObject.url;
[self.shareView shareWithType:shareType];
}
// 根据type 展示跳转指定分享平台(兼容之前的代码)
- (void)jsShare:(NSString *)shareDataString withType:(NSString *)type {
NSData *shareData = [shareDataString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *data = [NSJSONSerialization JSONObjectWithData:shareData options:NSJSONReadingAllowFragments error:NULL];
if ([type isNonEmpty]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:data];
dict[@"type"] = type;
data = dict;
}
[self jsSharewithType:data];
}
/*** 回放 */
- (void)jsPlayVideo:(NSString *)JSONString {
if (![JSONString isNonEmpty]) {
[self toast:@"视频无法播放"];
return;
}
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:NULL];
NSString *playFrom = jsonDict[@"page_name"];
NSString *topicId = jsonDict[@"topic_id"];
NSString *businessId = jsonDict[@"business_id"];
NSString *cardType = jsonDict[@"card_type"];
NSInteger index = [jsonDict[@"index"] integerValue];
CGFloat currentTime = [jsonDict[@"currentTime"] floatValue];
NSInteger state = [jsonDict[@"state"] integerValue];
GMVideoObject *video = [GMVideoObject new];
video.businessId = SafeString(businessId);
video.cardType = SafeString(cardType);
video.pageName = SafeString(playFrom);
_playBackView = [[GMPlayerView alloc] init];
_playBackView.topicId = SafeString(topicId);
_playBackView.from = SafeString(playFrom);
_playBackView.video = video;
_playBackView.needSkipSeconds = currentTime;
_playBackView.index = index;
_playBackView.playState = state;
NSString *playUrl = jsonDict[@"url"];
[_playBackView play:playUrl animationRect:CGRectZero];
}
/**
获取用户通讯录
@author zhaiguojun 16-09-21 in 6.3.2
@since 6.4.1
*/
- (void)jsGetAddressBook {
dispatch_async(dispatch_get_main_queue(), ^{
_manager = [GMAddressManager new];
[_manager getAddressBookAuthorization];
});
}
/**
弹出通讯录供用户选择
@author zhaiguojun 16-09-28 in 6.4.1
*/
- (void)jsChooseOneContactPerson:(NSString *)key {
_manager = [GMAddressManager new];
__weak __typeof(self)weakSelf = self;
_manager.getOneContactPersonBlock = ^(NSDictionary *result){
NSData *jsParamData = [NSJSONSerialization dataWithJSONObject:@{key:result} options:0 error:NULL];
NSString *jsParam = [[NSString alloc] initWithData:jsParamData encoding:NSUTF8StringEncoding];
NSString *jsString = [NSString stringWithFormat:@"gm.util.setContactPerson('%@')",jsParam];
[weakSelf.webCompent.webView evaluateJavaScript:jsString completionHandler:nil];
};
[_manager chooseContactPersonAddressBookJsonString];
}
#pragma mark - AlertView
- (void)jsShowTaskAlert:(NSString *)extra {
NSData *data = [extra dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *extraDic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
if (extraDic == nil) {
return;
}
[AppDelegate.shareInstance showPointAlert:extraDic];
}
- (void)jsShowAlertViewWithJSONString:(NSString *)JSONString {
WMAlertParameterObject *object = nil;
if ([JSONString isNonEmpty]) {
object = [[WMAlertParameterObject alloc] initWithString:JSONString error:nil];
}
__weak typeof(self)weakSelf = self;
NSString *title = object.title?:@"标题";
NSString *content = object.content?:@"内容";
NSString *confirmText = object.confirmText?:@"确定";
NSString *confirmCallback = object.confirmCallback?:@"";
UIAlertController *alert = [UIAlertController showCustomAlertWithTitle:title message:content];
[alert addAction:confirmText actionHandler:^{
[weakSelf.webCompent.webView evaluateJavaScript:confirmCallback completionHandler:nil];
}];
}
- (void)jsShowConfirmViewWithJSONString:(NSString *)JSONString{
WMAlertParameterObject *object = nil;
if ([JSONString isNonEmpty]) {
object = [[WMAlertParameterObject alloc] initWithString:JSONString error:nil];
}
NSString *title = object.title?:@"标题";
NSString *content = object.content?:@"内容";
NSString *confirmText = object.confirmText?:@"确定";
NSString *cancelText = object.cancelText?:@"取消";
NSString *confirmCallback = object.confirmCallback?:@"";
NSString *cancelCallback = object.cancelCallback?:@"";
__weak typeof(self)weakSelf = self;
UIAlertController *alert = [UIAlertController showCustomAlertWithTitle:title message:content];
[alert addAction:confirmText actionHandler:^{
[weakSelf.webCompent.webView evaluateJavaScript:confirmCallback completionHandler:nil];
}];
[alert addAction:cancelText actionHandler:^{
[weakSelf.webCompent.webView evaluateJavaScript:cancelCallback completionHandler:nil];
}];
}
- (void)jsShowToastWithJSONString:(NSString *)JSONString{
WMToastParameterObject *object = nil;
if ([JSONString isNonEmpty]) {
object = [[WMToastParameterObject alloc] initWithString:JSONString error:nil];
}
[self toast:SafeValue(object.text)];
}
#pragma mark - JumpToOtherPage
- (void)jsOpenBrowser:(NSString *)url{
UIAlertController *alert = [UIAlertController showCustomAlertWithTitle:@"提示" message:@"你访问的网址将以外部浏览器打开,是否继续?"];
[alert addAction:@"否" actionHandler:nil];
[alert addAction:@"是" actionHandler:^{
NSURL *browserUrl = [NSURL URLWithString:url];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
[[UIApplication sharedApplication] openURL:browserUrl options:@{} completionHandler:NULL];
} else {
[[UIApplication sharedApplication] openURL:browserUrl];
}
}];
}
// 调起第三方APP 比如 爱奇艺 7.15.1
- (void)jsSkipToThirdApp:(NSString *)jsonString {
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSURL *url = [NSURL URLWithString:dic[@"target_url"]];
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:NULL];
} else {
[[UIApplication sharedApplication] openURL:url];
}
}
// 跳转到设置页面
- (void)jsJumpToLocationSettings {
[GMLocationManager jumpToSettings];
}
// 调用打电话
- (void)jsCallPhone:(NSString *)phone {
NSString *phoneStr = [NSString stringWithFormat:@"tel:%@",phone];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: phoneStr]];
}
// 发短信
- (void)jsSendMessage:(NSString *)phone withBody:(NSString *)body {
if ([GMMessageController canSendText]) {
GMMessageController *messageVc = [[GMMessageController alloc] init];
messageVc.messageComposeDelegate = self;
messageVc.recipients = @[phone];
messageVc.body = body;
#warning ios 13
messageVc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:messageVc animated:YES completion:nil];
} else {
[self toast:@"当前设备不支持发短信"];
}
}
/*!
* @author zhaiguojun, 16-07-05
*
* @brief 选择图片上传,然后把返回的URL返回给前端
*
* @param requestCode 传过来的标识
*
* @since 6.2.0
*/
- (void)openAlbum:(NSString *)requestCode isPrivate:(BOOL)isPrivate{
_requestCoder = requestCode;
_isPrivatePhoto = isPrivate;
GMPhotoPickController *controller = [[GMPhotoPickController alloc] initWithMaxImageCount:1 maxVideoCount:0 maxCount:1 photoDisplayType:GMPhotosDisplayImageType];
controller.dismissDelegate = self;
controller.root.singleSelection = YES;
#warning ios 13
controller.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:controller animated:YES completion:NULL];
}
- (void)hintGotoSetting {
UIAlertAction *action = [UIAlertAction actionWithTitle:@"我知道了" style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *gotoAction = [UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self goToAppSystemSettings];
}];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"请您先去设置允许APP访问您的相机 设置>隐私>相机" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:action];
[alert addAction:gotoAction];
[self presentViewController:alert animated:YES completion:nil];
}
/// 保险状态返回
/// @param dict has_bought: true(确定购买) / false(取消购买)
- (void)jsInsurancePurchase:(NSDictionary *)dict {
//可以考虑废弃 在GMInsuranceWebviewController做了处理 避免其他类用到这个方法因此先保存着
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:REFRESH_SETTLEMENT_LIST object:nil userInfo: dict];
[self.navigationController popViewControllerAnimated:YES];
});
}
#pragma mark - MFMessageComposeViewControllerDelegate
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result {
[controller dismissViewControllerAnimated:true completion:nil];
}
#pragma mark - local Data
- (void)jsSetLocalStorage:(NSString *)key withStorage:(NSString *)value {
if (![value isNonEmpty]) {
value = @"";
}
[GMCache storeObjectAtDocumentPathWithkey:key object:value];
}
- (NSString *)jsGetLocalStorage:(NSString *)key {
NSString *cacheKey = [GMCache fetchObjectAtDocumentPathWithkey:key];
#warning h5
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('getLocalStorage',%@)",cacheKey];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
if (![cacheKey isNonEmpty]) {
return @"";
}
return cacheKey;
}
- (void)jsSyncSSOInfo:(NSString *)jsonString {
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
if (info[@"user_id"] != nil) {
[[GMLoginManager shareInstance] loginSuccessWithUserInfo:info shouldBroadcast:NO];
}
if (info[@"cookie"] != nil && info[@"domain"] != nil) {
[GMCookieManager saveSessionId:info[@"cookie"] domain:info[@"domain"]];
}
}
- (void)jsNewbieCouponGot:(NSInteger)flag {
[GMCache storeObjectAtMemoryWithkey:kNewUserTicketHasGot object:@(flag)];
}
/**
* @author wangjun, 16-07-04 14:07:51
*
* 秒杀页面添加到系统日历事件
* @param jsonString H5传来的的美购信息
*/
- (void)jsRemindEvent:(NSString *)jsonString {
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:NULL];
NSInteger beginTime = [json[@"start_time"] longValue];
NSInteger endTime = [json[@"end_time"] longValue];
NSString *title = json[@"title"];
NSString *url = json[@"url"];
NSString *serviceId = json[@"service_id"];
BOOL notify = [json[@"notify"] boolValue];
GMCalendarEventManager *manager = [[GMCalendarEventManager alloc] init];
__weak typeof(manager) weakManager = manager;
manager.authorizationAllowed = ^{
NSMutableDictionary *param = [NSMutableDictionary dictionary];
param[@"service_id"] = serviceId;
//7.3.0修改,@王成亮
[Phobos track:@"activity_promotion_seckill_notice" attributes:param];
[weakManager addEvent: title startDate: beginTime endDate: endTime serviceId: serviceId url: url];
};
if ([manager checkAuthorized]) {
if (notify) {
//添加提醒
NSMutableDictionary *param = [NSMutableDictionary dictionary];
param[@"service_id"] = serviceId;
[Phobos track:@"activity_promotion_seckill_notice" attributes:param];
[manager addEvent: title startDate: beginTime endDate: endTime serviceId: serviceId url: url];
} else {
//取消提醒
[manager removeEvent: serviceId];
}
}
}
- (BOOL)jsIsLocationEnable {
BOOL isLocation = [GMLocationManager hasOpenLocationService];
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('isLocationEnableCallback',%d)",isLocation];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
return isLocation;
}
#pragma mark - 导航栏按键响应
- (void)backAction:(OCNavigationBarButton *)button {
// 目前只有签到页面和获取列表支持返回拦截,后续建议逐渐删除
if ([_jsGlobalObject[@"page_name"] isEqualToString:@"activity_list"]) {
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('activityListBack')" completionHandler:nil];
}
[self nativeBackToCallJS];
// 由前端控制当前页面的返回事件是否需要拦截
if (![_jsGlobalObject.allKeys containsObject:@"back_call"] ||
![_jsGlobalObject[@"back_call"] boolValue]) {
[super backAction:button];
}
}
- (void)rightButtonClicked:(OCNavigationBarButton *)button {
[super rightButtonClicked:button];
// 隐藏键盘,避免不必要的bug发生
[self.view endEditing:YES];
if (_isSign) {
return;
}
// 显示分享
if (_isShowShare) {
// self.shareView = nil 这句话不能删除。删除了会导致 shareView 的背景消失
self.shareView = nil;
[self.shareView showWithShareUrl:self.shareObject.url];
}
/*
统计预告分享pv uv(device)。另外数据端还可以利用这个做其它统计。in 7.0.5
*/
if ([self.pageName isNonEmpty] && _isShowShare) {
NSDictionary *dic = @{@"business_id": self.businessId,
@"page_name": self.pageName,
@"referrer": self.referer};
[Phobos track:@"on_click_common_webview_share" attributes:dic];
}
// 没有分享,只显示了一个JS_Button
if (!_isShowShare && _isJsRightButton) {
NSString *jumpURL = _jsGlobalObject[@"right_button"][@"url"];
NSString *jsFunction = _jsGlobalObject[@"right_button"][@"js_function"];
if ([jumpURL isNonEmpty]) {
[[GMRouter sharedInstance] pushScheme:jumpURL];
}
if ([jsFunction isNonEmpty]) {
[self.webCompent.webView evaluateJavaScript:jsFunction completionHandler:nil];
}
}
// 口红王子投票活动
if ([_jsGlobalObject[@"navigation"][@"right_action"] isNonEmpty]) {
NSString *rightAction = _jsGlobalObject[@"navigation"][@"right_action"];
[self.webCompent.webView evaluateJavaScript:rightAction completionHandler:nil];
}
}
- (void)nearRightButtonClicked:(OCNavigationBarButton *)button {
[super nearRightButtonClicked:button];
if (_isShowShare && _isJsRightButton) {
NSString *jumpURL = _jsGlobalObject[@"right_button"][@"url"];
NSString *jsFunction = _jsGlobalObject[@"right_button"][@"js_function"];
if ([jumpURL isNonEmpty]) {
[[GMRouter sharedInstance] pushScheme:jumpURL];
}
if ([jsFunction isNonEmpty]) {
[self.webCompent.webView evaluateJavaScript:jsFunction completionHandler:nil];
}
}
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_isNavigationAlpha) {
// animationDistance 为0时表示header_height值在h5端还未确认
if (self.animationDistance > 0) {
[self animationBar:self.navigationBar withScrollView:scrollView];
} else {
CGFloat headerHeight = [_jsGlobalObject[@"header_height"] floatValue];
// 如果header_height不为0,表示图片大小已经确定,
if (headerHeight != 0) {
self.animationDistance = headerHeight - OCNavigationBar.barHeight;
}
}
CGFloat alpha = [self animationBar:self.navigationBar withScrollView:scrollView];
if (_isSign) {
// 签到 rightButton不隐藏ase
CGFloat offsetY = self.contentOffsetY > 0 ? self.contentOffsetY : scrollView.contentOffset.y;
self.navigationBar.rightButton.hidden = NO;
self.navigationBar.titleLabel.hidden = NO;
if (offsetY > [_jsGlobalObject[@"header_height"] floatValue] - OCNavigationBar.statusBarHeight) {
self.navigationBar.titleLabel.textColor = [UIColor colorWithWhite:0 alpha:alpha];
self.navigationBar.rightIconURL = _jsGlobalObject[@"navigation"][@"right_scrolled_button"];
self.navigationBar.leftIcon = @"back";
} else {
self.navigationBar.titleLabel.textColor = [UIColor whiteColor];
self.navigationBar.rightIconURL = _jsGlobalObject[@"navigation"][@"right_button"];
self.navigationBar.leftIcon = @"backWhite";
}
} else {
self.navigationBar.rightButton.hidden = !(alpha == 1);
}
}
}
- (void)showReport{
[self showComplete:@"举报成功,所长会尽快处理哒"];
}
/**
粘贴邀请好友口令到粘贴板
@param boardText 粘贴板文字
*/
- (void)jsClipboard:(NSString *)boardText {
UIPasteboard *board = [UIPasteboard generalPasteboard];
board.string = boardText;
[self toast:@"复制成功"];
}
#pragma mark - GMPhotoPickDismissDelegate
- (void)dismissPhotoPick:(NSArray<GMEditPhotoInfo *> *)infos{
if (infos.count == 0) {
return ;
}
GMEditPhotoInfo *model = infos.firstObject;
UIImage *image = model.finshedImage;
NSString *remoteUrl = _isPrivatePhoto ? API_UPLOAD_IMAGE_SAFE : API_UPLOAD_IMAGE;
WMImageUploadOperation *operation = [[WMImageUploadOperation alloc]initWithImage:image url:remoteUrl withCompressionQuality:0.75];
[operation setImageUploadFinishBlock:^(NSDictionary *JSON) {
NSString *publicUrl = @"";
NSString *jsParam = @"";
if (_isPrivatePhoto) {
publicUrl = JSON[@"data"][@"public_url"];
NSString *privateUrl = JSON[@"data"][@"private_url"];
if ([publicUrl isNonEmpty] && [privateUrl isNonEmpty]) {
NSDictionary *data = @{_requestCoder : @{@"private_url": privateUrl, @"public_url" : publicUrl}};
NSData *jsParamData = [NSJSONSerialization dataWithJSONObject:data options:0 error:NULL];
jsParam = [[NSString alloc] initWithData:jsParamData encoding:NSUTF8StringEncoding];
}
} else {
publicUrl = JSON[@"data"][@"public_url"];
NSDictionary *data = @{_requestCoder : @{@"public_url" : publicUrl}};
NSData *jsParamData = [NSJSONSerialization dataWithJSONObject:data options:0 error:NULL];
jsParam = [[NSString alloc] initWithData:jsParamData encoding:NSUTF8StringEncoding];
}
NSString *jsString = [NSString stringWithFormat:@"gm.util.setPageData('%@')",jsParam];
[self.webCompent.webView evaluateJavaScript:jsString completionHandler:nil];
}];
[operation startUpload];
}
// 签到页弹窗提示后,点击不再提醒会调用
- (void)jsSignNeverNotify {
[self.navigationController popViewControllerAnimated:YES];
}
- (int)jsIsUserLogin {
int isLogin = GMLoginManager.shareInstance.isVisitor ? 0 : 1;
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('isUserLoginCallback',%d)",isLogin];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
return isLogin;
}
// js 获取NativeData数据
- (NSString *)jsNativeDataLoaded {
NSString *trackerJson = [self nativeCommonParam];
if ([trackerJson isNonEmpty]) {
NSString *jsonStr = [NSString stringWithFormat:@"window.gm.pack.run('setNativeDataLoaded','%@')",trackerJson];
[self.webCompent.webView evaluateJavaScript:jsonStr completionHandler:nil];
return trackerJson;
}
return @"";
}
// 本地获取通用数据
- (NSString *)nativeCommonParam {
NSMutableDictionary *param = [NSMutableDictionary dictionaryWithDictionary:[NSDictionary trackerPageParam:self]];
[param setValue:SafeString(@(OCNavigationBar.barHeight)) forKey:@"title_bar_height"];
[param setValue:(self.referrerLink ? : @[]) forKey:@"referrer_link"];
// 仅当 cpc_referer 不为none时候传递此参数
if (self.cpc_referer != GMCpcNone) {
[param setValue:SafeString(@(self.is_cpc)) forKey:@"is_cpc"];
[param setValue:SafeString(@(self.cpc_referer)) forKey:@"cpc_referer"];
}
NSData *referrerData = [NSJSONSerialization dataWithJSONObject:param options:0 error:NULL];
NSString *trackerJson = [[NSString alloc] initWithData:referrerData encoding:NSUTF8StringEncoding];
if ([trackerJson isNonEmpty]) {
return trackerJson;
}
return @"";
}
- (NSString *)jsInflateUserInfo {
NSDictionary *userDic = @{@"user_id":@([GMLoginManager shareInstance].user.userId), @"user_name":SafeString([GMLoginManager shareInstance].user.nickName)};
NSData *userData = [NSJSONSerialization dataWithJSONObject:userDic options:0 error:NULL];
NSString *userParam = [[NSString alloc] initWithData:userData encoding:NSUTF8StringEncoding];
NSString *js = [NSString stringWithFormat:@"window.gm.pack.run('setInflateUserInfo',%@)",userParam];
[self.webCompent.webView evaluateJavaScript:js completionHandler:nil];
return userParam;
}
- (void)jsShowActionSheet:(NSString *)jsonString {
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options: NSJSONReadingAllowFragments error:nil];
if (jsonArray.count <= 0) {
return;
}
GMActionSheet *alertView = [[GMActionSheet alloc] init];
// 回复弹窗内容和回调方法
for (NSDictionary *dic in jsonArray) {
GMActionSheetItemType type;
if ([dic[@"title"] isEqualToString:@"取消"]) {
type = GMActionSheetItemTypeCancel;
[alertView addItemWithTitle:dic[@"title"] style:type color:UIColor.headlineText handle:nil];
} else {
__weak __typeof(self)weakSelf = self;
type = GMActionSheetItemTypeDefault;
[alertView addItemWithTitle:dic[@"title"] style:type color:UIColor.headlineText handle:^(NSInteger index) {
NSDictionary *className = jsonArray[index];
NSString *js = [NSString stringWithFormat:@"window.gm.pack.run('%@')", SafeString(className[@"callback"])];
[weakSelf.webCompent.webView evaluateJavaScript:js completionHandler:nil];
}];
}
}
[alertView show];
}
- (void)jsRightbuttonCallBack:(NSString *)json {
NSData *jsonData = [json dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
self.navigationBar.rightIconURL = dict[@"icon_url"];
}
- (int)jsNavigationbarHeight {
float barH = OCNavigationBar.barHeight;
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('navigationbarHeightCallback',%f)",barH];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
return OCNavigationBar.barHeight;
}
- (void)jsCheckAuthority:(NSString *)jsonString {
[AppDelegate.shareInstance userIsAllowPush:^(BOOL status) {
if (!status) {
_isSettingCallback = YES;
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
GMCommonAlertView *alertView = [[GMCommonAlertView alloc] init];
alertView.titleLabel.text = dict[@"title"];
alertView.subLabel.text = dict[@"tip"];
[alertView.button1 setTitle:dict[@"openName"] forState:UIControlStateNormal];
[alertView.button2 setTitle:dict[@"cancelName"] forState:UIControlStateNormal];
alertView.button3.hidden = YES;
self.settingType = [dict[@"type"] integerValue];
alertView.delegate = self;
[alertView show];
} else {
[self excuteSettingCallback:YES];
}
}];
}
- (void)didClickCommonAlertWithButtonIndex:(NSInteger)buttonIndex alertView:(GMCommonAlertView *)alertView {
[alertView dismiss];
if (buttonIndex == 1) {
//打开
[self goToAppSystemSettings];
self.goToAppSettingsPage = YES;
} else if (buttonIndex == 2) {
[self excuteSettingCallback:NO];
}
}
- (void)excuteSettingCallback:(BOOL)setting {
NSString *javaScriptStr = [NSString stringWithFormat:@"window.gm.pack.run('settingCallback',%d)",setting];
[self.webCompent.webView evaluateJavaScript:javaScriptStr completionHandler:nil];
}
#pragma mark - Native Call JS
/// Native 告知 H5页面展现
- (void)nativeAppearToCallJS {
[self.webCompent.webView evaluateJavaScript:@"window.gm&&window.gm.pack&&window.gm.pack.run('appear')" completionHandler:nil];
}
/// Native 告知 H5页面不可见
- (void)nativeDisAppearToCallJS {
[self.webCompent.webView evaluateJavaScript:@"window.gm&&window.gm.pack&&window.gm.pack.run('disappear')" completionHandler:nil];
}
/// Native 告知 H5返回执行
- (void)nativeBackToCallJS {
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('backAction')" completionHandler:nil];
}
/// Native 告知 H5取消登录
- (void)nativeLoginCancelToCallJS {
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('loginCancel')" completionHandler:nil];
}
/// Native 告知 H5进入后台
- (void)nativeEnterBackgroundToCallJS {
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('enterBackground')" completionHandler:nil];
}
/// Native 告知 H5进入前台
- (void)nativeEnterForgroundToCallJS {
[self.webCompent.webView evaluateJavaScript:@"window.gm.pack.run('enterForground')" completionHandler:nil];
}
#pragma mark - 埋点
/*** type由h5负责,所有h5埋点的方法都走这里 */
- (void)jsTrackEvent:(NSString *)JSONString {
@try {
NSData *data = [JSONString dataUsingEncoding:NSUnicodeStringEncoding];
NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
id pa = dict[@"params"];
BOOL sendNow = [dict[@"nowSend"] boolValue];
NSDictionary *json;
if (pa != nil) {
if ([pa isKindOfClass:[NSString class]]) {
NSError *jsonError;
NSData *objectData = [pa dataUsingEncoding:NSUTF8StringEncoding];
json = [NSJSONSerialization JSONObjectWithData:objectData
options:NSJSONReadingMutableContainers
error:&jsonError];
[Phobos track:dict[@"type"] attributes:json sendNow:sendNow];
}else{
[Phobos track:dict[@"type"] attributes:dict[@"params"] sendNow:sendNow];
}
} else {
[Phobos track:dict[@"type"] attributes:@{} sendNow:sendNow];
}
}
@catch (NSException *exception) {
debugLog(@"exception:%@",exception);
}
}
/*** 精准曝光 */
- (void)jsHybridExposure:(NSString *)JSONString {
@try {
NSData *data = [JSONString dataUsingEncoding:NSUnicodeStringEncoding];
NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
if (dict) {
[Phobos track:@"page_precise_exposure" attributes:dict sendNow:YES currentAPI:GMExactExposureApi];
}
}
@catch (NSException *exception) {
debugLog(@"exception:%@",exception);
}
}
#pragma mark - notification
- (void)addActiveObserver {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didBecomeActive)
name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(willResignActive)
name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(loginCancelNotification)
name:LOGIN_CANCEL_NOTIFICATION object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(enterBackground)
name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(enterForground)
name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)enterBackground {
[self nativeEnterBackgroundToCallJS];
}
- (void)enterForground {
[self nativeEnterForgroundToCallJS];
}
- (void)willResignActive {
// 活动页面
if ([_jsGlobalObject[@"page_name"] isEqualToString:@"activity_list"]) {
[self nativeDisAppearToCallJS];
}
}
- (void)didBecomeActive {
if ([_jsGlobalObject[@"page_name"] isEqualToString:@"activity_list"]) {
[self nativeAppearToCallJS];
}
// 前往设置页面后 回到当前页面需要告知前端状态,前端会直接弹出“开抢提醒”弹框
if (self.goToAppSettingsPage) {
[AppDelegate.shareInstance userIsAllowPush:^(BOOL status) {
[self excuteSettingCallback:status];
// 返回app的时候把数据清空了,所以延迟3秒执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 获取通知权限浮层页授权按钮点击(需要获取授权状态)
[Phobos track:@"authority_page_click" attributes:@{@"user_id":@([GMLoginManager shareInstance].user.userId), @"authority_type":@(status)}];
});
}];
}
// 由于秒杀专场使用了common_webivew协议,所以这段逻辑不得不放到这个controller里,并且添加相关页面的判断
// 秒杀专场的url activity/promotion/1024/seckill
// 美购专题的url special_promotion
if ([_jsGlobalObject[@"is_seckill"] boolValue] && [GMLocationManager hasOpenLocationService] &&
[GMLocationManager shareInstance].statusWhenAppBackground != [CLLocationManager authorizationStatus]) {
/*
当系统地理位置权限改变后再回到app时,需要刷新webview,也就是说要重新确认 is_location_enable 参数的状态
由于先前使用的是query的方式来取得该参数,导致在webView.refresh时,该参数状态还是原来的状态,并没有改变
所以采取了下面强制webview重新loadRequest
由于是又加载了一个页面,在后退时webView是内部后退,而不是controller后退,所以又得重写 backAction 方法
如果h5方便的时候,将 is_location_enable 改为js主动获取,而不是使用query参数,这样更灵活。
*/
[self webViewLoadRequest];
}
}
// 用户取消登录通知
- (void)loginCancelNotification {
[self nativeLoginCancelToCallJS];
}
#pragma mark - lazy loading
- (GMWebViewComponent *)webCompent {
if (!_webCompent) {
_webCompent = [GMWebViewComponent new];
_webCompent.delegate = self;
_webCompent.clientH5Object.delegate = self;
}
if (!_webCompent.superview) {
[self.view addSubview:_webCompent];
}
return _webCompent;
}
- (GMShareView *)shareView{
if (!_shareView) {
_shareView = [[GMShareView alloc] init];
_shareView.delegate = self;
}
return _shareView;
}
- (GMDiaryShareView *)diaryShareView {
if (!_diaryShareView) {
_diaryShareView = [[GMDiaryShareView alloc] init];
_diaryShareView.delegate = self;
}
return _diaryShareView;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 在某些情况下,如果controller先dealloc了,会导致webView找不到delegate而crash。例如修复jspatch与webview冲突时就会遇到。
// 复现步骤:先注释以下代码,再push到一个webController,加载时立即后退,等待,然后crash,提示 “webView:identifierForInitialRequest:fromDataSource”
_webCompent.delegate = nil;
[_webCompent.webView stopLoading];
}
@end
//
// 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
//
// JXCategoryBaseView.m
// UI系列测试
//
// Created by jiaxin on 2018/3/15.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXCategoryBaseView.h"
#import "JXCategoryFactory.h"
struct DelegateFlags {
unsigned int didSelectedItemAtIndexFlag : 1;
unsigned int didClickSelectedItemAtIndexFlag : 1;
unsigned int didScrollSelectedItemAtIndexFlag : 1;
unsigned int didClickedItemContentScrollViewTransitionToIndexFlag : 1;
unsigned int scrollingFromLeftIndexToRightIndexFlag : 1;
};
@interface JXCategoryBaseView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, assign) CGFloat innerCellSpacing;
@property (nonatomic, assign) CGPoint lastContentViewContentOffset;
@end
@implementation JXCategoryBaseView
- (void)dealloc
{
if (self.contentScrollView) {
[self.contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
}
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initializeData];
[self initializeViews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self initializeData];
[self initializeViews];
}
return self;
}
- (void)setDelegate:(id<JXCategoryViewDelegate>)delegate {
_delegate = delegate;
_delegateFlags.didSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didSelectedItemAtIndex:)];
_delegateFlags.didClickSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didClickSelectedItemAtIndex:)];
_delegateFlags.didScrollSelectedItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:didScrollSelectedItemAtIndex:)];
_delegateFlags.didClickedItemContentScrollViewTransitionToIndexFlag = [delegate respondsToSelector:@selector(categoryView:didClickedItemContentScrollViewTransitionToIndex:)];
_delegateFlags.scrollingFromLeftIndexToRightIndexFlag = [delegate respondsToSelector:@selector(categoryView:scrollingFromLeftIndex:toRightIndex:ratio:)];
}
- (void)initializeData
{
_dataSource = [NSMutableArray array];
_selectedIndex = 0;
_cellWidth = JXCategoryViewAutomaticDimension;
_cellWidthIncrement = 0;
_cellSpacing = 20;
_averageCellSpacingEnabled = YES;
_cellWidthZoomEnabled = NO;
_cellWidthZoomScale = 1.2;
_cellWidthZoomScrollGradientEnabled = YES;
_contentEdgeInsetLeft = JXCategoryViewAutomaticDimension;
_contentEdgeInsetRight = JXCategoryViewAutomaticDimension;
_lastContentViewContentOffset = CGPointZero;
}
- (void)initializeViews
{
self.collectionView = ({
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
JXCategoryCollectionView *collectionView = [[JXCategoryCollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout];
collectionView.backgroundColor = [UIColor clearColor];
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.showsVerticalScrollIndicator = NO;
collectionView.scrollsToTop = NO;
collectionView.dataSource = self;
collectionView.delegate = self;
[collectionView registerClass:[self preferredCellClass] forCellWithReuseIdentifier:NSStringFromClass([self preferredCellClass])];
collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
collectionView;
});
[self addSubview:self.collectionView];
}
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex
{
_defaultSelectedIndex = defaultSelectedIndex;
self.selectedIndex = defaultSelectedIndex;
}
- (void)setContentScrollView:(UIScrollView *)contentScrollView
{
if (_contentScrollView != nil) {
[_contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
}
_contentScrollView = contentScrollView;
_contentScrollView.scrollsToTop = NO;
[contentScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)reloadData {
[self refreshDataSource];
[self refreshState];
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView reloadData];
}
- (void)reloadCellAtIndex:(NSInteger)index {
if (index >= self.dataSource.count) {
return;
}
JXCategoryBaseCellModel *cellModel = self.dataSource[index];
[self refreshCellModel:cellModel index:index];
JXCategoryBaseCell *cell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[cell reloadData:cellModel];
}
- (void)selectItemAtIndex:(NSInteger)index {
[self selectCellAtIndex:index isClicked:YES];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self reloadData];
}
#pragma mark - Subclass Override
- (void)refreshDataSource {
}
- (void)refreshState {
if (self.selectedIndex >= self.dataSource.count) {
self.selectedIndex = 0;
}
self.innerCellSpacing = self.cellSpacing;
__block CGFloat totalItemWidth = [self getContentEdgeInsetLeft];
CGFloat totalCellWidth = 0;
for (int i = 0; i < self.dataSource.count; i++) {
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
cellModel.index = i;
cellModel.cellWidth = [self preferredCellWidthAtIndex:i] + self.cellWidthIncrement;
totalCellWidth += cellModel.cellWidth;
cellModel.cellWidthZoomEnabled = self.cellWidthZoomEnabled;
cellModel.cellWidthZoomScale = 1.0;
cellModel.cellSpacing = self.cellSpacing;
if (i == self.dataSource.count - 1) {
totalItemWidth += cellModel.cellWidth + [self getContentEdgeInsetRight];
}else {
totalItemWidth += cellModel.cellWidth + self.cellSpacing;
}
if (i == self.selectedIndex) {
cellModel.selected = YES;
cellModel.cellWidthZoomScale = self.cellWidthZoomScale;
}else {
cellModel.selected = NO;
}
[self refreshCellModel:cellModel index:i];
}
if (self.averageCellSpacingEnabled && totalItemWidth < self.bounds.size.width) {
//如果总的内容宽度都没有超过视图度,就将cellWidth等分
NSInteger cellSpacingItemCount = self.dataSource.count - 1;
CGFloat totalCellSpacingWidth = self.bounds.size.width - totalCellWidth;
//如果内容左边距是Automatic,就加1
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
cellSpacingItemCount += 1;
}else {
totalCellSpacingWidth -= self.contentEdgeInsetLeft;
}
//如果内容右边距是Automatic,就加1
if (self.contentEdgeInsetRight == JXCategoryViewAutomaticDimension) {
cellSpacingItemCount += 1;
}else {
totalCellSpacingWidth -= self.contentEdgeInsetRight;
}
CGFloat cellSpacing = 0;
if (cellSpacingItemCount > 0) {
cellSpacing = totalCellSpacingWidth/cellSpacingItemCount;
}
self.innerCellSpacing = cellSpacing;
[self.dataSource enumerateObjectsUsingBlock:^(JXCategoryBaseCellModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.cellSpacing = cellSpacing;
}];
}
__block CGFloat frameXOfSelectedCell = self.innerCellSpacing;
__block CGFloat selectedCellWidth = 0;
totalItemWidth = self.innerCellSpacing;
[self.dataSource enumerateObjectsUsingBlock:^(JXCategoryBaseCellModel * cellModel, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx < self.selectedIndex) {
frameXOfSelectedCell += cellModel.cellWidth + self.innerCellSpacing;
}else if (idx == self.selectedIndex) {
selectedCellWidth = cellModel.cellWidth;
}
totalItemWidth += cellModel.cellWidth + self.innerCellSpacing;
}];
CGFloat minX = 0;
CGFloat maxX = totalItemWidth - self.bounds.size.width;
CGFloat targetX = frameXOfSelectedCell - self.bounds.size.width/2.0 + selectedCellWidth/2.0;
[self.collectionView setContentOffset:CGPointMake(MAX(MIN(maxX, targetX), minX), 0) animated:NO];
if (CGRectEqualToRect(self.contentScrollView.frame, CGRectZero) && self.contentScrollView.superview != nil) {
//某些情况、系统会出现JXCategoryView先布局,contentScrollView后布局。就会导致下面指定defaultSelectedIndex失效,所以发现frame为zero时,强行触发布局。
[self.contentScrollView.superview setNeedsLayout];
[self.contentScrollView.superview layoutIfNeeded];
}
[self.contentScrollView setContentOffset:CGPointMake(self.selectedIndex*self.contentScrollView.bounds.size.width, 0) animated:NO];
}
- (BOOL)selectCellAtIndex:(NSInteger)targetIndex isClicked:(BOOL)isClicked {
return [self _selectCellAtIndex:targetIndex isClicked:isClicked];
}
- (BOOL)_selectCellAtIndex:(NSInteger)targetIndex isClicked:(BOOL)isClicked{
if (targetIndex >= self.dataSource.count) {
return NO;
}
if (self.selectedIndex == targetIndex) {
if (isClicked) {
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
}
}else {
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
}
}
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
}
return NO;
}
JXCategoryBaseCellModel *lastCellModel = self.dataSource[self.selectedIndex];
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
[self refreshSelectedCellModel:selectedCellModel unselectedCellModel:lastCellModel];
JXCategoryBaseCell *lastCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.selectedIndex inSection:0]];
[lastCell reloadData:lastCellModel];
JXCategoryBaseCell *selectedCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0]];
[selectedCell reloadData:selectedCellModel];
if (self.cellWidthZoomEnabled) {
[self.collectionView.collectionViewLayout invalidateLayout];
//延时为了解决cellwidth变化,点击最后几个cell,scrollToItem会出现位置偏移bug
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
});
}else {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
if (isClicked) {
if (self.delegateFlags.didClickedItemContentScrollViewTransitionToIndexFlag) {
[self.delegate categoryView:self didClickedItemContentScrollViewTransitionToIndex:targetIndex];
}else {
[self.contentScrollView setContentOffset:CGPointMake(targetIndex*self.contentScrollView.bounds.size.width, 0) animated:YES];
}
}
self.selectedIndex = targetIndex;
if (isClicked) {
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
}
}else {
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
}
}
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
}
return YES;
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
selectedCellModel.selected = YES;
selectedCellModel.cellWidthZoomScale = self.cellWidthZoomScale;
unselectedCellModel.selected = NO;
unselectedCellModel.cellWidthZoomScale = 1.0;
}
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset {
CGFloat ratio = contentOffset.x/self.contentScrollView.bounds.size.width;
if (ratio > self.dataSource.count - 1 || ratio < 0) {
//超过了边界,不需要处理
return;
}
if (contentOffset.x == 0 && self.selectedIndex == 0 && self.lastContentViewContentOffset.x == 0) {
//滚动到了最左边,且已经选中了第一个,且之前的contentOffset.x为0
return;
}
CGFloat maxContentOffsetX = self.contentScrollView.contentSize.width - self.contentScrollView.bounds.size.width;
if (contentOffset.x == maxContentOffsetX && self.selectedIndex == self.dataSource.count - 1 && self.lastContentViewContentOffset.x == maxContentOffsetX) {
//滚动到了最右边,且已经选中了最后一个,且之前的contentOffset.x为maxContentOffsetX
return;
}
ratio = MAX(0, MIN(self.dataSource.count - 1, ratio));
NSInteger baseIndex = floorf(ratio);
CGFloat remainderRatio = ratio - baseIndex;
if (remainderRatio == 0) {
//滑动翻页,需要更新选中状态
//滑动一小段距离,然后放开回到原位,contentOffset同样的值会回调多次。例如在index为1的情况,滑动放开回到原位,contentOffset会多次回调CGPoint(width, 0)
if (!(self.lastContentViewContentOffset.x == contentOffset.x && self.selectedIndex == baseIndex)) {
[self scrollselectItemAtIndex:baseIndex];
}
}else {
//快速滑动翻页,当remainderRatio没有变成0,但是已经翻页了,需要通过下面的判断,触发选中
if (fabs(ratio - self.selectedIndex) > 1) {
NSInteger targetIndex = baseIndex;
if (ratio < self.selectedIndex) {
targetIndex = baseIndex + 1;
}
[self _selectCellAtIndex:targetIndex isClicked:NO];
}
if (self.cellWidthZoomEnabled && self.cellWidthZoomScrollGradientEnabled) {
JXCategoryBaseCellModel *leftCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex];
JXCategoryBaseCellModel *rightCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex + 1];
leftCellModel.cellWidthZoomScale = [JXCategoryFactory interpolationFrom:self.cellWidthZoomScale to:1.0 percent:remainderRatio];
rightCellModel.cellWidthZoomScale = [JXCategoryFactory interpolationFrom:1.0 to:self.cellWidthZoomScale percent:remainderRatio];
[self.collectionView.collectionViewLayout invalidateLayout];
}
if (self.delegateFlags.scrollingFromLeftIndexToRightIndexFlag) {
[self.delegate categoryView:self scrollingFromLeftIndex:baseIndex toRightIndex:baseIndex + 1 ratio:remainderRatio];
}
}
}
- (CGFloat)preferredCellWidthAtIndex:(NSInteger)index {
return 0;
}
- (Class)preferredCellClass {
return JXCategoryBaseCell.class;
}
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index {
}
#pragma mark - <UICollectionViewDataSource, UICollectionViewDelegate>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.dataSource.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
return [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([self preferredCellClass]) forIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
JXCategoryBaseCell *categoryCell = (JXCategoryBaseCell *)cell;
[categoryCell reloadData:self.dataSource[indexPath.item]];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self clickselectItemAtIndex:indexPath.row];
}
#pragma mark - <UICollectionViewDelegateFlowLayout>
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, [self getContentEdgeInsetLeft], 0, [self getContentEdgeInsetRight]);
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
JXCategoryBaseCellModel *cellModel = self.dataSource[indexPath.item];
return CGSizeMake(cellModel.cellWidth, self.bounds.size.height);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return self.innerCellSpacing;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return self.innerCellSpacing;
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentOffset"]) {
CGPoint contentOffset = [change[NSKeyValueChangeNewKey] CGPointValue];
if ((self.contentScrollView.isTracking || self.contentScrollView.isDecelerating)) {
//用户滚动引起的contentOffset变化,才处理。
[self contentOffsetOfContentScrollViewDidChanged:contentOffset];
}
self.lastContentViewContentOffset = contentOffset;
}
}
#pragma mark - Other
- (CGRect)getTargetCellFrame:(NSInteger)targetIndex
{
CGFloat x = [self getContentEdgeInsetLeft];
for (int i = 0; i < targetIndex; i ++) {
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
x += cellModel.cellWidth + self.innerCellSpacing;
}
CGFloat width = self.dataSource[targetIndex].cellWidth;
return CGRectMake(x, 0, width, self.bounds.size.height);
}
#pragma mark - Private
- (CGFloat)getContentEdgeInsetLeft {
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
return self.innerCellSpacing;
}
return self.contentEdgeInsetLeft;
}
- (CGFloat)getContentEdgeInsetRight {
if (self.contentEdgeInsetRight == JXCategoryViewAutomaticDimension) {
return self.innerCellSpacing;
}
return self.contentEdgeInsetRight;
}
- (void)clickselectItemAtIndex:(NSInteger)index {
[self selectCellAtIndex:index isClicked:YES];
}
- (void)scrollselectItemAtIndex:(NSInteger)index {
[self selectCellAtIndex:index isClicked:NO];
}
@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
//
// JXCategoryComponentView.m
// DQGuess
//
// Created by jiaxin on 2018/7/25.
// Copyright © 2018年 jingbo. All rights reserved.
//
#import "JXCategoryIndicatorView.h"
#import "JXCategoryIndicatorBackgroundView.h"
#import "JXCategoryFactory.h"
@interface JXCategoryIndicatorView()
@property (nonatomic, strong) CALayer *backgroundEllipseLayer;
@end
@implementation JXCategoryIndicatorView
- (void)initializeData {
[super initializeData];
_separatorLineShowEnabled = NO;
_separatorLineColor = [UIColor lightGrayColor];
_separatorLineSize = CGSizeMake(1/[UIScreen mainScreen].scale, 20);
_cellBackgroundColorGradientEnabled = NO;
_cellBackgroundUnselectedColor = [UIColor whiteColor];
_cellBackgroundSelectedColor = [UIColor lightGrayColor];
}
- (void)initializeViews {
[super initializeViews];
}
- (void)setIndicators:(NSArray<UIView<JXCategoryIndicatorProtocol> *> *)indicators {
for (UIView *component in self.indicators) {
//先移除之前的component
[component removeFromSuperview];
}
_indicators = indicators;
for (UIView *component in self.indicators) {
[self.collectionView addSubview:component];
}
self.collectionView.indicators = indicators;
}
- (void)refreshState {
[super refreshState];
CGRect selectedCellFrame = CGRectZero;
JXCategoryIndicatorCellModel *selectedCellModel = nil;
for (int i = 0; i < self.dataSource.count; i++) {
JXCategoryIndicatorCellModel *cellModel = (JXCategoryIndicatorCellModel *)self.dataSource[i];
cellModel.sepratorLineShowEnabled = self.separatorLineShowEnabled;
cellModel.separatorLineColor = self.separatorLineColor;
cellModel.separatorLineSize = self.separatorLineSize;
cellModel.backgroundViewMaskFrame = CGRectZero;
cellModel.cellBackgroundColorGradientEnabled = self.cellBackgroundColorGradientEnabled;
cellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
cellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
if (i == self.dataSource.count - 1) {
cellModel.sepratorLineShowEnabled = NO;
}
if (i == self.selectedIndex) {
selectedCellModel = cellModel;
cellModel.selected = YES;
selectedCellFrame = [self getTargetCellFrame:i];
}
}
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
if (self.dataSource.count <= 0) {
component.hidden = YES;
}else {
component.hidden = NO;
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.selectedIndex = self.selectedIndex;
indicatorParamsModel.selectedCellFrame = selectedCellFrame;
[component jx_refreshState:indicatorParamsModel];
if ([component isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect maskFrame = component.frame;
maskFrame.origin.x = maskFrame.origin.x - selectedCellFrame.origin.x;
selectedCellModel.backgroundViewMaskFrame = maskFrame;
}
}
}
}
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
[super refreshSelectedCellModel:selectedCellModel unselectedCellModel:unselectedCellModel];
JXCategoryIndicatorCellModel *myUnselectedCellModel = (JXCategoryIndicatorCellModel *)unselectedCellModel;
myUnselectedCellModel.backgroundViewMaskFrame = CGRectZero;
myUnselectedCellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
myUnselectedCellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
JXCategoryIndicatorCellModel *myselectedCellModel = (JXCategoryIndicatorCellModel *)selectedCellModel;
myselectedCellModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
myselectedCellModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset {
[super contentOffsetOfContentScrollViewDidChanged:contentOffset];
CGFloat ratio = contentOffset.x/self.contentScrollView.bounds.size.width;
if (ratio > self.dataSource.count - 1 || ratio < 0) {
//超过了边界,不需要处理
return;
}
ratio = MAX(0, MIN(self.dataSource.count - 1, ratio));
NSInteger baseIndex = floorf(ratio);
if (baseIndex + 1 >= self.dataSource.count) {
//右边越界了,不需要处理
return;
}
CGFloat remainderRatio = ratio - baseIndex;
CGRect leftCellFrame = [self getTargetCellFrame:baseIndex];
CGRect rightCellFrame = [self getTargetCellFrame:baseIndex + 1];
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.selectedIndex = self.selectedIndex;
indicatorParamsModel.leftIndex = baseIndex;
indicatorParamsModel.leftCellFrame = leftCellFrame;
indicatorParamsModel.rightIndex = baseIndex + 1;
indicatorParamsModel.rightCellFrame = rightCellFrame;
indicatorParamsModel.percent = remainderRatio;
if (remainderRatio == 0) {
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
[component jx_contentScrollViewDidScroll:indicatorParamsModel];
}
}else {
JXCategoryIndicatorCellModel *leftCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[baseIndex];
JXCategoryIndicatorCellModel *rightCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[baseIndex + 1];
[self refreshLeftCellModel:leftCellModel rightCellModel:rightCellModel ratio:remainderRatio];
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
[component jx_contentScrollViewDidScroll:indicatorParamsModel];
if ([component isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect leftMaskFrame = component.frame;
leftMaskFrame.origin.x = leftMaskFrame.origin.x - leftCellFrame.origin.x;
leftCellModel.backgroundViewMaskFrame = leftMaskFrame;
CGRect rightMaskFrame = component.frame;
rightMaskFrame.origin.x = rightMaskFrame.origin.x - rightCellFrame.origin.x;
rightCellModel.backgroundViewMaskFrame = rightMaskFrame;
}
}
JXCategoryBaseCell *leftCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:baseIndex inSection:0]];
[leftCell reloadData:leftCellModel];
JXCategoryBaseCell *rightCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:baseIndex + 1 inSection:0]];
[rightCell reloadData:rightCellModel];
}
}
- (BOOL)selectCellAtIndex:(NSInteger)index isClicked:(BOOL)isClicked {
NSInteger lastSelectedIndex = self.selectedIndex;
BOOL result = [super selectCellAtIndex:index isClicked:isClicked];
if (!result) {
return NO;
}
CGRect clickedCellFrame = [self getTargetCellFrame:index];
JXCategoryIndicatorCellModel *selectedCellModel = (JXCategoryIndicatorCellModel *)self.dataSource[index];
for (UIView<JXCategoryIndicatorProtocol> *component in self.indicators) {
JXCategoryIndicatorParamsModel *indicatorParamsModel = [[JXCategoryIndicatorParamsModel alloc] init];
indicatorParamsModel.lastSelectedIndex = lastSelectedIndex;
indicatorParamsModel.selectedIndex = index;
indicatorParamsModel.selectedCellFrame = clickedCellFrame;
indicatorParamsModel.isClicked = isClicked;
[component jx_selectedCell:indicatorParamsModel];
if ([component isKindOfClass:[JXCategoryIndicatorBackgroundView class]]) {
CGRect maskFrame = component.frame;
maskFrame.origin.x = maskFrame.origin.x - clickedCellFrame.origin.x;
selectedCellModel.backgroundViewMaskFrame = maskFrame;
}
}
JXCategoryIndicatorCell *selectedCell = (JXCategoryIndicatorCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
[selectedCell reloadData:selectedCellModel];
return YES;
}
- (void)refreshLeftCellModel:(JXCategoryBaseCellModel *)leftCellModel rightCellModel:(JXCategoryBaseCellModel *)rightCellModel ratio:(CGFloat)ratio {
if (self.cellBackgroundColorGradientEnabled) {
//处理cell背景色渐变
JXCategoryIndicatorCellModel *leftModel = (JXCategoryIndicatorCellModel *)leftCellModel;
JXCategoryIndicatorCellModel *rightModel = (JXCategoryIndicatorCellModel *)rightCellModel;
if (leftModel.selected) {
leftModel.cellBackgroundSelectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundSelectedColor to:self.cellBackgroundUnselectedColor percent:ratio];
leftModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
}else {
leftModel.cellBackgroundUnselectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundSelectedColor to:self.cellBackgroundUnselectedColor percent:ratio];
leftModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
if (rightModel.selected) {
rightModel.cellBackgroundSelectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundUnselectedColor to:self.cellBackgroundSelectedColor percent:ratio];
rightModel.cellBackgroundUnselectedColor = self.cellBackgroundUnselectedColor;
}else {
rightModel.cellBackgroundUnselectedColor = [JXCategoryFactory interpolationColorFrom:self.cellBackgroundUnselectedColor to:self.cellBackgroundSelectedColor percent:ratio];
rightModel.cellBackgroundSelectedColor = self.cellBackgroundSelectedColor;
}
}
}
@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
//
// JXPagerView.m
// JXPagerView
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPagerView.h"
@import GMRefresh;
@interface JXPagerView () <UITableViewDataSource, UITableViewDelegate, JXPagerListContainerViewDelegate>
//@property (nonatomic, strong) JXPagerMainTableView *mainTableView;
@property (nonatomic, strong) JXPagerListContainerView *listContainerView;
@property (nonatomic, strong) UIScrollView *currentScrollingListView;
@property (nonatomic, weak) id<JXPagerViewListViewDelegate> currentList;
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXPagerViewListViewDelegate>> *validListDict;
@property (nonatomic, assign) CGFloat mainViewScrollOffsetY;
@property (nonatomic, assign) BOOL isListScroll;
@end
@implementation JXPagerView
- (instancetype)initWithDelegate:(id<JXPagerViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
_validListDict = [NSMutableDictionary dictionary];
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_mainTableView = [[JXPagerMainTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.mainTableView.showsVerticalScrollIndicator = NO;
self.mainTableView.showsHorizontalScrollIndicator = NO;
self.mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.mainTableView.scrollsToTop = NO;
self.mainTableView.dataSource = self;
self.mainTableView.delegate = self;
[self refreshTableHeaderView];
[self.mainTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
if (@available(iOS 11.0, *)) {
self.mainTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.mainTableView];
_listContainerView = [[JXPagerListContainerView alloc] initWithDelegate:self];
self.listContainerView.mainTableView = self.mainTableView;
self.isListHorizontalScrollEnabled = YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.mainTableView.frame = self.bounds;
}
- (void)setIsListHorizontalScrollEnabled:(BOOL)isListHorizontalScrollEnabled {
_isListHorizontalScrollEnabled = isListHorizontalScrollEnabled;
self.listContainerView.collectionView.scrollEnabled = isListHorizontalScrollEnabled;
}
- (void)reloadData {
self.currentList = nil;
self.currentScrollingListView = nil;
for (id<JXPagerViewListViewDelegate> list in self.validListDict.allValues) {
[list.listView removeFromSuperview];
}
[_validListDict removeAllObjects];
[self refreshTableHeaderView];
[self.mainTableView reloadData];
[self.listContainerView reloadData];
}
- (void)refreshTableHeaderView {
UIView *tableHeaderView = [self.delegate tableHeaderViewInPagerView:self];
// UIView *containerView = [[UIView alloc] initWithFrame:tableHeaderView.bounds];
// [containerView addSubview:tableHeaderView];
self.mainTableView.tableHeaderView = tableHeaderView;
}
- (void)preferredProcessListViewDidScroll:(UIScrollView *)scrollView {
CGFloat listScrollOffsetY = scrollView.contentOffset.y;
if ([self.delegate respondsToSelector:@selector(listAndMainScrollViewDidScroll:)]) {
[self.delegate listAndMainScrollViewDidScroll:(listScrollOffsetY + self.mainViewScrollOffsetY)];
}
if (self.mainTableView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView的header还没有消失,让listScrollView一直为0
if (self.currentList && [self.currentList respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[self.currentList listScrollViewWillResetContentOffset];
}
scrollView.contentOffset = CGPointZero;
scrollView.showsVerticalScrollIndicator = NO;
}else {
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
scrollView.showsVerticalScrollIndicator = YES;
}
}
- (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) {
if ([list respondsToSelector:@selector(listScrollViewWillResetContentOffset)]) {
[list listScrollViewWillResetContentOffset];
}
[list listScrollView].contentOffset = CGPointZero;
}
}
if (scrollView.contentOffset.y > [self.delegate tableHeaderViewHeightInPagerView:self] && self.currentScrollingListView.contentOffset.y == 0) {
//当往上滚动mainTableView的headerView时,滚动到底时,修复listView往上小幅度滚动
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
}
}
#pragma mark - Private
- (void)listViewDidScroll:(UIScrollView *)scrollView {
self.currentScrollingListView = scrollView;
[self preferredProcessListViewDidScroll:scrollView];
}
#pragma mark - UITableViewDataSource, UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return self.bounds.size.height - [self.delegate heightForPinSectionHeaderInPagerView:self];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
self.listContainerView.frame = cell.contentView.bounds;
[cell.contentView addSubview:self.listContainerView];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return [self.delegate heightForPinSectionHeaderInPagerView:self];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return [self.delegate viewForPinSectionHeaderInPagerView:self];
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 1;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *footer = [[UIView alloc] initWithFrame:CGRectZero];
footer.backgroundColor = [UIColor clearColor];
return footer;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.mainTableView.mj_header.refreshing) {
return;
}
CGFloat sectionHeaderHeight = [self.delegate heightForPinSectionHeaderInPagerView:self];
CGFloat sectionHeaderContentY = 0;
if ([self.delegate respondsToSelector:@selector(tableViewSectionHeaderViewContentYInPagerView:)]) {
sectionHeaderContentY = [self.delegate tableViewSectionHeaderViewContentYInPagerView:self];
}
CGFloat ceilPositon = [self.delegate tableHeaderViewHeightInPagerView:self] -sectionHeaderHeight - sectionHeaderContentY;
if (scrollView.contentOffset.y < sectionHeaderHeight) {
scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
}
if (scrollView.contentOffset.y>=ceilPositon && scrollView.contentOffset.y>=sectionHeaderHeight) {
scrollView.contentInset = UIEdgeInsetsMake(sectionHeaderContentY, 0, 0, 0);
}
CGFloat offsetY = scrollView.contentOffset.y;
self.mainViewScrollOffsetY = offsetY;
if ([self.delegate respondsToSelector:@selector(mainTableViewDidScroll:)]) {
[self.delegate mainTableViewDidScroll:scrollView];
}
if (scrollView.isTracking && self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = NO;
}
[self preferredProcessMainTableViewDidScroll:scrollView];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = YES;
}
if ([self.delegate respondsToSelector:@selector(mainTableViewDidEndDecelerating:)]) {
if (_isListScroll) {
[self.delegate mainTableViewDidEndDecelerating:self.currentScrollingListView];
} else {
[self.delegate mainTableViewDidEndDecelerating:scrollView];
}
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = YES;
}
if ([self.delegate respondsToSelector:@selector(mainTableViewDidEndDragging:willDecelerate:)]) {
if (_isListScroll) {
[self.delegate mainTableViewDidEndDragging:self.currentScrollingListView willDecelerate:decelerate];
} else {
[self.delegate mainTableViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if ([self.delegate respondsToSelector:@selector(mainTableViewWillBeginDragging:)]) {
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
_isListScroll = YES; // 为了前后判断滑动的view是否一致
[self.delegate mainTableViewWillBeginDragging:self.currentScrollingListView];
} else {
_isListScroll = NO;
[self.delegate mainTableViewWillBeginDragging:scrollView];
}
}
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
if (self.isListHorizontalScrollEnabled) {
self.listContainerView.collectionView.scrollEnabled = YES;
}
}
#pragma mark - JXPagingListContainerViewDelegate
- (NSInteger)numberOfRowsInListContainerView:(JXPagerListContainerView *)listContainerView {
return [self.delegate numberOfListsInPagerView:self];
}
- (UIView *)listContainerView:(JXPagerListContainerView *)listContainerView listViewInRow:(NSInteger)row {
id<JXPagerViewListViewDelegate> list = self.validListDict[@(row)];
if (list == nil) {
list = [self.delegate pagerView:self initListAtIndex:row];
__weak typeof(self)weakSelf = self;
__weak typeof(id<JXPagerViewListViewDelegate>) weakList = list;
[list listViewDidScrollCallback:^(UIScrollView *scrollView) {
weakSelf.currentList = weakList;
[weakSelf listViewDidScroll:scrollView];
}];
_validListDict[@(row)] = list;
}
return [list listView];
}
- (void)listContainerView:(JXPagerListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row {
self.currentScrollingListView = [self.validListDict[@(row)] listScrollView];
}
- (void)listContainerView:(JXPagerListContainerView *)listContainerView didScroll:(NSInteger)row {
if ([self.delegate respondsToSelector:@selector(pagerView:didScroll:)]) {
[self.delegate pagerView:self didScroll:row];
}
}
@end
//
// 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
//
// JXPagerMainTableView.m
//
// Created by jiaxin on 2018/8/27.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPageListMainTableView.h"
@interface JXPageListMainTableView ()<UIGestureRecognizerDelegate>
@end
@implementation JXPageListMainTableView
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}
@end
//
// JXPageView.h
//
// Created by jiaxin on 2018/9/13.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "JXPageListMainTableView.h"
#import "JXPageListContainerView.h"
#import "JXCategoryView.h"
@class JXPageListView;
@protocol JXPageListViewListDelegate <NSObject>
/**
返回listView内部持有的UIScrollView或UITableView或UICollectionView
主要用于mainTableView已经显示了顶部内容,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;
/**
选中listView之后触发数据加载。会多次回调,所以listView内部要做个标志,第一次才触发数据加载。
*/
- (void)listViewLoadDataIfNeeded;
@end
@protocol JXPageListViewDelegate <NSObject>
/**
返回listViews,数组的item需要是UIView的子类,且要遵循JXPageListViewListDelegate。
数组item要求返回一个UIView而不是一个UIScrollView,因为列表的UIScrollView一般是被包装到一个view里面,里面会处理数据源和其他逻辑。
@param pageListView pageListView description
@return listViews
*/
- (NSArray <UIView <JXPageListViewListDelegate>*>*)listViewsInPageListView:(JXPageListView *)pageListView;
@optional
/**
悬浮分类视图选中某个index的回调
@param pinCategoryView 悬浮分类视图
@param index index
*/
- (void)pinCategoryView:(JXCategoryBaseView *)pinCategoryView didSelectedItemAtIndex:(NSInteger)index;
@end
/**
按照demo工程PageViewController里面的tips使用,把最后一个section留给listContainerCell。其余的,你就把mainTableView当做普通的tableView来处理就行。上面想添加多少cell都随你。
*/
@interface JXPageListView : UIView
@property (nonatomic, strong, readonly) JXCategoryTitleView *pinCategoryView;
@property (nonatomic, strong, readonly) JXPageListMainTableView *mainTableView;
@property (nonatomic, assign, getter=isListViewScrollStateSaveEnabled) BOOL listViewScrollStateSaveEnabled; //是否保存底部滚动列表视图的滚动状态,默认YES。设置为YES,表示A列表被滚动一定距离后,切换到B列表,当B列表往上滚动到最顶部的时候,再切换到A列表,A列表保存了它之前的滚动距离;设置为NO,表示A列表被滚动一定距离后,切换到B列表,当B列表往上滚动到最顶部的时候,再切换到A列表,A列表也同步被滚动到最顶部了;具体可以参考demo效果。
@property (nonatomic, assign) CGFloat pinCategoryViewHeight; //顶部固定悬浮categoryView的高度
@property (nonatomic, assign) CGFloat pinCategoryViewVerticalOffset; //顶部固定sectionHeader的垂直偏移量。数值越大越往下沉。
- (instancetype)initWithDelegate:(id<JXPageListViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
/**
通过服务器获取到数据之后,再调用reloadData方法刷新
*/
- (void)reloadData;
/**
返回列表容器cell的高度
*/
- (CGFloat)listContainerCellHeight;
/**
返回列表容器cell
*/
- (UITableViewCell *)listContainerCellForRowAtIndexPath:(NSIndexPath *)indexPath;
/**
mainTableView的代理方法`scrollViewDidScroll`回调时需要调用该方法
*/
- (void)mainTableViewDidScroll:(UIScrollView *)scrollView;
@end
//
// JXPageView.m
//
// Created by jiaxin on 2018/9/13.
// Copyright © 2018年 jiaxin. All rights reserved.
//
#import "JXPageListView.h"
#import "JXPageListContainerView.h"
static NSString *const kListContainerCellIdentifier = @"jx_kListContainerCellIdentifier";
@interface JXPageListView () <JXPageListContainerViewDelegate, JXCategoryViewDelegate>
@property (nonatomic, strong) JXCategoryTitleView *pinCategoryView;
@property (nonatomic, weak) id<JXPageListViewDelegate> delegate;
@property (nonatomic, strong) JXPageListMainTableView *mainTableView;
@property (nonatomic, strong) JXPageListContainerView *listContainerView;
@property (nonatomic, strong) UIScrollView *currentScrollingListView;
@property (nonatomic, strong) UITableViewCell *listContainerCell;
@property (nonatomic, strong) NSArray *originalListViews;
@end
@implementation JXPageListView
- (instancetype)initWithDelegate:(id<JXPageListViewDelegate>)delegate {
self = [super initWithFrame:CGRectZero];
if (self) {
_delegate = delegate;
[self initializeViews];
}
return self;
}
- (void)initializeViews {
_listViewScrollStateSaveEnabled = YES;
_pinCategoryViewVerticalOffset = 0;
self.pinCategoryView = [[JXCategoryTitleView alloc] initWithFrame:CGRectZero];
self.pinCategoryView.backgroundColor = [UIColor whiteColor];
self.pinCategoryView.delegate = self;
self.pinCategoryView.titleSelectedColor = [UIColor colorWithRed:105/255.0 green:144/255.0 blue:239/255.0 alpha:1];
self.pinCategoryView.titleColor = [UIColor blackColor];
self.pinCategoryView.titleColorGradientEnabled = YES;
self.pinCategoryView.titleLabelZoomEnabled = YES;
JXCategoryIndicatorLineView *lineView = [[JXCategoryIndicatorLineView alloc] init];
lineView.indicatorColor = [UIColor colorWithRed:105/255.0 green:144/255.0 blue:239/255.0 alpha:1];
lineView.indicatorWidth = 30;
self.pinCategoryView.indicators = @[lineView];
_mainTableView = [[JXPageListMainTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.mainTableView.showsVerticalScrollIndicator = NO;
self.mainTableView.tableFooterView = [UIView new];
[self.mainTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kListContainerCellIdentifier];
if (@available(iOS 11.0, *)) {
self.mainTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.mainTableView];
_listContainerView = [[JXPageListContainerView alloc] initWithDelegate:self];
self.listContainerView.mainTableView = self.mainTableView;
self.pinCategoryView.contentScrollView = self.listContainerView.collectionView;
[self configListViewDidScrollCallback];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.mainTableView.frame = self.bounds;
}
- (void)reloadData {
//先移除以前的listView
for (UIView *listView in self.originalListViews) {
[listView removeFromSuperview];
}
[self configListViewDidScrollCallback];
[self.mainTableView reloadData];
[self.listContainerView reloadData];
[self.pinCategoryView reloadData];
}
- (CGFloat)listContainerCellHeight {
return self.bounds.size.height - self.pinCategoryViewVerticalOffset;
}
- (UITableViewCell *)listContainerCellForRowAtIndexPath:(NSIndexPath *)indexPath {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//触发第一个列表的下拉刷新
NSArray *listViews = [self.delegate listViewsInPageListView:self];
if (listViews.count > 0) {
[listViews[self.pinCategoryView.selectedIndex] listViewLoadDataIfNeeded];
}
});
UITableViewCell *cell = [self.mainTableView dequeueReusableCellWithIdentifier:kListContainerCellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
self.listContainerCell = cell;
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
self.pinCategoryView.frame = CGRectMake(0, 0, cell.contentView.bounds.size.width, self.pinCategoryViewHeight);
if (self.pinCategoryView.superview != cell.contentView) {
//首次使用pinCategoryView的时候,把pinCategoryView添加到它上面。
[cell.contentView addSubview:self.pinCategoryView];
}
self.listContainerView.frame = CGRectMake(0, self.pinCategoryViewHeight, cell.contentView.bounds.size.width, cell.contentView.bounds.size.height - self.pinCategoryViewHeight);
[cell.contentView addSubview:self.listContainerView];
return cell;
}
- (void)mainTableViewDidScroll:(UIScrollView *)scrollView {
//处理当页面滚动在最底部pageView的时候,页面需要立即停住,没有回弹效果。给用户一种切换到pageView浏览模式了
if (scrollView.contentOffset.y > 10) {
scrollView.bounces = NO;
}else {
scrollView.bounces = YES;
}
CGFloat topContentY = [self mainTableViewMaxContentOffsetY];
if (scrollView.contentOffset.y >= topContentY) {
//当滚动的contentOffset.y大于了指定sectionHeader的y值,且还没有被添加到self.view上的时候,就需要切换superView
if (self.pinCategoryView.superview != self) {
CGRect frame = self.pinCategoryView.frame;
frame.origin.y = self.pinCategoryViewVerticalOffset;
self.pinCategoryView.frame = frame;
[self addSubview:self.pinCategoryView];
}
}else if (self.pinCategoryView.superview != self.listContainerCell.contentView) {
//当滚动的contentOffset.y小于了指定sectionHeader的y值,且还没有被添加到sectionCategoryHeaderView上的时候,就需要切换superView
CGRect frame = self.pinCategoryView.frame;
frame.origin.y = 0;
self.pinCategoryView.frame = frame;
[self.listContainerCell.contentView addSubview:self.pinCategoryView];
}
if (scrollView.isTracking || scrollView.isDecelerating) {
//用户滚动的才处理
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
self.mainTableView.contentOffset = CGPointMake(self.mainTableView.contentOffset.x, topContentY);
}
}
if (!self.isListViewScrollStateSaveEnabled) {
if (scrollView.contentOffset.y < topContentY) {
//mainTableView已经显示了header,listView的contentOffset需要重置
NSArray *listViews = [self.delegate listViewsInPageListView:self];
CGFloat insetTop = scrollView.contentInset.top;
if (@available(iOS 11.0, *)) {
insetTop = scrollView.adjustedContentInset.top;
}
for (UIView <JXPageListViewListDelegate>* listView in listViews) {
[listView listScrollView].contentOffset = CGPointMake(self.mainTableView.contentOffset.x, -insetTop);
}
}
}
}
#pragma mark - Private
- (void)configListViewDidScrollCallback {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
self.originalListViews = listViews;
for (UIView <JXPageListViewListDelegate>* listView in listViews) {
__weak typeof(self)weakSelf = self;
[listView listViewDidScrollCallback:^(UIScrollView *scrollView) {
[weakSelf listViewDidScroll:scrollView];
}];
}
}
- (void)listViewDidSelectedAtIndex:(NSInteger)index {
UIView<JXPageListViewListDelegate> *listContainerView = [self.delegate listViewsInPageListView:self][index];
if ([listContainerView listScrollView].contentOffset.y > 0) {
//当前列表视图已经滚动显示了内容
[self.mainTableView setContentOffset:CGPointMake(self.mainTableView.contentOffset.x, [self mainTableViewMaxContentOffsetY]) animated:YES];
}
}
- (CGFloat)mainTableViewMaxContentOffsetY {
return floor(self.mainTableView.contentSize.height) - self.bounds.size.height;
}
- (void)listViewDidScroll:(UIScrollView *)scrollView {
self.currentScrollingListView = scrollView;
if (scrollView.isTracking == NO && scrollView.isDecelerating == NO) {
return;
}
CGFloat topContentHeight = [self mainTableViewMaxContentOffsetY];
if (self.mainTableView.contentOffset.y < topContentHeight) {
//mainTableView的header还没有消失,让listScrollView固定
CGFloat insetTop = scrollView.contentInset.top;
if (@available(iOS 11.0, *)) {
insetTop = scrollView.adjustedContentInset.top;
}
scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, -insetTop);
}else {
//mainTableView的header刚好消失,固定mainTableView的位置,显示listScrollView的滚动条
self.mainTableView.contentOffset = CGPointMake(self.mainTableView.contentOffset.x, topContentHeight);
}
}
#pragma mark - JXPagingListContainerViewDelegate
- (NSInteger)numberOfRowsInListContainerView:(JXPageListContainerView *)listContainerView {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
return listViews.count;
}
- (UIView *)listContainerView:(JXPageListContainerView *)listContainerView listViewInRow:(NSInteger)row {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
return listViews[row];
}
- (void)listContainerView:(JXPageListContainerView *)listContainerView willDisplayCellAtRow:(NSInteger)row {
NSArray *listViews = [self.delegate listViewsInPageListView:self];
self.currentScrollingListView = [listViews[row] listScrollView];
}
#pragma mark - JXCategoryViewDelegate
- (void)categoryView:(JXCategoryBaseView *)categoryView didSelectedItemAtIndex:(NSInteger)index {
if ([self.delegate respondsToSelector:@selector(pinCategoryView:didSelectedItemAtIndex:)]) {
[self.delegate pinCategoryView:categoryView didSelectedItemAtIndex:index];
}
NSArray *listViews = [self.delegate listViewsInPageListView:self];
[listViews[index] listViewLoadDataIfNeeded];
}
- (void)categoryView:(JXCategoryBaseView *)categoryView didClickSelectedItemAtIndex:(NSInteger)index {
//点击选中,会立马回调该方法。但是page还在左右切换。所以延迟0.25秒等左右切换结束再处理。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self listViewDidSelectedAtIndex:index];
});
}
- (void)categoryView:(JXCategoryBaseView *)categoryView didScrollSelectedItemAtIndex:(NSInteger)index {
[self listViewDidSelectedAtIndex:index];
}
@end
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