首次提交
This commit is contained in:
116
SweetParty/第三方库/AvoidCrash/AvoidCrash.h
Normal file
116
SweetParty/第三方库/AvoidCrash/AvoidCrash.h
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
//===================================================
|
||||
// 使用方法和注意事项:
|
||||
// https://www.jianshu.com/p/2b90aa96c0a0
|
||||
//===================================================
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
//category
|
||||
#import "NSObject+AvoidCrash.h"
|
||||
|
||||
#import "NSArray+AvoidCrash.h"
|
||||
#import "NSMutableArray+AvoidCrash.h"
|
||||
|
||||
#import "NSDictionary+AvoidCrash.h"
|
||||
#import "NSMutableDictionary+AvoidCrash.h"
|
||||
|
||||
#import "NSString+AvoidCrash.h"
|
||||
#import "NSMutableString+AvoidCrash.h"
|
||||
|
||||
#import "NSAttributedString+AvoidCrash.h"
|
||||
#import "NSMutableAttributedString+AvoidCrash.h"
|
||||
|
||||
//define
|
||||
#import "AvoidCrashStubProxy.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@interface AvoidCrash : NSObject
|
||||
|
||||
//===================================================
|
||||
// 使用方法和注意事项:
|
||||
// https://www.jianshu.com/p/2b90aa96c0a0
|
||||
//===================================================
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* 开始生效.你可以在AppDelegate的didFinishLaunchingWithOptions方法中调用becomeEffective方法
|
||||
* 【默认不开启 对”unrecognized selector sent to instance”防止崩溃的处理】
|
||||
*
|
||||
* 这是全局生效,若你只需要部分生效,你可以单个进行处理,比如:
|
||||
* [NSArray avoidCrashExchangeMethod];
|
||||
* [NSMutableArray avoidCrashExchangeMethod];
|
||||
* .................
|
||||
* .................
|
||||
*
|
||||
*/
|
||||
+ (void)becomeEffective;
|
||||
|
||||
|
||||
/**
|
||||
* 相比于becomeEffective,增加
|
||||
* 对”unrecognized selector sent to instance”防止崩溃的处理
|
||||
*
|
||||
* 但是必须配合:
|
||||
* setupClassStringsArr:和
|
||||
* setupNoneSelClassStringPrefixsArr
|
||||
* 这两个方法可以同时使用
|
||||
*/
|
||||
+ (void)makeAllEffective;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 初始化一个需要防止”unrecognized selector sent to instance”的崩溃的类名数组
|
||||
* ⚠️不可将@"NSObject"加入classStrings数组中
|
||||
* ⚠️不可将UI前缀的字符串加入classStrings数组中
|
||||
*/
|
||||
+ (void)setupNoneSelClassStringsArr:(NSArray<NSString *> *)classStrings;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化一个需要防止”unrecognized selector sent to instance”的崩溃的类名前缀的数组
|
||||
* ⚠️不可将UI前缀的字符串(包括@"UI")加入classStringPrefixs数组中
|
||||
* ⚠️不可将NS前缀的字符串(包括@"NS")加入classStringPrefixs数组中
|
||||
*/
|
||||
+ (void)setupNoneSelClassStringPrefixsArr:(NSArray<NSString *> *)classStringPrefixs;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//您可以忽略以下方法
|
||||
|
||||
+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel;
|
||||
|
||||
+ (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel;
|
||||
|
||||
+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo;
|
||||
|
||||
|
||||
@end
|
||||
219
SweetParty/第三方库/AvoidCrash/AvoidCrash.m
Normal file
219
SweetParty/第三方库/AvoidCrash/AvoidCrash.m
Normal file
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
|
||||
|
||||
|
||||
#define key_errorName @"errorName"
|
||||
#define key_errorReason @"errorReason"
|
||||
#define key_errorPlace @"errorPlace"
|
||||
#define key_defaultToDo @"defaultToDo"
|
||||
#define key_callStackSymbols @"callStackSymbols"
|
||||
#define key_exception @"exception"
|
||||
|
||||
|
||||
@implementation AvoidCrash
|
||||
|
||||
|
||||
+ (void)becomeEffective {
|
||||
[self effectiveIfDealWithNoneSel:NO];
|
||||
|
||||
}
|
||||
|
||||
+ (void)makeAllEffective {
|
||||
[self effectiveIfDealWithNoneSel:YES];
|
||||
}
|
||||
|
||||
+ (void)effectiveIfDealWithNoneSel:(BOOL)dealWithNoneSel {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
[NSObject avoidCrashExchangeMethodIfDealWithNoneSel:dealWithNoneSel];
|
||||
|
||||
[NSArray avoidCrashExchangeMethod];
|
||||
[NSMutableArray avoidCrashExchangeMethod];
|
||||
|
||||
[NSDictionary avoidCrashExchangeMethod];
|
||||
[NSMutableDictionary avoidCrashExchangeMethod];
|
||||
|
||||
[NSString avoidCrashExchangeMethod];
|
||||
[NSMutableString avoidCrashExchangeMethod];
|
||||
|
||||
[NSAttributedString avoidCrashExchangeMethod];
|
||||
[NSMutableAttributedString avoidCrashExchangeMethod];
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)setupNoneSelClassStringsArr:(NSArray<NSString *> *)classStrings {
|
||||
[NSObject setupNoneSelClassStringsArr:classStrings];
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化一个需要防止”unrecognized selector sent to instance”的崩溃的类名前缀的数组
|
||||
*/
|
||||
+ (void)setupNoneSelClassStringPrefixsArr:(NSArray<NSString *> *)classStringPrefixs {
|
||||
[NSObject setupNoneSelClassStringPrefixsArr:classStringPrefixs];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 类方法的交换
|
||||
*
|
||||
* @param anClass 哪个类
|
||||
* @param method1Sel 方法1
|
||||
* @param method2Sel 方法2
|
||||
*/
|
||||
+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
|
||||
Method method1 = class_getClassMethod(anClass, method1Sel);
|
||||
Method method2 = class_getClassMethod(anClass, method2Sel);
|
||||
method_exchangeImplementations(method1, method2);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对象方法的交换
|
||||
*
|
||||
* @param anClass 哪个类
|
||||
* @param method1Sel 方法1(原本的方法)
|
||||
* @param method2Sel 方法2(要替换成的方法)
|
||||
*/
|
||||
+ (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
|
||||
|
||||
|
||||
Method originalMethod = class_getInstanceMethod(anClass, method1Sel);
|
||||
Method swizzledMethod = class_getInstanceMethod(anClass, method2Sel);
|
||||
|
||||
BOOL didAddMethod =
|
||||
class_addMethod(anClass,
|
||||
method1Sel,
|
||||
method_getImplementation(swizzledMethod),
|
||||
method_getTypeEncoding(swizzledMethod));
|
||||
|
||||
if (didAddMethod) {
|
||||
class_replaceMethod(anClass,
|
||||
method2Sel,
|
||||
method_getImplementation(originalMethod),
|
||||
method_getTypeEncoding(originalMethod));
|
||||
}
|
||||
|
||||
else {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取堆栈主要崩溃精简化的信息<根据正则表达式匹配出来>
|
||||
*
|
||||
* @param callStackSymbols 堆栈主要崩溃信息
|
||||
*
|
||||
* @return 堆栈主要崩溃精简化的信息
|
||||
*/
|
||||
|
||||
+ (NSString *)getMainCallStackSymbolMessageWithCallStackSymbols:(NSArray<NSString *> *)callStackSymbols {
|
||||
|
||||
//mainCallStackSymbolMsg的格式为 +[类名 方法名] 或者 -[类名 方法名]
|
||||
__block NSString *mainCallStackSymbolMsg = nil;
|
||||
|
||||
//匹配出来的格式为 +[类名 方法名] 或者 -[类名 方法名]
|
||||
NSString *regularExpStr = @"[-\\+]\\[.+\\]";
|
||||
|
||||
|
||||
NSRegularExpression *regularExp = [[NSRegularExpression alloc] initWithPattern:regularExpStr options:NSRegularExpressionCaseInsensitive error:nil];
|
||||
|
||||
|
||||
for (int index = 2; index < callStackSymbols.count; index++) {
|
||||
NSString *callStackSymbol = callStackSymbols[index];
|
||||
|
||||
[regularExp enumerateMatchesInString:callStackSymbol options:NSMatchingReportProgress range:NSMakeRange(0, callStackSymbol.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
|
||||
if (result) {
|
||||
NSString* tempCallStackSymbolMsg = [callStackSymbol substringWithRange:result.range];
|
||||
|
||||
//get className
|
||||
NSString *className = [tempCallStackSymbolMsg componentsSeparatedByString:@" "].firstObject;
|
||||
className = [className componentsSeparatedByString:@"["].lastObject;
|
||||
|
||||
NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(className)];
|
||||
|
||||
//filter category and system class
|
||||
if (![className hasSuffix:@")"] && bundle == [NSBundle mainBundle]) {
|
||||
mainCallStackSymbolMsg = tempCallStackSymbolMsg;
|
||||
|
||||
}
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
if (mainCallStackSymbolMsg.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return mainCallStackSymbolMsg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 提示崩溃的信息(控制台输出、通知)
|
||||
*
|
||||
* @param exception 捕获到的异常
|
||||
* @param defaultToDo 这个框架里默认的做法
|
||||
*/
|
||||
+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo {
|
||||
|
||||
//堆栈数据
|
||||
NSArray *callStackSymbolsArr = [NSThread callStackSymbols];
|
||||
|
||||
//获取在哪个类的哪个方法中实例化的数组 字符串格式 -[类名 方法名] 或者 +[类名 方法名]
|
||||
NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbolsArr];
|
||||
|
||||
if (mainCallStackSymbolMsg == nil) {
|
||||
|
||||
mainCallStackSymbolMsg = @"崩溃方法定位失败,请您查看函数调用栈来排查错误原因";
|
||||
|
||||
}
|
||||
|
||||
NSString *errorName = exception.name;
|
||||
NSString *errorReason = exception.reason;
|
||||
//errorReason 可能为 -[__NSCFConstantString avoidCrashCharacterAtIndex:]: Range or index out of bounds
|
||||
//将avoidCrash去掉
|
||||
errorReason = [errorReason stringByReplacingOccurrencesOfString:@"avoidCrash" withString:@""];
|
||||
|
||||
NSString *errorPlace = [NSString stringWithFormat:@"Error Place:%@",mainCallStackSymbolMsg];
|
||||
|
||||
NSString *logErrorMessage = [NSString stringWithFormat:@"\n\n%@\n\n%@\n%@\n%@\n%@",AvoidCrashSeparatorWithFlag, errorName, errorReason, errorPlace, defaultToDo];
|
||||
|
||||
logErrorMessage = [NSString stringWithFormat:@"%@\n\n%@\n\n",logErrorMessage,AvoidCrashSeparator];
|
||||
AvoidCrashLog(@"%@",logErrorMessage);
|
||||
|
||||
|
||||
//请忽略下面的赋值,目的只是为了能顺利上传到cocoapods
|
||||
logErrorMessage = logErrorMessage;
|
||||
|
||||
NSDictionary *errorInfoDic = @{
|
||||
key_errorName : errorName,
|
||||
key_errorReason : errorReason,
|
||||
key_errorPlace : errorPlace,
|
||||
key_defaultToDo : defaultToDo,
|
||||
key_exception : exception,
|
||||
key_callStackSymbols : callStackSymbolsArr
|
||||
};
|
||||
|
||||
//将错误信息放在字典里,用通知的形式发送出去
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
17
SweetParty/第三方库/AvoidCrash/AvoidCrashProtocol.h
Normal file
17
SweetParty/第三方库/AvoidCrash/AvoidCrashProtocol.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// AvoidCrashProtocol.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by chenfanfang on 2017/7/22.
|
||||
// Copyright © 2017年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
@protocol AvoidCrashProtocol <NSObject>
|
||||
|
||||
@required
|
||||
+ (void)avoidCrashExchangeMethod;
|
||||
|
||||
@end
|
||||
37
SweetParty/第三方库/AvoidCrash/AvoidCrashStubProxy.h
Normal file
37
SweetParty/第三方库/AvoidCrash/AvoidCrashStubProxy.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// AvoidCrashStubProxy.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by chenfanfang on 2017/7/25.
|
||||
// Copyright © 2017年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
#define AvoidCrashNotification @"AvoidCrashNotification"
|
||||
#define AvoidCrashIsiOS(version) ([[UIDevice currentDevice].systemVersion floatValue] >= version)
|
||||
|
||||
|
||||
//user can ignore below define
|
||||
#define AvoidCrashDefaultReturnNil @"AvoidCrash default is to return nil to avoid crash."
|
||||
#define AvoidCrashDefaultIgnore @"AvoidCrash default is to ignore this operation to avoid crash."
|
||||
|
||||
#define AvoidCrashSeparator @"================================================================"
|
||||
#define AvoidCrashSeparatorWithFlag @"========================AvoidCrash Log=========================="
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
#define AvoidCrashLog(...) NSLog(@"%@",[NSString stringWithFormat:__VA_ARGS__])
|
||||
|
||||
#else
|
||||
|
||||
#define AvoidCrashLog(...)
|
||||
#endif
|
||||
|
||||
@interface AvoidCrashStubProxy : NSObject
|
||||
|
||||
- (void)proxyMethod;
|
||||
|
||||
@end
|
||||
18
SweetParty/第三方库/AvoidCrash/AvoidCrashStubProxy.m
Normal file
18
SweetParty/第三方库/AvoidCrash/AvoidCrashStubProxy.m
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// AvoidCrashStubProxy.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by chenfanfang on 2017/7/25.
|
||||
// Copyright © 2017年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AvoidCrashStubProxy.h"
|
||||
|
||||
|
||||
@implementation AvoidCrashStubProxy
|
||||
|
||||
- (void)proxyMethod {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
25
SweetParty/第三方库/AvoidCrash/NSArray+AvoidCrash.h
Normal file
25
SweetParty/第三方库/AvoidCrash/NSArray+AvoidCrash.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// NSArray+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSArray (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1. NSArray的快速创建方式 NSArray *array = @[@"chenfanfang", @"AvoidCrash"]; //这种创建方式其实调用的是2中的方法
|
||||
* 2. +(instancetype)arrayWithObjects:(const id _Nonnull __unsafe_unretained *)objects count:(NSUInteger)cnt
|
||||
* 3. - (id)objectAtIndex:(NSUInteger)index
|
||||
* 4. - (void)getObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range
|
||||
*/
|
||||
258
SweetParty/第三方库/AvoidCrash/NSArray+AvoidCrash.m
Normal file
258
SweetParty/第三方库/AvoidCrash/NSArray+AvoidCrash.m
Normal file
@@ -0,0 +1,258 @@
|
||||
//
|
||||
// NSArray+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSArray+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSArray (AvoidCrash)
|
||||
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
//=================
|
||||
// class method
|
||||
//=================
|
||||
|
||||
//instance array method exchange
|
||||
[AvoidCrash exchangeClassMethod:[self class] method1Sel:@selector(arrayWithObjects:count:) method2Sel:@selector(AvoidCrashArrayWithObjects:count:)];
|
||||
|
||||
|
||||
|
||||
//====================
|
||||
// instance method
|
||||
//====================
|
||||
|
||||
Class __NSArray = NSClassFromString(@"NSArray");
|
||||
Class __NSArrayI = NSClassFromString(@"__NSArrayI");
|
||||
Class __NSSingleObjectArrayI = NSClassFromString(@"__NSSingleObjectArrayI");
|
||||
Class __NSArray0 = NSClassFromString(@"__NSArray0");
|
||||
|
||||
|
||||
//objectsAtIndexes:
|
||||
[AvoidCrash exchangeInstanceMethod:__NSArray method1Sel:@selector(objectsAtIndexes:) method2Sel:@selector(avoidCrashObjectsAtIndexes:)];
|
||||
|
||||
|
||||
//objectAtIndex:
|
||||
|
||||
[AvoidCrash exchangeInstanceMethod:__NSArrayI method1Sel:@selector(objectAtIndex:) method2Sel:@selector(__NSArrayIAvoidCrashObjectAtIndex:)];
|
||||
|
||||
[AvoidCrash exchangeInstanceMethod:__NSSingleObjectArrayI method1Sel:@selector(objectAtIndex:) method2Sel:@selector(__NSSingleObjectArrayIAvoidCrashObjectAtIndex:)];
|
||||
|
||||
[AvoidCrash exchangeInstanceMethod:__NSArray0 method1Sel:@selector(objectAtIndex:) method2Sel:@selector(__NSArray0AvoidCrashObjectAtIndex:)];
|
||||
|
||||
//objectAtIndexedSubscript:
|
||||
if (AvoidCrashIsiOS(11.0)) {
|
||||
[AvoidCrash exchangeInstanceMethod:__NSArrayI method1Sel:@selector(objectAtIndexedSubscript:) method2Sel:@selector(__NSArrayIAvoidCrashObjectAtIndexedSubscript:)];
|
||||
}
|
||||
|
||||
|
||||
//getObjects:range:
|
||||
[AvoidCrash exchangeInstanceMethod:__NSArray method1Sel:@selector(getObjects:range:) method2Sel:@selector(NSArrayAvoidCrashGetObjects:range:)];
|
||||
|
||||
[AvoidCrash exchangeInstanceMethod:__NSSingleObjectArrayI method1Sel:@selector(getObjects:range:) method2Sel:@selector(__NSSingleObjectArrayIAvoidCrashGetObjects:range:)];
|
||||
|
||||
[AvoidCrash exchangeInstanceMethod:__NSArrayI method1Sel:@selector(getObjects:range:) method2Sel:@selector(__NSArrayIAvoidCrashGetObjects:range:)];
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// instance array
|
||||
//=================================================================
|
||||
#pragma mark - instance array
|
||||
|
||||
|
||||
+ (instancetype)AvoidCrashArrayWithObjects:(const id _Nonnull __unsafe_unretained *)objects count:(NSUInteger)cnt {
|
||||
|
||||
id instance = nil;
|
||||
|
||||
@try {
|
||||
instance = [self AvoidCrashArrayWithObjects:objects count:cnt];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = @"AvoidCrash default is to remove nil object and instance a array.";
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
//以下是对错误数据的处理,把为nil的数据去掉,然后初始化数组
|
||||
NSInteger newObjsIndex = 0;
|
||||
id _Nonnull __unsafe_unretained newObjects[cnt];
|
||||
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
if (objects[i] != nil) {
|
||||
newObjects[newObjsIndex] = objects[i];
|
||||
newObjsIndex++;
|
||||
}
|
||||
}
|
||||
instance = [self AvoidCrashArrayWithObjects:newObjects count:newObjsIndex];
|
||||
}
|
||||
@finally {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================================================================
|
||||
// objectAtIndexedSubscript:
|
||||
//=================================================================
|
||||
#pragma mark - objectAtIndexedSubscript:
|
||||
- (id)__NSArrayIAvoidCrashObjectAtIndexedSubscript:(NSUInteger)idx {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self __NSArrayIAvoidCrashObjectAtIndexedSubscript:idx];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// objectsAtIndexes:
|
||||
//=================================================================
|
||||
#pragma mark - objectsAtIndexes:
|
||||
|
||||
- (NSArray *)avoidCrashObjectsAtIndexes:(NSIndexSet *)indexes {
|
||||
|
||||
NSArray *returnArray = nil;
|
||||
@try {
|
||||
returnArray = [self avoidCrashObjectsAtIndexes:indexes];
|
||||
} @catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
} @finally {
|
||||
return returnArray;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// objectAtIndex:
|
||||
//=================================================================
|
||||
#pragma mark - objectAtIndex:
|
||||
|
||||
//__NSArrayI objectAtIndex:
|
||||
- (id)__NSArrayIAvoidCrashObjectAtIndex:(NSUInteger)index {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self __NSArrayIAvoidCrashObjectAtIndex:index];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//__NSSingleObjectArrayI objectAtIndex:
|
||||
- (id)__NSSingleObjectArrayIAvoidCrashObjectAtIndex:(NSUInteger)index {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self __NSSingleObjectArrayIAvoidCrashObjectAtIndex:index];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
//__NSArray0 objectAtIndex:
|
||||
- (id)__NSArray0AvoidCrashObjectAtIndex:(NSUInteger)index {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self __NSArray0AvoidCrashObjectAtIndex:index];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// getObjects:range:
|
||||
//=================================================================
|
||||
#pragma mark - getObjects:range:
|
||||
|
||||
//NSArray getObjects:range:
|
||||
- (void)NSArrayAvoidCrashGetObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range {
|
||||
|
||||
@try {
|
||||
[self NSArrayAvoidCrashGetObjects:objects range:range];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
} @finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//__NSSingleObjectArrayI getObjects:range:
|
||||
- (void)__NSSingleObjectArrayIAvoidCrashGetObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range {
|
||||
|
||||
@try {
|
||||
[self __NSSingleObjectArrayIAvoidCrashGetObjects:objects range:range];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
} @finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//__NSArrayI getObjects:range:
|
||||
- (void)__NSArrayIAvoidCrashGetObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range {
|
||||
|
||||
@try {
|
||||
[self __NSArrayIAvoidCrashGetObjects:objects range:range];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
} @finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
||||
25
SweetParty/第三方库/AvoidCrash/NSAttributedString+AvoidCrash.h
Normal file
25
SweetParty/第三方库/AvoidCrash/NSAttributedString+AvoidCrash.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// NSAttributedString+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/15.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSAttributedString (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1.- (instancetype)initWithString:(NSString *)str
|
||||
* 2.- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr
|
||||
* 3.- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs
|
||||
*
|
||||
*
|
||||
*/
|
||||
96
SweetParty/第三方库/AvoidCrash/NSAttributedString+AvoidCrash.m
Normal file
96
SweetParty/第三方库/AvoidCrash/NSAttributedString+AvoidCrash.m
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// NSAttributedString+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/15.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSAttributedString+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSAttributedString (AvoidCrash)
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
Class NSConcreteAttributedString = NSClassFromString(@"NSConcreteAttributedString");
|
||||
|
||||
//initWithString:
|
||||
[AvoidCrash exchangeInstanceMethod:NSConcreteAttributedString method1Sel:@selector(initWithString:) method2Sel:@selector(avoidCrashInitWithString:)];
|
||||
|
||||
//initWithAttributedString
|
||||
[AvoidCrash exchangeInstanceMethod:NSConcreteAttributedString method1Sel:@selector(initWithAttributedString:) method2Sel:@selector(avoidCrashInitWithAttributedString:)];
|
||||
|
||||
//initWithString:attributes:
|
||||
[AvoidCrash exchangeInstanceMethod:NSConcreteAttributedString method1Sel:@selector(initWithString:attributes:) method2Sel:@selector(avoidCrashInitWithString:attributes:)];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// initWithString:
|
||||
//=================================================================
|
||||
#pragma mark - initWithString:
|
||||
|
||||
- (instancetype)avoidCrashInitWithString:(NSString *)str {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self avoidCrashInitWithString:str];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// initWithAttributedString
|
||||
//=================================================================
|
||||
#pragma mark - initWithAttributedString
|
||||
|
||||
- (instancetype)avoidCrashInitWithAttributedString:(NSAttributedString *)attrStr {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self avoidCrashInitWithAttributedString:attrStr];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// initWithString:attributes:
|
||||
//=================================================================
|
||||
#pragma mark - initWithString:attributes:
|
||||
|
||||
- (instancetype)avoidCrashInitWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self avoidCrashInitWithString:str attributes:attrs];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
24
SweetParty/第三方库/AvoidCrash/NSDictionary+AvoidCrash.h
Normal file
24
SweetParty/第三方库/AvoidCrash/NSDictionary+AvoidCrash.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// NSDictionary+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSDictionary (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1. NSDictionary的快速创建方式 NSDictionary *dict = @{@"frameWork" : @"AvoidCrash"}; //这种创建方式其实调用的是2中的方法
|
||||
* 2. +(instancetype)dictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt
|
||||
*
|
||||
*/
|
||||
56
SweetParty/第三方库/AvoidCrash/NSDictionary+AvoidCrash.m
Normal file
56
SweetParty/第三方库/AvoidCrash/NSDictionary+AvoidCrash.m
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// NSDictionary+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSDictionary+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSDictionary (AvoidCrash)
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
[AvoidCrash exchangeClassMethod:self method1Sel:@selector(dictionaryWithObjects:forKeys:count:) method2Sel:@selector(avoidCrashDictionaryWithObjects:forKeys:count:)];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype)avoidCrashDictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt {
|
||||
|
||||
id instance = nil;
|
||||
|
||||
@try {
|
||||
instance = [self avoidCrashDictionaryWithObjects:objects forKeys:keys count:cnt];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = @"AvoidCrash default is to remove nil key-values and instance a dictionary.";
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
//处理错误的数据,然后重新初始化一个字典
|
||||
NSUInteger index = 0;
|
||||
id _Nonnull __unsafe_unretained newObjects[cnt];
|
||||
id _Nonnull __unsafe_unretained newkeys[cnt];
|
||||
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
if (objects[i] && keys[i]) {
|
||||
newObjects[index] = objects[i];
|
||||
newkeys[index] = keys[i];
|
||||
index++;
|
||||
}
|
||||
}
|
||||
instance = [self avoidCrashDictionaryWithObjects:newObjects forKeys:newkeys count:index];
|
||||
}
|
||||
@finally {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
26
SweetParty/第三方库/AvoidCrash/NSMutableArray+AvoidCrash.h
Normal file
26
SweetParty/第三方库/AvoidCrash/NSMutableArray+AvoidCrash.h
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// NSMutableArray+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSMutableArray (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1. - (id)objectAtIndex:(NSUInteger)index
|
||||
* 2. - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
|
||||
* 3. - (void)removeObjectAtIndex:(NSUInteger)index
|
||||
* 4. - (void)insertObject:(id)anObject atIndex:(NSUInteger)index
|
||||
* 5. - (void)getObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range
|
||||
*/
|
||||
169
SweetParty/第三方库/AvoidCrash/NSMutableArray+AvoidCrash.m
Normal file
169
SweetParty/第三方库/AvoidCrash/NSMutableArray+AvoidCrash.m
Normal file
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// NSMutableArray+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/21.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSMutableArray+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSMutableArray (AvoidCrash)
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
Class arrayMClass = NSClassFromString(@"__NSArrayM");
|
||||
|
||||
|
||||
//objectAtIndex:
|
||||
[AvoidCrash exchangeInstanceMethod:arrayMClass method1Sel:@selector(objectAtIndex:) method2Sel:@selector(avoidCrashObjectAtIndex:)];
|
||||
|
||||
//objectAtIndexedSubscript
|
||||
if (AvoidCrashIsiOS(11.0)) {
|
||||
[AvoidCrash exchangeInstanceMethod:arrayMClass method1Sel:@selector(objectAtIndexedSubscript:) method2Sel:@selector(avoidCrashObjectAtIndexedSubscript:)];
|
||||
}
|
||||
|
||||
|
||||
//setObject:atIndexedSubscript:
|
||||
[AvoidCrash exchangeInstanceMethod:arrayMClass method1Sel:@selector(setObject:atIndexedSubscript:) method2Sel:@selector(avoidCrashSetObject:atIndexedSubscript:)];
|
||||
|
||||
|
||||
//removeObjectAtIndex:
|
||||
[AvoidCrash exchangeInstanceMethod:arrayMClass method1Sel:@selector(removeObjectAtIndex:) method2Sel:@selector(avoidCrashRemoveObjectAtIndex:)];
|
||||
|
||||
//insertObject:atIndex:
|
||||
[AvoidCrash exchangeInstanceMethod:arrayMClass method1Sel:@selector(insertObject:atIndex:) method2Sel:@selector(avoidCrashInsertObject:atIndex:)];
|
||||
|
||||
//getObjects:range:
|
||||
[AvoidCrash exchangeInstanceMethod:arrayMClass method1Sel:@selector(getObjects:range:) method2Sel:@selector(avoidCrashGetObjects:range:)];
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// array set object at index
|
||||
//=================================================================
|
||||
#pragma mark - get object from array
|
||||
|
||||
|
||||
- (void)avoidCrashSetObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
|
||||
|
||||
@try {
|
||||
[self avoidCrashSetObject:obj atIndexedSubscript:idx];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:AvoidCrashDefaultIgnore];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// removeObjectAtIndex:
|
||||
//=================================================================
|
||||
#pragma mark - removeObjectAtIndex:
|
||||
|
||||
- (void)avoidCrashRemoveObjectAtIndex:(NSUInteger)index {
|
||||
@try {
|
||||
[self avoidCrashRemoveObjectAtIndex:index];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:AvoidCrashDefaultIgnore];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// insertObject:atIndex:
|
||||
//=================================================================
|
||||
#pragma mark - set方法
|
||||
- (void)avoidCrashInsertObject:(id)anObject atIndex:(NSUInteger)index {
|
||||
@try {
|
||||
[self avoidCrashInsertObject:anObject atIndex:index];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:AvoidCrashDefaultIgnore];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// objectAtIndex:
|
||||
//=================================================================
|
||||
#pragma mark - objectAtIndex:
|
||||
|
||||
- (id)avoidCrashObjectAtIndex:(NSUInteger)index {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self avoidCrashObjectAtIndex:index];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// objectAtIndexedSubscript:
|
||||
//=================================================================
|
||||
#pragma mark - objectAtIndexedSubscript:
|
||||
- (id)avoidCrashObjectAtIndexedSubscript:(NSUInteger)idx {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self avoidCrashObjectAtIndexedSubscript:idx];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// getObjects:range:
|
||||
//=================================================================
|
||||
#pragma mark - getObjects:range:
|
||||
|
||||
- (void)avoidCrashGetObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range {
|
||||
|
||||
@try {
|
||||
[self avoidCrashGetObjects:objects range:range];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
} @finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// NSMutableAttributedString+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/15.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSMutableAttributedString (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1.- (instancetype)initWithString:(NSString *)str
|
||||
* 2.- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs
|
||||
*/
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// NSMutableAttributedString+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/15.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSMutableAttributedString+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSMutableAttributedString (AvoidCrash)
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
Class NSConcreteMutableAttributedString = NSClassFromString(@"NSConcreteMutableAttributedString");
|
||||
|
||||
//initWithString:
|
||||
[AvoidCrash exchangeInstanceMethod:NSConcreteMutableAttributedString method1Sel:@selector(initWithString:) method2Sel:@selector(avoidCrashInitWithString:)];
|
||||
|
||||
//initWithString:attributes:
|
||||
[AvoidCrash exchangeInstanceMethod:NSConcreteMutableAttributedString method1Sel:@selector(initWithString:attributes:) method2Sel:@selector(avoidCrashInitWithString:attributes:)];
|
||||
});
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// initWithString:
|
||||
//=================================================================
|
||||
#pragma mark - initWithString:
|
||||
|
||||
|
||||
- (instancetype)avoidCrashInitWithString:(NSString *)str {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self avoidCrashInitWithString:str];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// initWithString:attributes:
|
||||
//=================================================================
|
||||
#pragma mark - initWithString:attributes:
|
||||
|
||||
|
||||
- (instancetype)avoidCrashInitWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs {
|
||||
id object = nil;
|
||||
|
||||
@try {
|
||||
object = [self avoidCrashInitWithString:str attributes:attrs];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
24
SweetParty/第三方库/AvoidCrash/NSMutableDictionary+AvoidCrash.h
Normal file
24
SweetParty/第三方库/AvoidCrash/NSMutableDictionary+AvoidCrash.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// NSMutableDictionary+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/22.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSMutableDictionary (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1. - (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
|
||||
* 2. - (void)removeObjectForKey:(id)aKey
|
||||
*
|
||||
*/
|
||||
95
SweetParty/第三方库/AvoidCrash/NSMutableDictionary+AvoidCrash.m
Normal file
95
SweetParty/第三方库/AvoidCrash/NSMutableDictionary+AvoidCrash.m
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// NSMutableDictionary+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/9/22.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSMutableDictionary+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSMutableDictionary (AvoidCrash)
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class dictionaryM = NSClassFromString(@"__NSDictionaryM");
|
||||
|
||||
//setObject:forKey:
|
||||
[AvoidCrash exchangeInstanceMethod:dictionaryM method1Sel:@selector(setObject:forKey:) method2Sel:@selector(avoidCrashSetObject:forKey:)];
|
||||
|
||||
//setObject:forKeyedSubscript:
|
||||
if (AvoidCrashIsiOS(11.0)) {
|
||||
[AvoidCrash exchangeInstanceMethod:dictionaryM method1Sel:@selector(setObject:forKeyedSubscript:) method2Sel:@selector(avoidCrashSetObject:forKeyedSubscript:)];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//removeObjectForKey:
|
||||
Method removeObjectForKey = class_getInstanceMethod(dictionaryM, @selector(removeObjectForKey:));
|
||||
Method avoidCrashRemoveObjectForKey = class_getInstanceMethod(dictionaryM, @selector(avoidCrashRemoveObjectForKey:));
|
||||
method_exchangeImplementations(removeObjectForKey, avoidCrashRemoveObjectForKey);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// setObject:forKey:
|
||||
//=================================================================
|
||||
#pragma mark - setObject:forKey:
|
||||
|
||||
- (void)avoidCrashSetObject:(id)anObject forKey:(id<NSCopying>)aKey {
|
||||
|
||||
@try {
|
||||
[self avoidCrashSetObject:anObject forKey:aKey];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:AvoidCrashDefaultIgnore];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// setObject:forKeyedSubscript:
|
||||
//=================================================================
|
||||
#pragma mark - setObject:forKeyedSubscript:
|
||||
- (void)avoidCrashSetObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
|
||||
@try {
|
||||
[self avoidCrashSetObject:obj forKeyedSubscript:key];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:AvoidCrashDefaultIgnore];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// removeObjectForKey:
|
||||
//=================================================================
|
||||
#pragma mark - removeObjectForKey:
|
||||
|
||||
- (void)avoidCrashRemoveObjectForKey:(id)aKey {
|
||||
|
||||
@try {
|
||||
[self avoidCrashRemoveObjectForKey:aKey];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:AvoidCrashDefaultIgnore];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
||||
29
SweetParty/第三方库/AvoidCrash/NSMutableString+AvoidCrash.h
Normal file
29
SweetParty/第三方库/AvoidCrash/NSMutableString+AvoidCrash.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// NSMutableString+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/6.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSMutableString (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1. 由于NSMutableString是继承于NSString,所以这里和NSString有些同样的方法就不重复写了
|
||||
* 2. - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
|
||||
* 3. - (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc
|
||||
* 4. - (void)deleteCharactersInRange:(NSRange)range
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
97
SweetParty/第三方库/AvoidCrash/NSMutableString+AvoidCrash.m
Normal file
97
SweetParty/第三方库/AvoidCrash/NSMutableString+AvoidCrash.m
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// NSMutableString+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/6.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSMutableString+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSMutableString (AvoidCrash)
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
Class stringClass = NSClassFromString(@"__NSCFString");
|
||||
|
||||
//replaceCharactersInRange
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(replaceCharactersInRange:withString:) method2Sel:@selector(avoidCrashReplaceCharactersInRange:withString:)];
|
||||
|
||||
//insertString:atIndex:
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(insertString:atIndex:) method2Sel:@selector(avoidCrashInsertString:atIndex:)];
|
||||
|
||||
//deleteCharactersInRange
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(deleteCharactersInRange:) method2Sel:@selector(avoidCrashDeleteCharactersInRange:)];
|
||||
});
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// replaceCharactersInRange
|
||||
//=================================================================
|
||||
#pragma mark - replaceCharactersInRange
|
||||
|
||||
- (void)avoidCrashReplaceCharactersInRange:(NSRange)range withString:(NSString *)aString {
|
||||
|
||||
@try {
|
||||
[self avoidCrashReplaceCharactersInRange:range withString:aString];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// insertString:atIndex:
|
||||
//=================================================================
|
||||
#pragma mark - insertString:atIndex:
|
||||
|
||||
- (void)avoidCrashInsertString:(NSString *)aString atIndex:(NSUInteger)loc {
|
||||
|
||||
@try {
|
||||
[self avoidCrashInsertString:aString atIndex:loc];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// deleteCharactersInRange
|
||||
//=================================================================
|
||||
#pragma mark - deleteCharactersInRange
|
||||
|
||||
- (void)avoidCrashDeleteCharactersInRange:(NSRange)range {
|
||||
|
||||
@try {
|
||||
[self avoidCrashDeleteCharactersInRange:range];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@end
|
||||
34
SweetParty/第三方库/AvoidCrash/NSObject+AvoidCrash.h
Normal file
34
SweetParty/第三方库/AvoidCrash/NSObject+AvoidCrash.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// NSObject+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/11.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface NSObject (AvoidCrash)
|
||||
|
||||
/**
|
||||
* ifDealWithNoneSel : 是否开启"unrecognized selector sent to instance"异常的捕获
|
||||
*/
|
||||
+ (void)avoidCrashExchangeMethodIfDealWithNoneSel:(BOOL)ifDealWithNoneSel;
|
||||
|
||||
|
||||
+ (void)setupNoneSelClassStringsArr:(NSArray<NSString *> *)classStrings;
|
||||
|
||||
+ (void)setupNoneSelClassStringPrefixsArr:(NSArray<NSString *> *)classStringPrefixs;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1.- (void)setValue:(id)value forKey:(NSString *)key
|
||||
* 2.- (void)setValue:(id)value forKeyPath:(NSString *)keyPath
|
||||
* 3.- (void)setValue:(id)value forUndefinedKey:(NSString *)key //这个方法一般用来重写,不会主动调用
|
||||
* 4.- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues
|
||||
* 5. unrecognized selector sent to instance
|
||||
*/
|
||||
221
SweetParty/第三方库/AvoidCrash/NSObject+AvoidCrash.m
Normal file
221
SweetParty/第三方库/AvoidCrash/NSObject+AvoidCrash.m
Normal file
@@ -0,0 +1,221 @@
|
||||
//
|
||||
// NSObject+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/11.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSObject+AvoidCrash.h"
|
||||
#import "AvoidCrash.h"
|
||||
#import "AvoidCrashStubProxy.h"
|
||||
|
||||
@implementation NSObject (AvoidCrash)
|
||||
|
||||
|
||||
+ (void)avoidCrashExchangeMethodIfDealWithNoneSel:(BOOL)ifDealWithNoneSel {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
//setValue:forKey:
|
||||
[AvoidCrash exchangeInstanceMethod:[self class] method1Sel:@selector(setValue:forKey:) method2Sel:@selector(avoidCrashSetValue:forKey:)];
|
||||
|
||||
//setValue:forKeyPath:
|
||||
[AvoidCrash exchangeInstanceMethod:[self class] method1Sel:@selector(setValue:forKeyPath:) method2Sel:@selector(avoidCrashSetValue:forKeyPath:)];
|
||||
|
||||
//setValue:forUndefinedKey:
|
||||
[AvoidCrash exchangeInstanceMethod:[self class] method1Sel:@selector(setValue:forUndefinedKey:) method2Sel:@selector(avoidCrashSetValue:forUndefinedKey:)];
|
||||
|
||||
//setValuesForKeysWithDictionary:
|
||||
[AvoidCrash exchangeInstanceMethod:[self class] method1Sel:@selector(setValuesForKeysWithDictionary:) method2Sel:@selector(avoidCrashSetValuesForKeysWithDictionary:)];
|
||||
|
||||
|
||||
//unrecognized selector sent to instance
|
||||
if (ifDealWithNoneSel) {
|
||||
[AvoidCrash exchangeInstanceMethod:[self class] method1Sel:@selector(methodSignatureForSelector:) method2Sel:@selector(avoidCrashMethodSignatureForSelector:)];
|
||||
[AvoidCrash exchangeInstanceMethod:[self class] method1Sel:@selector(forwardInvocation:) method2Sel:@selector(avoidCrashForwardInvocation:)];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// unrecognized selector sent to instance
|
||||
//=================================================================
|
||||
#pragma mark - unrecognized selector sent to instance
|
||||
|
||||
|
||||
static NSMutableArray *noneSelClassStrings;
|
||||
static NSMutableArray *noneSelClassStringPrefixs;
|
||||
|
||||
+ (void)setupNoneSelClassStringsArr:(NSArray<NSString *> *)classStrings {
|
||||
|
||||
if (noneSelClassStrings) {
|
||||
|
||||
NSString *warningMsg = [NSString stringWithFormat:@"\n\n%@\n\n[AvoidCrash setupNoneSelClassStringsArr:];\n调用一此即可,多次调用会自动忽略后面的调用\n\n%@\n\n",AvoidCrashSeparatorWithFlag,AvoidCrashSeparator];
|
||||
AvoidCrashLog(@"%@",warningMsg);
|
||||
}
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
noneSelClassStrings = [NSMutableArray array];
|
||||
for (NSString *className in classStrings) {
|
||||
if ([className hasPrefix:@"UI"] == NO &&
|
||||
[className isEqualToString:NSStringFromClass([NSObject class])] == NO) {
|
||||
[noneSelClassStrings addObject:className];
|
||||
|
||||
} else {
|
||||
NSString *warningMsg = [NSString stringWithFormat:@"\n\n%@\n\n[AvoidCrash setupNoneSelClassStringsArr:];\n会忽略UI开头的类和NSObject类(请使用NSObject的子类)\n\n%@\n\n",AvoidCrashSeparatorWithFlag,AvoidCrashSeparator];
|
||||
AvoidCrashLog(@"%@",warningMsg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化一个需要防止”unrecognized selector sent to instance”的崩溃的类名前缀的数组
|
||||
*/
|
||||
+ (void)setupNoneSelClassStringPrefixsArr:(NSArray<NSString *> *)classStringPrefixs {
|
||||
if (noneSelClassStringPrefixs) {
|
||||
|
||||
NSString *warningMsg = [NSString stringWithFormat:@"\n\n%@\n\n[AvoidCrash setupNoneSelClassStringPrefixsArr:];\n调用一此即可,多次调用会自动忽略后面的调用\n\n%@\n\n",AvoidCrashSeparatorWithFlag,AvoidCrashSeparator];
|
||||
AvoidCrashLog(@"%@",warningMsg);
|
||||
}
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
noneSelClassStringPrefixs = [NSMutableArray array];
|
||||
for (NSString *classNamePrefix in classStringPrefixs) {
|
||||
if ([classNamePrefix hasPrefix:@"UI"] == NO &&
|
||||
[classNamePrefix hasPrefix:@"NS"] == NO) {
|
||||
[noneSelClassStringPrefixs addObject:classNamePrefix];
|
||||
|
||||
} else {
|
||||
NSString *warningMsg = [NSString stringWithFormat:@"\n\n%@\n\n[AvoidCrash setupNoneSelClassStringsArr:];\n会忽略UI开头的类和NS开头的类\n若需要对NS开头的类防止”unrecognized selector sent to instance”(比如NSArray),请使用setupNoneSelClassStringsArr:\n\n%@\n\n",AvoidCrashSeparatorWithFlag,AvoidCrashSeparator];
|
||||
AvoidCrashLog(@"%@",warningMsg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (NSMethodSignature *)avoidCrashMethodSignatureForSelector:(SEL)aSelector {
|
||||
|
||||
NSMethodSignature *ms = [self avoidCrashMethodSignatureForSelector:aSelector];
|
||||
|
||||
BOOL flag = NO;
|
||||
if (ms == nil) {
|
||||
for (NSString *classStr in noneSelClassStrings) {
|
||||
if ([self isKindOfClass:NSClassFromString(classStr)]) {
|
||||
ms = [AvoidCrashStubProxy instanceMethodSignatureForSelector:@selector(proxyMethod)];
|
||||
flag = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag == NO) {
|
||||
NSString *selfClass = NSStringFromClass([self class]);
|
||||
for (NSString *classStrPrefix in noneSelClassStringPrefixs) {
|
||||
if ([selfClass hasPrefix:classStrPrefix]) {
|
||||
ms = [AvoidCrashStubProxy instanceMethodSignatureForSelector:@selector(proxyMethod)];
|
||||
}
|
||||
}
|
||||
}
|
||||
return ms;
|
||||
}
|
||||
|
||||
- (void)avoidCrashForwardInvocation:(NSInvocation *)anInvocation {
|
||||
|
||||
@try {
|
||||
[self avoidCrashForwardInvocation:anInvocation];
|
||||
|
||||
} @catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
|
||||
} @finally {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// setValue:forKey:
|
||||
//=================================================================
|
||||
#pragma mark - setValue:forKey:
|
||||
|
||||
- (void)avoidCrashSetValue:(id)value forKey:(NSString *)key {
|
||||
@try {
|
||||
[self avoidCrashSetValue:value forKey:key];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// setValue:forKeyPath:
|
||||
//=================================================================
|
||||
#pragma mark - setValue:forKeyPath:
|
||||
|
||||
- (void)avoidCrashSetValue:(id)value forKeyPath:(NSString *)keyPath {
|
||||
@try {
|
||||
[self avoidCrashSetValue:value forKeyPath:keyPath];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================================================================
|
||||
// setValue:forUndefinedKey:
|
||||
//=================================================================
|
||||
#pragma mark - setValue:forUndefinedKey:
|
||||
|
||||
- (void)avoidCrashSetValue:(id)value forUndefinedKey:(NSString *)key {
|
||||
@try {
|
||||
[self avoidCrashSetValue:value forUndefinedKey:key];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// setValuesForKeysWithDictionary:
|
||||
//=================================================================
|
||||
#pragma mark - setValuesForKeysWithDictionary:
|
||||
|
||||
- (void)avoidCrashSetValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues {
|
||||
@try {
|
||||
[self avoidCrashSetValuesForKeysWithDictionary:keyedValues];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultIgnore;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
29
SweetParty/第三方库/AvoidCrash/NSString+AvoidCrash.h
Normal file
29
SweetParty/第三方库/AvoidCrash/NSString+AvoidCrash.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// NSString+AvoidCrash.h
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/5.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AvoidCrashProtocol.h"
|
||||
|
||||
@interface NSString (AvoidCrash)<AvoidCrashProtocol>
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Can avoid crash method
|
||||
*
|
||||
* 1. - (unichar)characterAtIndex:(NSUInteger)index
|
||||
* 2. - (NSString *)substringFromIndex:(NSUInteger)from
|
||||
* 3. - (NSString *)substringToIndex:(NSUInteger)to {
|
||||
* 4. - (NSString *)substringWithRange:(NSRange)range {
|
||||
* 5. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
|
||||
* 6. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange
|
||||
* 7. - (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement
|
||||
*
|
||||
*/
|
||||
204
SweetParty/第三方库/AvoidCrash/NSString+AvoidCrash.m
Normal file
204
SweetParty/第三方库/AvoidCrash/NSString+AvoidCrash.m
Normal file
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// NSString+AvoidCrash.m
|
||||
// https://github.com/chenfanfang/AvoidCrash
|
||||
//
|
||||
// Created by mac on 16/10/5.
|
||||
// Copyright © 2016年 chenfanfang. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+AvoidCrash.h"
|
||||
|
||||
#import "AvoidCrash.h"
|
||||
|
||||
@implementation NSString (AvoidCrash)
|
||||
|
||||
+ (void)avoidCrashExchangeMethod {
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class stringClass = NSClassFromString(@"__NSCFConstantString");
|
||||
|
||||
//characterAtIndex
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(characterAtIndex:) method2Sel:@selector(avoidCrashCharacterAtIndex:)];
|
||||
|
||||
//substringFromIndex
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(substringFromIndex:) method2Sel:@selector(avoidCrashSubstringFromIndex:)];
|
||||
|
||||
//substringToIndex
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(substringToIndex:) method2Sel:@selector(avoidCrashSubstringToIndex:)];
|
||||
|
||||
//substringWithRange:
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(substringWithRange:) method2Sel:@selector(avoidCrashSubstringWithRange:)];
|
||||
|
||||
//stringByReplacingOccurrencesOfString:
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(stringByReplacingOccurrencesOfString:withString:) method2Sel:@selector(avoidCrashStringByReplacingOccurrencesOfString:withString:)];
|
||||
|
||||
//stringByReplacingOccurrencesOfString:withString:options:range:
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(stringByReplacingOccurrencesOfString:withString:options:range:) method2Sel:@selector(avoidCrashStringByReplacingOccurrencesOfString:withString:options:range:)];
|
||||
|
||||
//stringByReplacingCharactersInRange:withString:
|
||||
[AvoidCrash exchangeInstanceMethod:stringClass method1Sel:@selector(stringByReplacingCharactersInRange:withString:) method2Sel:@selector(avoidCrashStringByReplacingCharactersInRange:withString:)];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// characterAtIndex:
|
||||
//=================================================================
|
||||
#pragma mark - characterAtIndex:
|
||||
|
||||
- (unichar)avoidCrashCharacterAtIndex:(NSUInteger)index {
|
||||
|
||||
unichar characteristic;
|
||||
@try {
|
||||
characteristic = [self avoidCrashCharacterAtIndex:index];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = @"AvoidCrash default is to return a without assign unichar.";
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
}
|
||||
@finally {
|
||||
return characteristic;
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// substringFromIndex:
|
||||
//=================================================================
|
||||
#pragma mark - substringFromIndex:
|
||||
|
||||
- (NSString *)avoidCrashSubstringFromIndex:(NSUInteger)from {
|
||||
|
||||
NSString *subString = nil;
|
||||
|
||||
@try {
|
||||
subString = [self avoidCrashSubstringFromIndex:from];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
subString = nil;
|
||||
}
|
||||
@finally {
|
||||
return subString;
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// substringToIndex
|
||||
//=================================================================
|
||||
#pragma mark - substringToIndex
|
||||
|
||||
- (NSString *)avoidCrashSubstringToIndex:(NSUInteger)to {
|
||||
|
||||
NSString *subString = nil;
|
||||
|
||||
@try {
|
||||
subString = [self avoidCrashSubstringToIndex:to];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
subString = nil;
|
||||
}
|
||||
@finally {
|
||||
return subString;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// substringWithRange:
|
||||
//=================================================================
|
||||
#pragma mark - substringWithRange:
|
||||
|
||||
- (NSString *)avoidCrashSubstringWithRange:(NSRange)range {
|
||||
|
||||
NSString *subString = nil;
|
||||
|
||||
@try {
|
||||
subString = [self avoidCrashSubstringWithRange:range];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
subString = nil;
|
||||
}
|
||||
@finally {
|
||||
return subString;
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// stringByReplacingOccurrencesOfString:
|
||||
//=================================================================
|
||||
#pragma mark - stringByReplacingOccurrencesOfString:
|
||||
|
||||
- (NSString *)avoidCrashStringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement {
|
||||
|
||||
NSString *newStr = nil;
|
||||
|
||||
@try {
|
||||
newStr = [self avoidCrashStringByReplacingOccurrencesOfString:target withString:replacement];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
newStr = nil;
|
||||
}
|
||||
@finally {
|
||||
return newStr;
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
// stringByReplacingOccurrencesOfString:withString:options:range:
|
||||
//=================================================================
|
||||
#pragma mark - stringByReplacingOccurrencesOfString:withString:options:range:
|
||||
|
||||
- (NSString *)avoidCrashStringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange {
|
||||
|
||||
NSString *newStr = nil;
|
||||
|
||||
@try {
|
||||
newStr = [self avoidCrashStringByReplacingOccurrencesOfString:target withString:replacement options:options range:searchRange];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
newStr = nil;
|
||||
}
|
||||
@finally {
|
||||
return newStr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================================================
|
||||
// stringByReplacingCharactersInRange:withString:
|
||||
//=================================================================
|
||||
#pragma mark - stringByReplacingCharactersInRange:withString:
|
||||
|
||||
- (NSString *)avoidCrashStringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement {
|
||||
|
||||
|
||||
NSString *newStr = nil;
|
||||
|
||||
@try {
|
||||
newStr = [self avoidCrashStringByReplacingCharactersInRange:range withString:replacement];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSString *defaultToDo = AvoidCrashDefaultReturnNil;
|
||||
[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo];
|
||||
newStr = nil;
|
||||
}
|
||||
@finally {
|
||||
return newStr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
47
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressModel.h
Executable file
47
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressModel.h
Executable file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// BRAddressModel.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// 省
|
||||
@interface BRProvinceModel : NSObject
|
||||
/** 省对应的code或id */
|
||||
@property (nonatomic, copy) NSString *code;
|
||||
/** 省的名称 */
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
/** 省的索引 */
|
||||
@property (nonatomic, assign) NSInteger index;
|
||||
/** 城市数组 */
|
||||
@property (nonatomic, copy) NSArray *citylist;
|
||||
|
||||
@end
|
||||
|
||||
/// 市
|
||||
@interface BRCityModel : NSObject
|
||||
/** 市对应的code或id */
|
||||
@property (nonatomic, copy) NSString *code;
|
||||
/** 市的名称 */
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
/** 市的索引 */
|
||||
@property (nonatomic, assign) NSInteger index;
|
||||
/** 地区数组 */
|
||||
@property (nonatomic, copy) NSArray *arealist;
|
||||
|
||||
@end
|
||||
|
||||
/// 区
|
||||
@interface BRAreaModel : NSObject
|
||||
/** 区对应的code或id */
|
||||
@property (nonatomic, copy) NSString *code;
|
||||
/** 区的名称 */
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
/** 区的索引 */
|
||||
@property (nonatomic, assign) NSInteger index;
|
||||
|
||||
@end
|
||||
24
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressModel.m
Executable file
24
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressModel.m
Executable file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// BRAddressModel.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRAddressModel.h"
|
||||
|
||||
@implementation BRProvinceModel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation BRCityModel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation BRAreaModel
|
||||
|
||||
@end
|
||||
122
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressPickerView.h
Executable file
122
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressPickerView.h
Executable file
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// BRAddressPickerView.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRBaseView.h"
|
||||
#import "BRAddressModel.h"
|
||||
|
||||
/// 地址选择器类型
|
||||
typedef NS_ENUM(NSInteger, BRAddressPickerMode) {
|
||||
/** 显示【省市区】(默认) */
|
||||
BRAddressPickerModeArea,
|
||||
/** 显示【省市】 */
|
||||
BRAddressPickerModeCity,
|
||||
/** 显示【省】 */
|
||||
BRAddressPickerModeProvince
|
||||
};
|
||||
|
||||
typedef void(^BRAddressResultBlock)(BRProvinceModel *province, BRCityModel *city, BRAreaModel *area);
|
||||
|
||||
@interface BRAddressPickerView : BRBaseView
|
||||
|
||||
/**
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// 【用法一】
|
||||
/// 特点:灵活,扩展性强(推荐使用!)
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/** 地址选择器显示类型 */
|
||||
@property (nonatomic, assign) BRAddressPickerMode pickerMode;
|
||||
|
||||
/** 默认选中的位置(1.传索引数组,如:@[@10, @0, @4]) */
|
||||
@property (nonatomic, copy) NSArray <NSNumber *>* selectIndexs;
|
||||
/** 默认选中的位置(2.传值数组,如:@[@"浙江省", @"杭州市", @"西湖区"]) */
|
||||
@property (nonatomic, copy) NSArray <NSString *>* selectValues; // 推荐使用 selectIndexs
|
||||
|
||||
/** 选择结果的回调 */
|
||||
@property (nonatomic, copy) BRAddressResultBlock resultBlock;
|
||||
|
||||
/** 滚动选择时触发的回调 */
|
||||
@property (nonatomic, copy) BRAddressResultBlock changeBlock;
|
||||
|
||||
/**
|
||||
* 地区数据源(不传或为nil,默认就获取框架内 BRCity.json 文件的数据)
|
||||
* 1.可以传 JSON数组,要注意 层级结构 和 key 要与 BRCity.json 保持一致
|
||||
* 2.可以传 模型数组(NSArray <BRProvinceModel *>* 类型),自己解析数据源 只需要注意层级结构就行
|
||||
*/
|
||||
@property (nonatomic, copy) NSArray *dataSourceArr;
|
||||
|
||||
/// 初始化地址选择器
|
||||
/// @param pickerMode 地址选择器显示类型
|
||||
- (instancetype)initWithPickerMode:(BRAddressPickerMode)pickerMode;
|
||||
|
||||
/// 弹出选择器视图
|
||||
- (void)show;
|
||||
|
||||
/// 关闭选择器视图
|
||||
- (void)dismiss;
|
||||
|
||||
|
||||
|
||||
|
||||
//================================================= 华丽的分割线 =================================================
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// 【用法二】:快捷使用,直接选择下面其中的一个方法进行使用
|
||||
/// 特点:快捷,方便
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* 1.显示地址选择器
|
||||
*
|
||||
* @param selectIndexs 默认选中的值(传索引数组,如:@[@10, @0, @4])
|
||||
* @param resultBlock 选择后的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showAddressPickerWithSelectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
resultBlock:(BRAddressResultBlock)resultBlock;
|
||||
|
||||
/**
|
||||
* 2.显示地址选择器
|
||||
*
|
||||
* @param mode 地址选择器显示类型
|
||||
* @param selectIndexs 默认选中的值(传索引数组,如:@[@10, @0, @4])
|
||||
* @param isAutoSelect 是否自动选择,即滚动选择器后就执行结果回调,默认为 NO
|
||||
* @param resultBlock 选择后的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showAddressPickerWithMode:(BRAddressPickerMode)mode
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRAddressResultBlock)resultBlock;
|
||||
|
||||
/**
|
||||
* 3.显示地址选择器
|
||||
*
|
||||
* @param mode 地址选择器显示类型
|
||||
* @param dataSource 地区数据源
|
||||
* @param selectIndexs 默认选中的值(传索引数组,如:@[@10, @0, @4])
|
||||
* @param isAutoSelect 是否自动选择,即滚动选择器后就执行结果回调,默认为 NO
|
||||
* @param resultBlock 选择后的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showAddressPickerWithMode:(BRAddressPickerMode)mode
|
||||
dataSource:(NSArray *)dataSource
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRAddressResultBlock)resultBlock;
|
||||
|
||||
@end
|
||||
570
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressPickerView.m
Executable file
570
SweetParty/第三方库/BRPickerView/AddressPickerView/BRAddressPickerView.m
Executable file
@@ -0,0 +1,570 @@
|
||||
//
|
||||
// BRAddressPickerView.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRAddressPickerView.h"
|
||||
#import "NSBundle+BRPickerView.h"
|
||||
|
||||
@interface BRAddressPickerView ()<UIPickerViewDataSource, UIPickerViewDelegate>
|
||||
// 地址选择器
|
||||
@property (nonatomic, strong) UIPickerView *pickerView;
|
||||
// 省模型数组
|
||||
@property(nonatomic, copy) NSArray *provinceModelArr;
|
||||
// 市模型数组
|
||||
@property(nonatomic, copy) NSArray *cityModelArr;
|
||||
// 区模型数组
|
||||
@property(nonatomic, copy) NSArray *areaModelArr;
|
||||
// 选中的省
|
||||
@property(nonatomic, strong) BRProvinceModel *selectProvinceModel;
|
||||
// 选中的市
|
||||
@property(nonatomic, strong) BRCityModel *selectCityModel;
|
||||
// 选中的区
|
||||
@property(nonatomic, strong) BRAreaModel *selectAreaModel;
|
||||
// 记录省选中的位置
|
||||
@property(nonatomic, assign) NSInteger provinceIndex;
|
||||
// 记录市选中的位置
|
||||
@property(nonatomic, assign) NSInteger cityIndex;
|
||||
// 记录区选中的位置
|
||||
@property(nonatomic, assign) NSInteger areaIndex;
|
||||
|
||||
@property (nonatomic, copy) NSArray <NSString *>* mSelectValues;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BRAddressPickerView
|
||||
|
||||
#pragma mark - 1.显示地址选择器
|
||||
+ (void)showAddressPickerWithSelectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
resultBlock:(BRAddressResultBlock)resultBlock {
|
||||
[self showAddressPickerWithMode:BRAddressPickerModeArea dataSource:nil selectIndexs:selectIndexs isAutoSelect:NO resultBlock:resultBlock];
|
||||
}
|
||||
|
||||
#pragma mark - 2.显示地址选择器
|
||||
+ (void)showAddressPickerWithMode:(BRAddressPickerMode)mode
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRAddressResultBlock)resultBlock {
|
||||
[self showAddressPickerWithMode:mode dataSource:nil selectIndexs:selectIndexs isAutoSelect:isAutoSelect resultBlock:resultBlock];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - 3.显示地址选择器
|
||||
+ (void)showAddressPickerWithMode:(BRAddressPickerMode)mode
|
||||
dataSource:(NSArray *)dataSource
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRAddressResultBlock)resultBlock {
|
||||
// 创建地址选择器
|
||||
BRAddressPickerView *addressPickerView = [[BRAddressPickerView alloc] initWithPickerMode:mode];
|
||||
addressPickerView.dataSourceArr = dataSource;
|
||||
addressPickerView.selectIndexs = selectIndexs;
|
||||
addressPickerView.isAutoSelect = isAutoSelect;
|
||||
addressPickerView.resultBlock = resultBlock;
|
||||
// 显示
|
||||
[addressPickerView show];
|
||||
}
|
||||
|
||||
#pragma mark - 初始化地址选择器
|
||||
- (instancetype)initWithPickerMode:(BRAddressPickerMode)pickerMode {
|
||||
if (self = [super init]) {
|
||||
self.pickerMode = pickerMode;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - 处理选择器数据
|
||||
- (void)handlerPickerData {
|
||||
if (self.dataSourceArr && self.dataSourceArr.count > 0) {
|
||||
id element = [self.dataSourceArr firstObject];
|
||||
// 如果传的值是解析好的模型数组
|
||||
if ([element isKindOfClass:[BRProvinceModel class]]) {
|
||||
self.provinceModelArr = self.dataSourceArr;
|
||||
} else {
|
||||
self.provinceModelArr = [self getProvinceModelArr:self.dataSourceArr];
|
||||
}
|
||||
} else {
|
||||
// 如果外部没有传入地区数据源,就使用本地的数据源
|
||||
NSArray *dataSource = [NSBundle br_addressJsonArray];
|
||||
|
||||
if (!dataSource || dataSource.count == 0) {
|
||||
return;
|
||||
}
|
||||
self.dataSourceArr = dataSource;
|
||||
self.provinceModelArr = [self getProvinceModelArr:self.dataSourceArr];
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
[self handlerDefaultSelectValue];
|
||||
}
|
||||
|
||||
#pragma mark - 获取模型数组
|
||||
- (NSArray <BRProvinceModel *>*)getProvinceModelArr:(NSArray *)dataSourceArr {
|
||||
NSMutableArray *tempArr1 = [NSMutableArray array];
|
||||
for (NSDictionary *proviceDic in dataSourceArr) {
|
||||
BRProvinceModel *proviceModel = [[BRProvinceModel alloc]init];
|
||||
proviceModel.code = [proviceDic objectForKey:@"code"];
|
||||
proviceModel.name = [proviceDic objectForKey:@"name"];
|
||||
proviceModel.index = [dataSourceArr indexOfObject:proviceDic];
|
||||
NSArray *cityList = [proviceDic.allKeys containsObject:@"cityList"] ? [proviceDic objectForKey:@"cityList"] : [proviceDic objectForKey:@"citylist"];
|
||||
NSMutableArray *tempArr2 = [NSMutableArray array];
|
||||
for (NSDictionary *cityDic in cityList) {
|
||||
BRCityModel *cityModel = [[BRCityModel alloc]init];
|
||||
cityModel.code = [cityDic objectForKey:@"code"];
|
||||
cityModel.name = [cityDic objectForKey:@"name"];
|
||||
cityModel.index = [cityList indexOfObject:cityDic];
|
||||
NSArray *areaList = [cityDic.allKeys containsObject:@"areaList"] ? [cityDic objectForKey:@"areaList"] : [cityDic objectForKey:@"arealist"];
|
||||
NSMutableArray *tempArr3 = [NSMutableArray array];
|
||||
for (NSDictionary *areaDic in areaList) {
|
||||
BRAreaModel *areaModel = [[BRAreaModel alloc]init];
|
||||
areaModel.code = [areaDic objectForKey:@"code"];
|
||||
areaModel.name = [areaDic objectForKey:@"name"];
|
||||
areaModel.index = [areaList indexOfObject:areaDic];
|
||||
[tempArr3 addObject:areaModel];
|
||||
}
|
||||
cityModel.arealist = [tempArr3 copy];
|
||||
[tempArr2 addObject:cityModel];
|
||||
}
|
||||
proviceModel.citylist = [tempArr2 copy];
|
||||
[tempArr1 addObject:proviceModel];
|
||||
}
|
||||
return [tempArr1 copy];
|
||||
}
|
||||
|
||||
#pragma mark - 设置默认选择的值
|
||||
- (void)handlerDefaultSelectValue {
|
||||
__block NSString *selectProvinceName = nil;
|
||||
__block NSString *selectCityName = nil;
|
||||
__block NSString *selectAreaName = nil;
|
||||
|
||||
if (self.mSelectValues.count > 0) {
|
||||
selectProvinceName = self.mSelectValues.count > 0 ? self.mSelectValues[0] : nil;
|
||||
selectCityName = self.mSelectValues.count > 1 ? self.mSelectValues[1] : nil;
|
||||
selectAreaName = self.mSelectValues.count > 2 ? self.mSelectValues[2] : nil;
|
||||
}
|
||||
|
||||
if (self.pickerMode == BRAddressPickerModeProvince || self.pickerMode == BRAddressPickerModeCity || self.pickerMode == BRAddressPickerModeArea) {
|
||||
if (self.selectIndexs.count > 0) {
|
||||
NSInteger provinceIndex = [self.selectIndexs[0] integerValue];
|
||||
self.provinceIndex = (provinceIndex > 0 && provinceIndex < self.provinceModelArr.count) ? provinceIndex : 0;
|
||||
self.selectProvinceModel = self.provinceModelArr.count > self.provinceIndex ? self.provinceModelArr[self.provinceIndex] : nil;
|
||||
} else {
|
||||
@weakify(self)
|
||||
[self.provinceModelArr enumerateObjectsUsingBlock:^(BRProvinceModel * _Nonnull model, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
@strongify(self)
|
||||
if (selectProvinceName && [model.name isEqualToString:selectProvinceName]) {
|
||||
self.provinceIndex = idx;
|
||||
self.selectProvinceModel = model;
|
||||
*stop = YES;
|
||||
}
|
||||
if (idx == self.provinceModelArr.count - 1) {
|
||||
self.provinceIndex = 0;
|
||||
self.selectProvinceModel = self.provinceModelArr.count > 0 ? self.provinceModelArr[0] : nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if (self.pickerMode == BRAddressPickerModeCity || self.pickerMode == BRAddressPickerModeArea) {
|
||||
self.cityModelArr = [self getCityModelArray:self.provinceIndex];
|
||||
if (self.selectIndexs.count > 0) {
|
||||
NSInteger cityIndex = self.selectIndexs.count > 1 ? [self.selectIndexs[1] integerValue] : 0;
|
||||
self.cityIndex = (cityIndex > 0 && cityIndex < self.cityModelArr.count) ? cityIndex : 0;
|
||||
self.selectCityModel = self.cityModelArr.count > self.cityIndex ? self.cityModelArr[self.cityIndex] : nil;
|
||||
} else {
|
||||
@weakify(self)
|
||||
[self.cityModelArr enumerateObjectsUsingBlock:^(BRCityModel * _Nonnull model, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
@strongify(self)
|
||||
if (selectCityName && [model.name isEqualToString:selectCityName]) {
|
||||
self.cityIndex = idx;
|
||||
self.selectCityModel = model;
|
||||
*stop = YES;
|
||||
}
|
||||
if (idx == self.cityModelArr.count - 1) {
|
||||
self.cityIndex = 0;
|
||||
self.selectCityModel = self.cityModelArr.count > 0 ? self.cityModelArr[0] : nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
if (self.pickerMode == BRAddressPickerModeArea) {
|
||||
self.areaModelArr = [self getAreaModelArray:self.provinceIndex cityIndex:self.cityIndex];
|
||||
if (self.selectIndexs.count > 0) {
|
||||
NSInteger areaIndex = self.selectIndexs.count > 2 ? [self.selectIndexs[2] integerValue] : 0;
|
||||
self.areaIndex = (areaIndex > 0 && areaIndex < self.areaModelArr.count) ? areaIndex : 0;
|
||||
self.selectAreaModel = self.areaModelArr.count > self.areaIndex ? self.areaModelArr[self.areaIndex] : nil;
|
||||
} else {
|
||||
@weakify(self)
|
||||
[self.areaModelArr enumerateObjectsUsingBlock:^(BRAreaModel * _Nonnull model, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
@strongify(self)
|
||||
if (selectAreaName && [model.name isEqualToString:selectAreaName]) {
|
||||
self.areaIndex = idx;
|
||||
self.selectAreaModel = model;
|
||||
*stop = YES;
|
||||
}
|
||||
if (idx == self.areaModelArr.count - 1) {
|
||||
self.areaIndex = 0;
|
||||
self.selectAreaModel = self.areaModelArr.count > 0 ? self.areaModelArr[0] : nil;
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// 注意必须先刷新UI,再设置默认滚动
|
||||
[self.pickerView reloadAllComponents];
|
||||
|
||||
// 滚动到指定行
|
||||
if (self.pickerMode == BRAddressPickerModeProvince) {
|
||||
[self.pickerView selectRow:self.provinceIndex inComponent:0 animated:YES];
|
||||
} else if (self.pickerMode == BRAddressPickerModeCity) {
|
||||
[self.pickerView selectRow:self.provinceIndex inComponent:0 animated:YES];
|
||||
[self.pickerView selectRow:self.cityIndex inComponent:1 animated:YES];
|
||||
} else if (self.pickerMode == BRAddressPickerModeArea) {
|
||||
[self.pickerView selectRow:self.provinceIndex inComponent:0 animated:YES];
|
||||
[self.pickerView selectRow:self.cityIndex inComponent:1 animated:YES];
|
||||
[self.pickerView selectRow:self.areaIndex inComponent:2 animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 省索引 获取 城市模型数组
|
||||
- (NSArray *)getCityModelArray:(NSInteger)provinceIndex {
|
||||
BRProvinceModel *provinceModel = self.provinceModelArr[provinceIndex];
|
||||
// 返回城市模型数组
|
||||
return provinceModel.citylist;
|
||||
}
|
||||
|
||||
// 根据 省索引和城市索引 获取 区域模型数组
|
||||
- (NSArray *)getAreaModelArray:(NSInteger)provinceIndex cityIndex:(NSInteger)cityIndex {
|
||||
BRProvinceModel *provinceModel = self.provinceModelArr[provinceIndex];
|
||||
if (provinceModel.citylist && provinceModel.citylist.count > 0) {
|
||||
BRCityModel *cityModel = provinceModel.citylist[cityIndex];
|
||||
// 返回地区模型数组
|
||||
return cityModel.arealist;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 地址选择器
|
||||
- (UIPickerView *)pickerView {
|
||||
if (!_pickerView) {
|
||||
_pickerView = [[UIPickerView alloc]initWithFrame:CGRectMake(0, self.pickerStyle.titleBarHeight, SCREEN_WIDTH, self.pickerStyle.pickerHeight)];
|
||||
_pickerView.backgroundColor = self.pickerStyle.pickerColor;
|
||||
_pickerView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
|
||||
_pickerView.dataSource = self;
|
||||
_pickerView.delegate = self;
|
||||
_pickerView.showsSelectionIndicator = YES;
|
||||
}
|
||||
return _pickerView;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UIPickerViewDataSource
|
||||
// 1.指定pickerview有几个表盘(几列)
|
||||
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
|
||||
switch (self.pickerMode) {
|
||||
case BRAddressPickerModeProvince:
|
||||
return 1;
|
||||
break;
|
||||
case BRAddressPickerModeCity:
|
||||
return 2;
|
||||
break;
|
||||
case BRAddressPickerModeArea:
|
||||
return 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2.指定每个表盘上有几行数据
|
||||
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
|
||||
if (component == 0) {
|
||||
// 返回省个数
|
||||
return self.provinceModelArr.count;
|
||||
}
|
||||
if (component == 1) {
|
||||
// 返回市个数
|
||||
return self.cityModelArr.count;
|
||||
}
|
||||
if (component == 2) {
|
||||
// 返回区个数
|
||||
return self.areaModelArr.count;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - UIPickerViewDelegate
|
||||
// 3.设置 pickerView 的 显示内容
|
||||
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(nullable UIView *)view {
|
||||
|
||||
// 设置分割线的颜色
|
||||
for (UIView *subView in pickerView.subviews) {
|
||||
if (subView && [subView isKindOfClass:[UIView class]] && subView.frame.size.height <= 1) {
|
||||
subView.backgroundColor = self.pickerStyle.separatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置选中行的背景颜色
|
||||
NSArray *subviews = pickerView.subviews;
|
||||
if (subviews.count > 0) {
|
||||
NSArray *coloms = subviews.firstObject;
|
||||
if (coloms) {
|
||||
NSArray *subviewCache = [coloms valueForKey:@"subviewCache"];
|
||||
if (subviewCache.count > 0) {
|
||||
UIView *middleContainerView = [subviewCache.firstObject valueForKey:@"middleContainerView"];
|
||||
if (middleContainerView) {
|
||||
middleContainerView.backgroundColor = self.pickerStyle.selectedColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UILabel *label = (UILabel *)view;
|
||||
if (!label) {
|
||||
label = [[UILabel alloc]init];
|
||||
label.backgroundColor = [UIColor clearColor];
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.font = self.pickerStyle.pickerTextFont;
|
||||
label.textColor = self.pickerStyle.pickerTextColor;
|
||||
// 字体自适应属性
|
||||
label.adjustsFontSizeToFitWidth = YES;
|
||||
// 自适应最小字体缩放比例
|
||||
label.minimumScaleFactor = 0.5f;
|
||||
}
|
||||
if (component == 0) {
|
||||
BRProvinceModel *model = self.provinceModelArr[row];
|
||||
label.text = model.name;
|
||||
} else if (component == 1) {
|
||||
BRCityModel *model = self.cityModelArr[row];
|
||||
label.text = model.name;
|
||||
} else if (component == 2) {
|
||||
BRAreaModel *model = self.areaModelArr[row];
|
||||
label.text = model.name;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
// 4.选中时回调的委托方法,在此方法中实现省份和城市间的联动
|
||||
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
|
||||
if (component == 0) { // 选择省
|
||||
// 保存选择的省份的索引
|
||||
self.provinceIndex = row;
|
||||
switch (self.pickerMode) {
|
||||
case BRAddressPickerModeProvince:
|
||||
{
|
||||
self.selectProvinceModel = self.provinceModelArr.count > self.provinceIndex ? self.provinceModelArr[self.provinceIndex] : nil;
|
||||
self.selectCityModel = nil;
|
||||
self.selectAreaModel = nil;
|
||||
}
|
||||
break;
|
||||
case BRAddressPickerModeCity:
|
||||
{
|
||||
self.cityModelArr = [self getCityModelArray:self.provinceIndex];
|
||||
[self.pickerView reloadComponent:1];
|
||||
[self.pickerView selectRow:0 inComponent:1 animated:YES];
|
||||
self.selectProvinceModel = self.provinceModelArr.count > self.provinceIndex ? self.provinceModelArr[self.provinceIndex] : nil;
|
||||
self.selectCityModel = self.cityModelArr.count > 0 ? self.cityModelArr[0] : nil;
|
||||
self.selectAreaModel = nil;
|
||||
}
|
||||
break;
|
||||
case BRAddressPickerModeArea:
|
||||
{
|
||||
self.cityModelArr = [self getCityModelArray:self.provinceIndex];
|
||||
self.areaModelArr = [self getAreaModelArray:self.provinceIndex cityIndex:0];
|
||||
[self.pickerView reloadComponent:1];
|
||||
[self.pickerView selectRow:0 inComponent:1 animated:YES];
|
||||
[self.pickerView reloadComponent:2];
|
||||
[self.pickerView selectRow:0 inComponent:2 animated:YES];
|
||||
self.selectProvinceModel = self.provinceModelArr.count > self.provinceIndex ? self.provinceModelArr[self.provinceIndex] : nil;
|
||||
self.selectCityModel = self.cityModelArr.count > 0 ? self.cityModelArr[0] : nil;
|
||||
self.selectAreaModel = self.areaModelArr.count > 0 ? self.areaModelArr[0] : nil;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (component == 1) { // 选择市
|
||||
// 保存选择的城市的索引
|
||||
self.cityIndex = row;
|
||||
switch (self.pickerMode) {
|
||||
case BRAddressPickerModeCity:
|
||||
{
|
||||
self.selectCityModel = self.cityModelArr.count > self.cityIndex ? self.cityModelArr[self.cityIndex] : nil;
|
||||
self.selectAreaModel = nil;
|
||||
}
|
||||
break;
|
||||
case BRAddressPickerModeArea:
|
||||
{
|
||||
self.areaModelArr = [self getAreaModelArray:self.provinceIndex cityIndex:self.cityIndex];
|
||||
[self.pickerView reloadComponent:2];
|
||||
[self.pickerView selectRow:0 inComponent:2 animated:YES];
|
||||
self.selectCityModel = self.cityModelArr.count > self.cityIndex ? self.cityModelArr[self.cityIndex] : nil;
|
||||
self.selectAreaModel = self.areaModelArr.count > 0 ? self.areaModelArr[0] : nil;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (component == 2) { // 选择区
|
||||
// 保存选择的地区的索引
|
||||
self.areaIndex = row;
|
||||
if (self.pickerMode == BRAddressPickerModeArea) {
|
||||
self.selectAreaModel = self.areaModelArr.count > self.areaIndex ? self.areaModelArr[self.areaIndex] : nil;
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动选择时执行 changeBlock
|
||||
if (self.changeBlock) {
|
||||
self.changeBlock(self.selectProvinceModel, self.selectCityModel, self.selectAreaModel);
|
||||
}
|
||||
|
||||
// 设置自动选择时,滚动选择时就执行 resultBlock
|
||||
if (self.isAutoSelect) {
|
||||
if (self.resultBlock) {
|
||||
self.resultBlock(self.selectProvinceModel, self.selectCityModel, self.selectAreaModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置行高
|
||||
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
|
||||
return self.pickerStyle.rowHeight;
|
||||
}
|
||||
|
||||
#pragma mark - 重写父类方法
|
||||
- (void)addPickerToView:(UIView *)view {
|
||||
// 添加地址选择器
|
||||
if (view) {
|
||||
// 立即刷新容器视图 view 的布局(防止 view 使用自动布局时,选择器视图无法正常显示)
|
||||
[view setNeedsLayout];
|
||||
[view layoutIfNeeded];
|
||||
|
||||
self.frame = view.bounds;
|
||||
self.pickerView.frame = view.bounds;
|
||||
[self addSubview:self.pickerView];
|
||||
} else {
|
||||
[self.alertView addSubview:self.pickerView];
|
||||
}
|
||||
|
||||
[self handlerPickerData];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.doneBlock = ^{
|
||||
// 点击确定按钮后,执行block回调
|
||||
[weakSelf removePickerFromView:view];
|
||||
|
||||
if (weakSelf.resultBlock) {
|
||||
weakSelf.resultBlock(weakSelf.selectProvinceModel, weakSelf.selectCityModel, weakSelf.selectAreaModel);
|
||||
}
|
||||
};
|
||||
|
||||
[super addPickerToView:view];
|
||||
}
|
||||
|
||||
#pragma mark - 重写父类方法
|
||||
- (void)addSubViewToPicker:(UIView *)customView {
|
||||
[self.pickerView addSubview:customView];
|
||||
}
|
||||
|
||||
#pragma mark - 弹出选择器视图
|
||||
- (void)show {
|
||||
[self addPickerToView:nil];
|
||||
}
|
||||
|
||||
#pragma mark - 关闭选择器视图
|
||||
- (void)dismiss {
|
||||
[self removePickerFromView:nil];
|
||||
}
|
||||
|
||||
#pragma mark - setter方法
|
||||
- (void)setPickerMode:(BRAddressPickerMode)pickerMode {
|
||||
_pickerMode = pickerMode;
|
||||
if (_pickerView) {
|
||||
[self handlerDefaultSelectValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSelectValues:(NSArray<NSString *> *)selectValues {
|
||||
self.mSelectValues = selectValues;
|
||||
}
|
||||
|
||||
#pragma mark - getter方法
|
||||
- (NSArray *)provinceModelArr {
|
||||
if (!_provinceModelArr) {
|
||||
_provinceModelArr = [NSArray array];
|
||||
}
|
||||
return _provinceModelArr;
|
||||
}
|
||||
|
||||
- (NSArray *)cityModelArr {
|
||||
if (!_cityModelArr) {
|
||||
_cityModelArr = [NSArray array];
|
||||
}
|
||||
return _cityModelArr;
|
||||
}
|
||||
|
||||
- (NSArray *)areaModelArr {
|
||||
if (!_areaModelArr) {
|
||||
_areaModelArr = [NSArray array];
|
||||
}
|
||||
return _areaModelArr;
|
||||
}
|
||||
|
||||
- (BRProvinceModel *)selectProvinceModel {
|
||||
if (!_selectProvinceModel) {
|
||||
_selectProvinceModel = [[BRProvinceModel alloc]init];
|
||||
}
|
||||
return _selectProvinceModel;
|
||||
}
|
||||
|
||||
- (BRCityModel *)selectCityModel {
|
||||
if (!_selectCityModel) {
|
||||
_selectCityModel = [[BRCityModel alloc]init];
|
||||
_selectCityModel.code = @"";
|
||||
_selectCityModel.name = @"";
|
||||
}
|
||||
return _selectCityModel;
|
||||
}
|
||||
|
||||
- (BRAreaModel *)selectAreaModel {
|
||||
if (!_selectAreaModel) {
|
||||
_selectAreaModel = [[BRAreaModel alloc]init];
|
||||
_selectAreaModel.code = @"";
|
||||
_selectAreaModel.name = @"";
|
||||
}
|
||||
return _selectAreaModel;
|
||||
}
|
||||
|
||||
- (NSArray *)dataSourceArr {
|
||||
if (!_dataSourceArr) {
|
||||
_dataSourceArr = [NSArray array];
|
||||
}
|
||||
return _dataSourceArr;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)mSelectValues {
|
||||
if (!_mSelectValues) {
|
||||
_mSelectValues = [NSArray array];
|
||||
}
|
||||
return _mSelectValues;
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)selectIndexs {
|
||||
if (!_selectIndexs) {
|
||||
_selectIndexs = [NSArray array];
|
||||
}
|
||||
return _selectIndexs;
|
||||
}
|
||||
|
||||
@end
|
||||
16
SweetParty/第三方库/BRPickerView/BRPickerView.h
Executable file
16
SweetParty/第三方库/BRPickerView/BRPickerView.h
Executable file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// BRPickerView.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef BRPickerView_h
|
||||
#define BRPickerView_h
|
||||
|
||||
#import "BRDatePickerView.h"
|
||||
#import "BRAddressPickerView.h"
|
||||
#import "BRStringPickerView.h"
|
||||
|
||||
#endif /* BRPickerView_h */
|
||||
56
SweetParty/第三方库/BRPickerView/Base/BRBaseView.h
Executable file
56
SweetParty/第三方库/BRPickerView/Base/BRBaseView.h
Executable file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// BaseView.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "BRPickerStyle.h"
|
||||
|
||||
typedef void(^BRCancelBlock)(void);
|
||||
typedef void(^BRResultBlock)(void);
|
||||
|
||||
@interface BRBaseView : UIView
|
||||
|
||||
/** 选择器标题 */
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
|
||||
/** 是否自动选择,即滚动选择器后就执行结果回调,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL isAutoSelect;
|
||||
|
||||
/** 自定义UI样式(不传或为nil时,是默认样式) */
|
||||
@property (nonatomic, strong) BRPickerStyle *pickerStyle;
|
||||
|
||||
/** 取消选择的回调 */
|
||||
@property (nonatomic, copy) BRCancelBlock cancelBlock;
|
||||
|
||||
/** 选择结果的回调(框架内部使用) */
|
||||
@property (nonatomic, copy) BRResultBlock doneBlock;
|
||||
|
||||
/** 弹框视图 */
|
||||
@property (nonatomic, strong) UIView *alertView;
|
||||
|
||||
|
||||
/// 扩展一:添加选择器到指定容器视图上
|
||||
/// 应用场景:可将选择器(pickerView,不包含标题栏)添加到任何自定义视图上,也支持自定义更多的弹框样式
|
||||
/// @param view 容器视图
|
||||
- (void)addPickerToView:(UIView *)view;
|
||||
|
||||
/// 从指定容器视图上移除选择器
|
||||
/// @param view 容器视图
|
||||
- (void)removePickerFromView:(UIView *)view;
|
||||
|
||||
/// 扩展二:添加自定义视图到选择器(pickerView)上
|
||||
/// 应用场景:可以添加一些固定的标题、数值的单位等到选择器中间
|
||||
/// @param customView 自定义视图
|
||||
- (void)addSubViewToPicker:(UIView *)customView;
|
||||
|
||||
/// 扩展三:添加自定义视图到标题栏(titleBarView)上
|
||||
/// 应用场景:先自定义标题栏高度,再添加一些固定的标题等到标题栏底部
|
||||
/// @param customView 自定义视图
|
||||
- (void)addSubViewToTitleBar:(UIView *)customView;
|
||||
|
||||
@end
|
||||
340
SweetParty/第三方库/BRPickerView/Base/BRBaseView.m
Executable file
340
SweetParty/第三方库/BRPickerView/Base/BRBaseView.m
Executable file
@@ -0,0 +1,340 @@
|
||||
//
|
||||
// BaseView.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRBaseView.h"
|
||||
|
||||
@interface BRBaseView ()
|
||||
// 蒙层视图
|
||||
@property (nonatomic, strong) UIView *displayMaskView;
|
||||
// 标题栏背景视图
|
||||
@property (nonatomic, strong) UIView *titleBarView;
|
||||
// 左边取消按钮
|
||||
@property (nonatomic, strong) UIButton *cancelBtn;
|
||||
// 右边确定按钮
|
||||
@property (nonatomic, strong) UIButton *doneBtn;
|
||||
// 中间标题
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
// 取消按钮离屏幕边缘的距离
|
||||
@property (nonatomic, assign) CGFloat cancelBtnMargin;
|
||||
// 确定按钮离屏幕边缘的距离
|
||||
@property (nonatomic, assign) CGFloat doneBtnMargin;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BRBaseView
|
||||
|
||||
- (void)initUI {
|
||||
self.frame = SCREEN_BOUNDS;
|
||||
// 设置子视图的宽度随着父视图变化
|
||||
self.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
if (!self.pickerStyle.hiddenMaskView) {
|
||||
[self addSubview:self.displayMaskView];
|
||||
}
|
||||
|
||||
[self addSubview:self.alertView];
|
||||
|
||||
// 是否隐藏标题栏
|
||||
if (!self.pickerStyle.hiddenTitleBarView) {
|
||||
[self.alertView addSubview:self.titleBarView];
|
||||
[self.alertView sendSubviewToBack:self.titleBarView];
|
||||
|
||||
if (!self.pickerStyle.hiddenTitleLabel) {
|
||||
[self.titleBarView addSubview:self.titleLabel];
|
||||
}
|
||||
if (!self.pickerStyle.hiddenCancelBtn) {
|
||||
[self.titleBarView addSubview:self.cancelBtn];
|
||||
// 获取边距
|
||||
if (self.pickerStyle.cancelBtnFrame.origin.x < self.bounds.size.width / 2) {
|
||||
self.cancelBtnMargin = self.pickerStyle.cancelBtnFrame.origin.x;
|
||||
} else {
|
||||
self.cancelBtnMargin = self.bounds.size.width - self.pickerStyle.cancelBtnFrame.origin.x - self.pickerStyle.cancelBtnFrame.size.width;
|
||||
}
|
||||
}
|
||||
if (!self.pickerStyle.hiddenDoneBtn) {
|
||||
[self.titleBarView addSubview:self.doneBtn];
|
||||
// 获取边距
|
||||
if (self.pickerStyle.doneBtnFrame.origin.x < self.bounds.size.width / 2) {
|
||||
self.doneBtnMargin = self.pickerStyle.doneBtnFrame.origin.x;
|
||||
} else {
|
||||
self.doneBtnMargin = self.bounds.size.width - self.pickerStyle.doneBtnFrame.origin.x - self.pickerStyle.doneBtnFrame.size.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 适配横屏安全区域,更新子视图布局
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
if (_cancelBtn || _doneBtn) {
|
||||
if (@available(iOS 11.0, *)) {
|
||||
UIEdgeInsets safeInsets = self.safeAreaInsets;
|
||||
if (_cancelBtn) {
|
||||
CGRect cancelBtnFrame = self.pickerStyle.cancelBtnFrame;
|
||||
if (cancelBtnFrame.origin.x < MIN(self.bounds.size.width / 2, self.bounds.size.height / 2)) {
|
||||
cancelBtnFrame.origin.x += safeInsets.left;
|
||||
} else {
|
||||
cancelBtnFrame.origin.x = self.bounds.size.width - cancelBtnFrame.size.width - safeInsets.right - self.cancelBtnMargin;
|
||||
}
|
||||
self.cancelBtn.frame = cancelBtnFrame;
|
||||
}
|
||||
if (_doneBtn) {
|
||||
CGRect doneBtnFrame = self.pickerStyle.doneBtnFrame;
|
||||
if (doneBtnFrame.origin.x < MIN(self.bounds.size.width / 2, self.bounds.size.height / 2)) {
|
||||
doneBtnFrame.origin.x += safeInsets.left;
|
||||
} else {
|
||||
doneBtnFrame.origin.x = self.bounds.size.width - doneBtnFrame.size.width - safeInsets.right - self.doneBtnMargin;
|
||||
}
|
||||
self.doneBtn.frame = doneBtnFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_alertView && self.pickerStyle.topCornerRadius > 0) {
|
||||
// 设置顶部圆角
|
||||
[self br_setView:_alertView roundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight withRadius:self.pickerStyle.topCornerRadius];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 蒙层视图
|
||||
- (UIView *)displayMaskView {
|
||||
if (!_displayMaskView) {
|
||||
_displayMaskView = [[UIView alloc]initWithFrame:SCREEN_BOUNDS];
|
||||
_displayMaskView.backgroundColor = self.pickerStyle.maskColor;
|
||||
// 设置子视图的大小随着父视图变化
|
||||
_displayMaskView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_displayMaskView.userInteractionEnabled = YES;
|
||||
UITapGestureRecognizer *myTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(didTapMaskView:)];
|
||||
[_displayMaskView addGestureRecognizer:myTap];
|
||||
}
|
||||
return _displayMaskView;
|
||||
}
|
||||
|
||||
#pragma mark - 弹框视图
|
||||
- (UIView *)alertView {
|
||||
if (!_alertView) {
|
||||
_alertView = [[UIView alloc]initWithFrame:CGRectMake(0, SCREEN_HEIGHT - self.pickerStyle.titleBarHeight - self.pickerStyle.pickerHeight - BR_BOTTOM_MARGIN, SCREEN_WIDTH, self.pickerStyle.titleBarHeight + self.pickerStyle.pickerHeight + BR_BOTTOM_MARGIN)];
|
||||
_alertView.backgroundColor = self.pickerStyle.alertViewColor;
|
||||
if (!self.pickerStyle.topCornerRadius && !self.pickerStyle.hiddenShadowLine) {
|
||||
// 设置弹框视图顶部边框线
|
||||
UIView *shadowLineView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, _alertView.frame.size.width, 1.0f)];
|
||||
shadowLineView.backgroundColor = self.pickerStyle.shadowLineColor;
|
||||
shadowLineView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
[_alertView addSubview:shadowLineView];
|
||||
}
|
||||
_alertView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
|
||||
}
|
||||
return _alertView;
|
||||
}
|
||||
|
||||
#pragma mark - 标题栏视图
|
||||
- (UIView *)titleBarView {
|
||||
if (!_titleBarView) {
|
||||
_titleBarView =[[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, self.pickerStyle.titleBarHeight)];
|
||||
_titleBarView.backgroundColor = self.pickerStyle.titleBarColor;
|
||||
_titleBarView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
if (!self.pickerStyle.hiddenTitleLine) {
|
||||
// 设置标题栏底部分割线
|
||||
UIView *titleLineView = [[UIView alloc]initWithFrame:CGRectMake(0, _titleBarView.frame.size.height - 0.5f, _titleBarView.frame.size.width, 0.5f)];
|
||||
titleLineView.backgroundColor = self.pickerStyle.titleLineColor;
|
||||
titleLineView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
[_titleBarView addSubview:titleLineView];
|
||||
}
|
||||
}
|
||||
return _titleBarView;
|
||||
}
|
||||
|
||||
#pragma mark - 取消按钮
|
||||
- (UIButton *)cancelBtn {
|
||||
if (!_cancelBtn) {
|
||||
_cancelBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_cancelBtn.frame = self.pickerStyle.cancelBtnFrame;
|
||||
_cancelBtn.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin;
|
||||
_cancelBtn.backgroundColor = self.pickerStyle.cancelColor;;
|
||||
_cancelBtn.titleLabel.font = self.pickerStyle.cancelTextFont;
|
||||
[_cancelBtn setTitleColor:self.pickerStyle.cancelTextColor forState:UIControlStateNormal];
|
||||
if (self.pickerStyle.cancelBtnImage) {
|
||||
[_cancelBtn setImage:self.pickerStyle.cancelBtnImage forState:UIControlStateNormal];
|
||||
}
|
||||
if (self.pickerStyle.cancelBtnTitle) {
|
||||
[_cancelBtn setTitle:self.pickerStyle.cancelBtnTitle forState:UIControlStateNormal];
|
||||
}
|
||||
[_cancelBtn addTarget:self action:@selector(clickCancelBtn) forControlEvents:UIControlEventTouchUpInside];
|
||||
// 设置按钮圆角或边框
|
||||
if (self.pickerStyle.cancelBorderStyle == BRBorderStyleSolid) {
|
||||
_cancelBtn.layer.cornerRadius = 6.0f;
|
||||
_cancelBtn.layer.borderColor = self.pickerStyle.cancelTextColor.CGColor;
|
||||
_cancelBtn.layer.borderWidth = 1.0f;
|
||||
_cancelBtn.layer.masksToBounds = YES;
|
||||
} else if (self.pickerStyle.cancelBorderStyle == BRBorderStyleFill) {
|
||||
_cancelBtn.layer.cornerRadius = 6.0f;
|
||||
_cancelBtn.layer.masksToBounds = YES;
|
||||
}
|
||||
}
|
||||
return _cancelBtn;
|
||||
}
|
||||
|
||||
#pragma mark - 确定按钮
|
||||
- (UIButton *)doneBtn {
|
||||
if (!_doneBtn) {
|
||||
_doneBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_doneBtn.frame = self.pickerStyle.doneBtnFrame;
|
||||
_doneBtn.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin;
|
||||
_doneBtn.backgroundColor = self.pickerStyle.doneColor;
|
||||
if (self.pickerStyle.doneBtnImage) {
|
||||
[_doneBtn setImage:self.pickerStyle.doneBtnImage forState:UIControlStateNormal];
|
||||
}
|
||||
if (self.pickerStyle.doneBtnTitle) {
|
||||
_doneBtn.titleLabel.font = self.pickerStyle.doneTextFont;
|
||||
[_doneBtn setTitleColor:self.pickerStyle.doneTextColor forState:UIControlStateNormal];
|
||||
[_doneBtn setTitle:self.pickerStyle.doneBtnTitle forState:UIControlStateNormal];
|
||||
}
|
||||
[_doneBtn addTarget:self action:@selector(clickDoneBtn) forControlEvents:UIControlEventTouchUpInside];
|
||||
// 设置按钮圆角或边框
|
||||
if (self.pickerStyle.doneBorderStyle == BRBorderStyleSolid) {
|
||||
_doneBtn.layer.cornerRadius = 6.0f;
|
||||
_doneBtn.layer.borderColor = self.pickerStyle.doneTextColor.CGColor;
|
||||
_doneBtn.layer.borderWidth = 1.0f;
|
||||
_doneBtn.layer.masksToBounds = YES;
|
||||
} else if (self.pickerStyle.doneBorderStyle == BRBorderStyleFill) {
|
||||
_doneBtn.layer.cornerRadius = 6.0f;
|
||||
_doneBtn.layer.masksToBounds = YES;
|
||||
}
|
||||
}
|
||||
return _doneBtn;
|
||||
}
|
||||
|
||||
#pragma mark - 中间标题label
|
||||
- (UILabel *)titleLabel {
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [[UILabel alloc]initWithFrame:self.pickerStyle.titleLabelFrame];
|
||||
_titleLabel.backgroundColor = self.pickerStyle.titleLabelColor;
|
||||
_titleLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin;
|
||||
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_titleLabel.font = self.pickerStyle.titleTextFont;
|
||||
_titleLabel.textColor = self.pickerStyle.titleTextColor;
|
||||
_titleLabel.text = self.title;
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
#pragma mark - 点击蒙层视图事件
|
||||
- (void)didTapMaskView:(UITapGestureRecognizer *)sender {
|
||||
[self removePickerFromView:nil];
|
||||
if (self.cancelBlock) {
|
||||
self.cancelBlock();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 取消按钮的点击事件
|
||||
- (void)clickCancelBtn {
|
||||
[self removePickerFromView:nil];
|
||||
if (self.cancelBlock) {
|
||||
self.cancelBlock();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 确定按钮的点击事件
|
||||
- (void)clickDoneBtn {
|
||||
if (self.doneBlock) {
|
||||
self.doneBlock();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 添加视图方法
|
||||
- (void)addPickerToView:(UIView *)view {
|
||||
if (view) {
|
||||
self.frame = view.bounds;
|
||||
self.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[view addSubview:self];
|
||||
} else {
|
||||
[self initUI];
|
||||
|
||||
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
|
||||
[keyWindow addSubview:self];
|
||||
// 动画前初始位置
|
||||
CGRect rect = self.alertView.frame;
|
||||
rect.origin.y = SCREEN_HEIGHT;
|
||||
self.alertView.frame = rect;
|
||||
// 弹出动画
|
||||
if (!self.pickerStyle.hiddenMaskView) {
|
||||
self.displayMaskView.alpha = 0;
|
||||
}
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
if (!self.pickerStyle.hiddenMaskView) {
|
||||
self.displayMaskView.alpha = 1;
|
||||
}
|
||||
CGRect rect = self.alertView.frame;
|
||||
rect.origin.y -= self.pickerStyle.pickerHeight + self.pickerStyle.titleBarHeight + BR_BOTTOM_MARGIN;
|
||||
self.alertView.frame = rect;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 移除视图方法
|
||||
- (void)removePickerFromView:(UIView *)view {
|
||||
if (view) {
|
||||
[self removeFromSuperview];
|
||||
} else {
|
||||
// 关闭动画
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
CGRect rect = self.alertView.frame;
|
||||
rect.origin.y += self.pickerStyle.pickerHeight + self.pickerStyle.titleBarHeight + BR_BOTTOM_MARGIN;
|
||||
self.alertView.frame = rect;
|
||||
if (!self.pickerStyle.hiddenMaskView) {
|
||||
self.displayMaskView.alpha = 0;
|
||||
}
|
||||
} completion:^(BOOL finished) {
|
||||
[self removeFromSuperview];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 添加自定义视图到选择器(picker)上
|
||||
- (void)addSubViewToPicker:(UIView *)customView {
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - 添加自定义视图到标题栏(titleBar)上
|
||||
- (void)addSubViewToTitleBar:(UIView *)customView {
|
||||
if (!self.pickerStyle.hiddenTitleBarView) {
|
||||
[self.titleBarView addSubview:customView];
|
||||
}
|
||||
}
|
||||
|
||||
- (BRPickerStyle *)pickerStyle {
|
||||
if (!_pickerStyle) {
|
||||
_pickerStyle = [[BRPickerStyle alloc]init];
|
||||
}
|
||||
return _pickerStyle;
|
||||
}
|
||||
|
||||
#pragma mark - 设置 view 的部分圆角
|
||||
// corners(枚举类型,可组合使用):UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft | UIRectCornerBottomRight | UIRectCornerAllCorners
|
||||
- (void)br_setView:(UIView *)view roundingCorners:(UIRectCorner)corners withRadius:(CGFloat)radius {
|
||||
UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];
|
||||
CAShapeLayer *shape = [[CAShapeLayer alloc]init];
|
||||
[shape setPath:rounded.CGPath];
|
||||
view.layer.mask = shape;
|
||||
}
|
||||
|
||||
#pragma mark - setter 方法(支持动态设置标题)
|
||||
- (void)setTitle:(NSString *)title {
|
||||
_title = title;
|
||||
if (_titleLabel) {
|
||||
_titleLabel.text = title;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"%@ dealloc", NSStringFromClass([self class]));
|
||||
}
|
||||
|
||||
@end
|
||||
209
SweetParty/第三方库/BRPickerView/Base/BRPickerStyle.h
Executable file
209
SweetParty/第三方库/BRPickerView/Base/BRPickerStyle.h
Executable file
@@ -0,0 +1,209 @@
|
||||
//
|
||||
// BRPickerStyle.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2019/10/2.
|
||||
// Copyright © 2019年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "BRPickerViewMacro.h"
|
||||
|
||||
// 边框样式(左边取消按钮/右边确定按钮)
|
||||
typedef NS_ENUM(NSUInteger, BRBorderStyle) {
|
||||
/** 无边框(默认) */
|
||||
BRBorderStyleNone = 0,
|
||||
/** 有圆角和边框 */
|
||||
BRBorderStyleSolid,
|
||||
/** 仅有圆角 */
|
||||
BRBorderStyleFill
|
||||
};
|
||||
|
||||
@interface BRPickerStyle : NSObject
|
||||
|
||||
/////////////////////////////// 选中行颜色,不是框架的,是自己定义的 ///////////////////////////////
|
||||
|
||||
/** 设置选中行颜色(selectedColor)*/
|
||||
@property (nonatomic, strong) UIColor *selectedColor;
|
||||
|
||||
/////////////////////////////// 蒙层视图(maskView)///////////////////////////////
|
||||
|
||||
/** 设置背景颜色(backgroundColor)*/
|
||||
@property (nonatomic, strong) UIColor *maskColor;
|
||||
|
||||
/** 隐藏 maskView,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL hiddenMaskView;
|
||||
|
||||
|
||||
////////////////////////////// 弹框视图(alertView)///////////////////////////////
|
||||
|
||||
/** 设置 alertView 弹框视图的背景颜色(backgroundColor)*/
|
||||
@property (nonatomic, strong) UIColor *alertViewColor;
|
||||
|
||||
/** 设置 alertView 弹框视图左上和右上的圆角半径 */
|
||||
@property (nonatomic, assign) NSInteger topCornerRadius;
|
||||
|
||||
/** 设置 alertView 弹框视图顶部边框线颜色 */
|
||||
@property (nonatomic, strong) UIColor *shadowLineColor;
|
||||
|
||||
/** 隐藏 alertView 弹框视图顶部边框线,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL hiddenShadowLine;
|
||||
|
||||
|
||||
//////////////////////////// 标题栏视图(titleBarView) ////////////////////////////
|
||||
|
||||
/** 设置 titleBarView 标题栏的背景颜色(backgroundColor)*/
|
||||
@property (nonatomic, strong) UIColor *titleBarColor;
|
||||
|
||||
/** 设置 titleBarView 标题栏的高度(height)*/
|
||||
@property (nonatomic, assign) CGFloat titleBarHeight;
|
||||
|
||||
/** 设置 titleBarView 标题栏底部分割线颜色 */
|
||||
@property (nonatomic, strong) UIColor *titleLineColor;
|
||||
|
||||
/** 隐藏 titleBarView 标题栏底部分割线,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL hiddenTitleLine;
|
||||
|
||||
/** 隐藏 titleBarView,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL hiddenTitleBarView;
|
||||
|
||||
|
||||
////////////////////////// 标题栏中间label(titleLabel)///////////////////////////
|
||||
|
||||
/** 设置 titleLabel 的背景颜色(backgroundColor)*/
|
||||
@property (nonatomic, strong) UIColor *titleLabelColor;
|
||||
|
||||
/** 设置 titleLabel 文本颜色(textColor)*/
|
||||
@property (nonatomic, strong) UIColor *titleTextColor;
|
||||
|
||||
/** 设置 titleLabel 字体大小(font)*/
|
||||
@property (nonatomic, strong) UIFont *titleTextFont;
|
||||
|
||||
/** 设置 titleLabel 的 frame */
|
||||
@property (nonatomic, assign) CGRect titleLabelFrame;
|
||||
|
||||
/** 隐藏 titleLabel,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL hiddenTitleLabel;
|
||||
|
||||
|
||||
/////////////////////////////// 取消按钮(cancelBtn)//////////////////////////////
|
||||
|
||||
/** 设置 cancelBtn 的背景颜色(backgroundColor)*/
|
||||
@property (nonatomic, strong) UIColor *cancelColor;
|
||||
|
||||
/** 设置 cancelBtn 标题的颜色(titleColor)*/
|
||||
@property (nonatomic, strong) UIColor *cancelTextColor;
|
||||
|
||||
/** 设置 cancelBtn 标题的字体(font)*/
|
||||
@property (nonatomic, strong) UIFont *cancelTextFont;
|
||||
|
||||
/** 设置 cancelBtn 的边框样式(borderStyle)*/
|
||||
@property (nonatomic, assign) BRBorderStyle cancelBorderStyle;
|
||||
|
||||
/** 设置 cancelBtn 的 frame */
|
||||
@property (nonatomic, assign) CGRect cancelBtnFrame;
|
||||
|
||||
/** 设置 cancelBtn 的 image */
|
||||
@property (nonatomic, strong) UIImage *cancelBtnImage;
|
||||
|
||||
/** 设置 cancelBtn 的 title */
|
||||
@property (nonatomic, copy) NSString *cancelBtnTitle;
|
||||
|
||||
/** 隐藏 cancelBtn,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL hiddenCancelBtn;
|
||||
|
||||
|
||||
/////////////////////////////// 确定按钮(doneBtn)////////////////////////////////
|
||||
|
||||
/** 设置 doneBtn 的背景颜色(backgroundColor)*/
|
||||
@property (nonatomic, strong) UIColor *doneColor;
|
||||
|
||||
/** 设置 doneBtn 标题的颜色(titleColor)*/
|
||||
@property (nonatomic, strong) UIColor *doneTextColor;
|
||||
|
||||
/** 设置 doneBtn 标题的字体(font)*/
|
||||
@property (nonatomic, strong) UIFont *doneTextFont;
|
||||
|
||||
/** 设置 doneBtn 的边框样式(borderStyle)*/
|
||||
@property (nonatomic, assign) BRBorderStyle doneBorderStyle;
|
||||
|
||||
/** 设置 doneBtn 的 frame */
|
||||
@property (nonatomic, assign) CGRect doneBtnFrame;
|
||||
|
||||
/** 设置 doneBtn 的 image */
|
||||
@property (nonatomic, strong) UIImage *doneBtnImage;
|
||||
|
||||
/** 设置 doneBtn 的 title */
|
||||
@property (nonatomic, copy) NSString *doneBtnTitle;
|
||||
|
||||
/** 隐藏 doneBtn,默认为 NO */
|
||||
@property (nonatomic, assign) BOOL hiddenDoneBtn;
|
||||
|
||||
|
||||
/////////////////////////////// 选择器(pickerView)///////////////////////////////
|
||||
|
||||
/** 设置 picker 的背景颜色(backgroundColor)*/
|
||||
@property (nonatomic, strong) UIColor *pickerColor;
|
||||
|
||||
/** 设置 picker 中间两条分割线的背景颜色(separatorColor)*/
|
||||
@property (nonatomic, strong) UIColor *separatorColor;
|
||||
|
||||
/** 设置 picker 文本的颜色(textColor)*/
|
||||
@property (nonatomic, strong) UIColor *pickerTextColor;
|
||||
|
||||
/** 设置 picker 文本的字体(font)*/
|
||||
@property (nonatomic, strong) UIFont *pickerTextFont;
|
||||
|
||||
/** 设置 picker 的高度(height),系统默认高度为 216 */
|
||||
@property (nonatomic, assign) CGFloat pickerHeight;
|
||||
|
||||
/** 设置 picker 的行高(rowHeight)*/
|
||||
@property (nonatomic, assign) CGFloat rowHeight;
|
||||
|
||||
|
||||
/**
|
||||
* 设置语言(不设置或为nil时,将随系统的语言自动改变)
|
||||
* language: zh-Hans(简体中文)、zh-Hant(繁体中文)、en(英语 )
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *language;
|
||||
|
||||
|
||||
/////// 日期选择器单位样式(showUnitType == BRShowUnitTypeSingleRow 时,生效)////////
|
||||
|
||||
/** 设置日期选择器单位文本的颜色(textColor)*/
|
||||
@property (nonatomic, strong) UIColor *dateUnitTextColor;
|
||||
|
||||
/** 设置日期选择器单位文本的字体(font)*/
|
||||
@property (nonatomic, strong) UIFont *dateUnitTextFont;
|
||||
|
||||
/** 设置日期选择器单位 label 的水平方向偏移量(offsetX)*/
|
||||
@property (nonatomic, assign) CGFloat dateUnitOffsetX;
|
||||
|
||||
/** 设置日期选择器单位 label 的竖直方向偏移量(offsetY)*/
|
||||
@property (nonatomic, assign) CGFloat dateUnitOffsetY;
|
||||
|
||||
/** 设置日期选择器单位是否与 选择器的列 水平居中对齐 */
|
||||
@property (nonatomic, assign) BOOL horizontalCenter;
|
||||
|
||||
|
||||
//////////////////////////////// 常用的几种模板样式 ////////////////////////////////
|
||||
|
||||
/// 模板样式1 - 取消/确定按钮圆角样式
|
||||
/// @param themeColor 主题颜色
|
||||
+ (instancetype)pickerStyleWithThemeColor:(UIColor *)themeColor;
|
||||
|
||||
/// 模板样式2 - 顶部圆角样式 + 完成按钮
|
||||
/// @param doneTextColor 完成按钮标题的颜色
|
||||
+ (instancetype)pickerStyleWithDoneTextColor:(UIColor *)doneTextColor;
|
||||
|
||||
/// 模板样式3 - 顶部圆角样式 + 图标按钮
|
||||
/// @param doneBtnImage 完成按钮的 image
|
||||
+ (instancetype)pickerStyleWithDoneBtnImage:(UIImage *)doneBtnImage;
|
||||
|
||||
/// 模板样式4 - 日期选择器单位顶部显示(showUnitType == BRShowUnitTypeSingleRow 时,可设置)
|
||||
+ (instancetype)pickerStyleWithDateUnitOnTop;
|
||||
|
||||
|
||||
@end
|
||||
343
SweetParty/第三方库/BRPickerView/Base/BRPickerStyle.m
Executable file
343
SweetParty/第三方库/BRPickerView/Base/BRPickerStyle.m
Executable file
@@ -0,0 +1,343 @@
|
||||
//
|
||||
// BRPickerStyle.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2019/10/2.
|
||||
// Copydone © 2019年 91renb. All dones reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRPickerStyle.h"
|
||||
#import "NSBundle+BRPickerView.h"
|
||||
|
||||
// 标题颜色
|
||||
#ifndef kDefaultTextColor
|
||||
#define kDefaultTextColor BR_RGB_HEX(0x333333, 1.0f)
|
||||
#endif
|
||||
#ifndef kBorderColor
|
||||
// 边框颜色
|
||||
#define kBorderColor BR_RGB_HEX(0xe2e2e3, 1.0f)
|
||||
#endif
|
||||
|
||||
@implementation BRPickerStyle
|
||||
|
||||
/// 设置默认样式
|
||||
|
||||
- (UIColor *)maskColor {
|
||||
if (!_maskColor) {
|
||||
_maskColor = [UIColor colorWithWhite:0 alpha:0.2f];
|
||||
}
|
||||
return _maskColor;
|
||||
}
|
||||
|
||||
- (UIColor *)alertViewColor {
|
||||
if (!_alertViewColor) {
|
||||
_alertViewColor = self.pickerColor;
|
||||
}
|
||||
return _alertViewColor;
|
||||
}
|
||||
|
||||
- (UIColor *)shadowLineColor {
|
||||
if (!_shadowLineColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_shadowLineColor = [UIColor separatorColor];
|
||||
} else {
|
||||
_shadowLineColor = kBorderColor;
|
||||
}
|
||||
}
|
||||
return _shadowLineColor;
|
||||
}
|
||||
|
||||
- (UIColor *)titleBarColor {
|
||||
if (!_titleBarColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_titleBarColor = [self br_customDynamicColor:[UIColor whiteColor] darkColor:[UIColor secondarySystemBackgroundColor]];
|
||||
} else {
|
||||
_titleBarColor = [UIColor whiteColor];
|
||||
}
|
||||
}
|
||||
return _titleBarColor;
|
||||
}
|
||||
|
||||
- (CGFloat)titleBarHeight {
|
||||
if (!self.hiddenTitleBarView) {
|
||||
if (_titleBarHeight < 44.0f && (!self.hiddenCancelBtn || !self.hiddenDoneBtn || !self.hiddenTitleLabel)) {
|
||||
_titleBarHeight = 44.0f;
|
||||
}
|
||||
} else {
|
||||
_titleBarHeight = 0;
|
||||
}
|
||||
return _titleBarHeight;
|
||||
}
|
||||
|
||||
- (UIColor *)titleLineColor {
|
||||
if (!_titleLineColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_titleLineColor = [UIColor quaternaryLabelColor];
|
||||
} else {
|
||||
_titleLineColor = BR_RGB_HEX(0xededee, 1.0f);
|
||||
}
|
||||
}
|
||||
return _titleLineColor;
|
||||
}
|
||||
|
||||
- (UIColor *)cancelColor {
|
||||
if (!_cancelColor) {
|
||||
_cancelColor = [UIColor clearColor];
|
||||
}
|
||||
return _cancelColor;
|
||||
}
|
||||
|
||||
- (UIColor *)cancelTextColor {
|
||||
if (!_cancelTextColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_cancelTextColor = [UIColor labelColor];
|
||||
} else {
|
||||
_cancelTextColor = kDefaultTextColor;
|
||||
}
|
||||
}
|
||||
return _cancelTextColor;
|
||||
}
|
||||
|
||||
- (UIFont *)cancelTextFont {
|
||||
if (!_cancelTextFont) {
|
||||
_cancelTextFont = [UIFont systemFontOfSize:16.0f];
|
||||
}
|
||||
return _cancelTextFont;
|
||||
}
|
||||
|
||||
- (NSString *)cancelBtnTitle {
|
||||
if (!_cancelBtnTitle && !_cancelBtnImage) {
|
||||
_cancelBtnTitle = [NSBundle br_localizedStringForKey:@"取消" language:self.language];
|
||||
}
|
||||
return _cancelBtnTitle;
|
||||
}
|
||||
|
||||
- (CGRect)cancelBtnFrame {
|
||||
if (CGRectEqualToRect(_cancelBtnFrame, CGRectZero) || _cancelBtnFrame.size.height == 0) {
|
||||
_cancelBtnFrame = CGRectMake(5, 8, 60, 28);
|
||||
}
|
||||
return _cancelBtnFrame;
|
||||
}
|
||||
|
||||
- (UIColor *)titleLabelColor {
|
||||
if (!_titleLabelColor) {
|
||||
_titleLabelColor = [UIColor clearColor];
|
||||
}
|
||||
return _titleLabelColor;
|
||||
}
|
||||
|
||||
- (UIColor *)titleTextColor {
|
||||
if (!_titleTextColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_titleTextColor = [UIColor secondaryLabelColor];
|
||||
} else {
|
||||
_titleTextColor = BR_RGB_HEX(0x999999, 1.0f);
|
||||
}
|
||||
}
|
||||
return _titleTextColor;
|
||||
}
|
||||
|
||||
- (UIFont *)titleTextFont {
|
||||
if (!_titleTextFont) {
|
||||
_titleTextFont = [UIFont systemFontOfSize:15.0f];
|
||||
}
|
||||
return _titleTextFont;
|
||||
}
|
||||
|
||||
- (CGRect)titleLabelFrame {
|
||||
if (CGRectEqualToRect(_titleLabelFrame, CGRectZero) || _titleLabelFrame.size.height == 0) {
|
||||
_titleLabelFrame = CGRectMake(5 + 60 + 2, 0, SCREEN_WIDTH - 2 * (5 + 60 + 2), 44);
|
||||
}
|
||||
return _titleLabelFrame;
|
||||
}
|
||||
|
||||
- (UIColor *)doneColor {
|
||||
if (!_doneColor) {
|
||||
_doneColor = [UIColor clearColor];
|
||||
}
|
||||
return _doneColor;
|
||||
}
|
||||
|
||||
- (UIColor *)doneTextColor {
|
||||
if (!_doneTextColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_doneTextColor = [UIColor labelColor];
|
||||
} else {
|
||||
_doneTextColor = kDefaultTextColor;
|
||||
}
|
||||
}
|
||||
return _doneTextColor;
|
||||
}
|
||||
|
||||
- (UIFont *)doneTextFont {
|
||||
if (!_doneTextFont) {
|
||||
_doneTextFont = [UIFont systemFontOfSize:16.0f];
|
||||
}
|
||||
return _doneTextFont;
|
||||
}
|
||||
|
||||
- (NSString *)doneBtnTitle {
|
||||
if (!_doneBtnTitle && !_doneBtnImage) {
|
||||
_doneBtnTitle = [NSBundle br_localizedStringForKey:@"确定" language:self.language];
|
||||
}
|
||||
return _doneBtnTitle;
|
||||
}
|
||||
|
||||
- (CGRect)doneBtnFrame {
|
||||
if (CGRectEqualToRect(_doneBtnFrame, CGRectZero) || _doneBtnFrame.size.height == 0) {
|
||||
_doneBtnFrame = CGRectMake(SCREEN_WIDTH - 60 - 5, 8, 60, 28);
|
||||
}
|
||||
return _doneBtnFrame;
|
||||
}
|
||||
|
||||
- (UIColor *)pickerColor {
|
||||
if (!_pickerColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_pickerColor = [self br_customDynamicColor:[UIColor whiteColor] darkColor:[UIColor secondarySystemBackgroundColor]];
|
||||
} else {
|
||||
_pickerColor = [UIColor whiteColor];
|
||||
}
|
||||
}
|
||||
return _pickerColor;
|
||||
}
|
||||
|
||||
- (UIColor *)separatorColor {
|
||||
if (!_separatorColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_separatorColor = [UIColor separatorColor];
|
||||
} else {
|
||||
_separatorColor = [UIColor colorWithRed:195/255.0 green:195/255.0 blue:195/255.0 alpha:1.0];
|
||||
}
|
||||
}
|
||||
return _separatorColor;
|
||||
}
|
||||
|
||||
- (UIColor *)pickerTextColor {
|
||||
if (!_pickerTextColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_pickerTextColor = [UIColor labelColor];
|
||||
} else {
|
||||
_pickerTextColor = kDefaultTextColor;
|
||||
}
|
||||
}
|
||||
return _pickerTextColor;
|
||||
}
|
||||
|
||||
- (UIFont *)pickerTextFont {
|
||||
if (!_pickerTextFont) {
|
||||
_pickerTextFont = [UIFont systemFontOfSize:18.0f];
|
||||
}
|
||||
return _pickerTextFont;
|
||||
}
|
||||
|
||||
- (CGFloat)pickerHeight {
|
||||
if (_pickerHeight < 40) {
|
||||
_pickerHeight = 216.0f * kScaleFit;
|
||||
}
|
||||
return _pickerHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)rowHeight {
|
||||
if (_rowHeight < 20) {
|
||||
_rowHeight = 35.0f;
|
||||
}
|
||||
return _rowHeight;
|
||||
}
|
||||
|
||||
- (NSString *)language {
|
||||
if (!_language) {
|
||||
// 跟随系统的首选语言自动改变
|
||||
_language = [NSLocale preferredLanguages].firstObject;
|
||||
}
|
||||
return _language;
|
||||
}
|
||||
|
||||
- (UIColor *)dateUnitTextColor {
|
||||
if (!_dateUnitTextColor) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_dateUnitTextColor = [UIColor labelColor];
|
||||
} else {
|
||||
_dateUnitTextColor = kDefaultTextColor;
|
||||
}
|
||||
}
|
||||
return _dateUnitTextColor;
|
||||
}
|
||||
|
||||
- (UIFont *)dateUnitTextFont {
|
||||
if (!_dateUnitTextFont) {
|
||||
_dateUnitTextFont = [UIFont systemFontOfSize:18.0f];
|
||||
}
|
||||
return _dateUnitTextFont;
|
||||
}
|
||||
|
||||
#pragma mark - 创建自定义动态颜色(适配深色模式)
|
||||
- (UIColor *)br_customDynamicColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIColor *dyColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
|
||||
if ([traitCollection userInterfaceStyle] == UIUserInterfaceStyleLight) {
|
||||
return lightColor;
|
||||
} else {
|
||||
return darkColor;
|
||||
}
|
||||
}];
|
||||
return dyColor;
|
||||
} else {
|
||||
return lightColor;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 模板样式1 - 取消/确定按钮圆角样式
|
||||
+ (instancetype)pickerStyleWithThemeColor:(UIColor *)themeColor {
|
||||
BRPickerStyle *customStyle = [[self alloc]init];
|
||||
if (themeColor) {
|
||||
customStyle.cancelTextColor = themeColor;
|
||||
customStyle.cancelBorderStyle = BRBorderStyleSolid;
|
||||
customStyle.doneColor = themeColor;
|
||||
customStyle.doneTextColor = [UIColor whiteColor];
|
||||
customStyle.doneBorderStyle = BRBorderStyleFill;
|
||||
}
|
||||
return customStyle;
|
||||
}
|
||||
|
||||
#pragma mark - 模板样式2 - 顶部圆角样式 + 完成按钮
|
||||
+ (instancetype)pickerStyleWithDoneTextColor:(UIColor *)doneTextColor {
|
||||
BRPickerStyle *customStyle = [[BRPickerStyle alloc]init];
|
||||
if (doneTextColor) {
|
||||
customStyle.topCornerRadius = 16.0f;
|
||||
customStyle.hiddenCancelBtn = YES;
|
||||
customStyle.hiddenTitleLine = YES;
|
||||
customStyle.titleLabelFrame = CGRectMake(20, 4, 100, 40);
|
||||
customStyle.doneTextColor = doneTextColor;
|
||||
customStyle.doneTextFont = [UIFont boldSystemFontOfSize:16.0f];
|
||||
customStyle.doneBtnFrame = CGRectMake(SCREEN_WIDTH - 60, 4, 60, 40);
|
||||
customStyle.doneBtnTitle = [NSBundle br_localizedStringForKey:@"完成" language:customStyle.language];
|
||||
}
|
||||
return customStyle;
|
||||
}
|
||||
|
||||
#pragma mark - 模板样式3 - 顶部圆角样式 + 图标按钮
|
||||
+ (instancetype)pickerStyleWithDoneBtnImage:(UIImage *)doneBtnImage {
|
||||
BRPickerStyle *customStyle = [[BRPickerStyle alloc]init];
|
||||
if (doneBtnImage) {
|
||||
customStyle.topCornerRadius = 16.0f;
|
||||
customStyle.hiddenTitleLine = YES;
|
||||
customStyle.hiddenCancelBtn = YES;
|
||||
customStyle.titleLabelFrame = CGRectMake(20, 4, 100, 40);
|
||||
customStyle.doneBtnImage = doneBtnImage;
|
||||
customStyle.doneBtnFrame = CGRectMake(SCREEN_WIDTH - 44, 4, 40, 40);
|
||||
}
|
||||
return customStyle;
|
||||
}
|
||||
|
||||
#pragma mark - 模板样式4 - 日期选择器单位顶部显示
|
||||
+ (instancetype)pickerStyleWithDateUnitOnTop {
|
||||
BRPickerStyle *customStyle = [[self alloc]init];
|
||||
customStyle.titleBarHeight += customStyle.rowHeight + 10;
|
||||
customStyle.horizontalCenter = YES;
|
||||
customStyle.dateUnitOffsetY = -(customStyle.pickerHeight / 2 + customStyle.rowHeight / 2);
|
||||
|
||||
return customStyle;
|
||||
}
|
||||
|
||||
@end
|
||||
13508
SweetParty/第三方库/BRPickerView/Base/BRPickerView.bundle/BRCity.json
Executable file
13508
SweetParty/第三方库/BRPickerView/Base/BRPickerView.bundle/BRCity.json
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
BRPickerViewDemo
|
||||
|
||||
Created by 任波 on 2019/10/30.
|
||||
Copyright © 2019 91renb. All rights reserved.
|
||||
*/
|
||||
|
||||
"确定" = "OK";
|
||||
"取消" = "Cancel";
|
||||
"完成" = "Done";
|
||||
|
||||
"年" = " Y";
|
||||
"月" = " M";
|
||||
"日" = " D";
|
||||
"时" = " H";
|
||||
"分" = " M";
|
||||
"秒" = " S";
|
||||
"至今" = " Now";
|
||||
"今天" = " Today";
|
||||
|
||||
"周一" = " Mon";
|
||||
"周二" = " Tue";
|
||||
"周三" = " Wed";
|
||||
"周四" = " Thu";
|
||||
"周五" = " Fri";
|
||||
"周六" = " Sat";
|
||||
"周日" = " Sun";
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
BRPickerViewDemo
|
||||
|
||||
Created by 任波 on 2019/10/30.
|
||||
Copyright © 2019 91renb. All rights reserved.
|
||||
*/
|
||||
|
||||
"确定" = "确定";
|
||||
"取消" = "取消";
|
||||
"完成" = "完成";
|
||||
|
||||
"年" = "年";
|
||||
"月" = "月";
|
||||
"日" = "日";
|
||||
"时" = "时";
|
||||
"分" = "分";
|
||||
"秒" = "秒";
|
||||
"至今" = "至今";
|
||||
"今天" = "今天";
|
||||
|
||||
"周一" = "周一";
|
||||
"周二" = "周二";
|
||||
"周三" = "周三";
|
||||
"周四" = "周四";
|
||||
"周五" = "周五";
|
||||
"周六" = "周六";
|
||||
"周日" = "周日";
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
BRPickerViewDemo
|
||||
|
||||
Created by 任波 on 2019/10/30.
|
||||
Copyright © 2019 91renb. All rights reserved.
|
||||
*/
|
||||
|
||||
"确定" = "確定";
|
||||
"取消" = "取消";
|
||||
"完成" = "完成";
|
||||
|
||||
"年" = "年";
|
||||
"月" = "月";
|
||||
"日" = "日";
|
||||
"时" = "時";
|
||||
"分" = "分";
|
||||
"秒" = "秒";
|
||||
"至今" = "至今";
|
||||
"今天" = "今天";
|
||||
|
||||
"周一" = "周壹";
|
||||
"周二" = "周二";
|
||||
"周三" = "周三";
|
||||
"周四" = "周四";
|
||||
"周五" = "周五";
|
||||
"周六" = "周六";
|
||||
"周日" = "周日";
|
||||
106
SweetParty/第三方库/BRPickerView/Base/BRPickerViewMacro.h
Executable file
106
SweetParty/第三方库/BRPickerView/Base/BRPickerViewMacro.h
Executable file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// BRPickerViewMacro.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2018/4/23.
|
||||
// Copyright © 2018年 91renb. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef BRPickerViewMacro_h
|
||||
#define BRPickerViewMacro_h
|
||||
|
||||
// 屏幕大小、宽、高
|
||||
#ifndef SCREEN_BOUNDS
|
||||
#define SCREEN_BOUNDS [UIScreen mainScreen].bounds
|
||||
#endif
|
||||
#ifndef SCREEN_WIDTH
|
||||
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
|
||||
#endif
|
||||
#ifndef SCREEN_HEIGHT
|
||||
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
|
||||
#endif
|
||||
|
||||
// RGB颜色(16进制)
|
||||
#define BR_RGB_HEX(rgbValue, a) \
|
||||
[UIColor colorWithRed:((CGFloat)((rgbValue & 0xFF0000) >> 16)) / 255.0 \
|
||||
green:((CGFloat)((rgbValue & 0xFF00) >> 8)) / 255.0 \
|
||||
blue:((CGFloat)(rgbValue & 0xFF)) / 255.0 alpha:(a)]
|
||||
|
||||
#define BR_IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
|
||||
#define BR_IS_PAD (UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPad)
|
||||
|
||||
// 等比例适配系数
|
||||
#ifndef kScaleFit
|
||||
#define kScaleFit (BR_IS_IPHONE ? ((SCREEN_WIDTH < SCREEN_HEIGHT) ? SCREEN_WIDTH / 375.0f : SCREEN_WIDTH / 667.0f) : 1.1f)
|
||||
#endif
|
||||
|
||||
// 状态栏的高度(20 / 44(iPhoneX))
|
||||
#define BR_STATUSBAR_HEIGHT ([UIApplication sharedApplication].statusBarFrame.size.height)
|
||||
#define BR_IS_iPhoneX ((BR_STATUSBAR_HEIGHT == 44) ? YES : NO)
|
||||
// 底部安全区域远离高度
|
||||
#define BR_BOTTOM_MARGIN ((CGFloat)(BR_IS_iPhoneX ? ((SCREEN_WIDTH < SCREEN_HEIGHT) ? 34 : 21) : 0))
|
||||
|
||||
// 静态库中编写 Category 时的便利宏,用于解决 Category 方法从静态库中加载需要特别设置的问题
|
||||
#ifndef BRSYNTH_DUMMY_CLASS
|
||||
|
||||
#define BRSYNTH_DUMMY_CLASS(_name_) \
|
||||
@interface BRSYNTH_DUMMY_CLASS_ ## _name_ : NSObject @end \
|
||||
@implementation BRSYNTH_DUMMY_CLASS_ ## _name_ @end
|
||||
|
||||
#endif
|
||||
|
||||
// 过期提醒
|
||||
#define BRPickerViewDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead)
|
||||
|
||||
// 打印错误日志
|
||||
#ifdef DEBUG
|
||||
#define BRErrorLog(...) NSLog(@"reason: %@", [NSString stringWithFormat:__VA_ARGS__])
|
||||
#else
|
||||
#define BRErrorLog(...)
|
||||
#endif
|
||||
|
||||
/**
|
||||
合成弱引用/强引用
|
||||
|
||||
Example:
|
||||
@weakify(self)
|
||||
[self doSomething^{
|
||||
@strongify(self)
|
||||
if (!self) return;
|
||||
...
|
||||
}];
|
||||
|
||||
*/
|
||||
#ifndef weakify
|
||||
#if DEBUG
|
||||
#if __has_feature(objc_arc)
|
||||
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
|
||||
#else
|
||||
#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
|
||||
#endif
|
||||
#else
|
||||
#if __has_feature(objc_arc)
|
||||
#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
|
||||
#else
|
||||
#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef strongify
|
||||
#if DEBUG
|
||||
#if __has_feature(objc_arc)
|
||||
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
|
||||
#else
|
||||
#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
|
||||
#endif
|
||||
#else
|
||||
#if __has_feature(objc_arc)
|
||||
#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
|
||||
#else
|
||||
#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* BRPickerViewMacro_h */
|
||||
28
SweetParty/第三方库/BRPickerView/Base/NSBundle+BRPickerView.h
Executable file
28
SweetParty/第三方库/BRPickerView/Base/NSBundle+BRPickerView.h
Executable file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// NSBundle+BRPickerView.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2019/10/30.
|
||||
// Copyright © 2019 91renb. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSBundle (BRPickerView)
|
||||
|
||||
/// 获取 BRPickerView.bundle
|
||||
+ (instancetype)br_pickerBundle;
|
||||
|
||||
/// 获取城市JSON数据
|
||||
+ (NSArray *)br_addressJsonArray;
|
||||
|
||||
/// 获取国际化后的文本
|
||||
/// @param key 代表 Localizable.strings 文件中 key-value 中的 key。
|
||||
/// @param language 设置语言(可为空,为nil时将随系统的语言自动改变)
|
||||
+ (NSString *)br_localizedStringForKey:(NSString *)key language:(NSString *)language;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
79
SweetParty/第三方库/BRPickerView/Base/NSBundle+BRPickerView.m
Executable file
79
SweetParty/第三方库/BRPickerView/Base/NSBundle+BRPickerView.m
Executable file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// NSBundle+BRPickerView.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2019/10/30.
|
||||
// Copyright © 2019 91renb. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSBundle+BRPickerView.h"
|
||||
#import "BRBaseView.h"
|
||||
|
||||
BRSYNTH_DUMMY_CLASS(NSBundle_BRPickerView)
|
||||
|
||||
@implementation NSBundle (BRPickerView)
|
||||
|
||||
#pragma mark - 获取 BRPickerView.bundle
|
||||
+ (instancetype)br_pickerBundle {
|
||||
static NSBundle *pickerBundle = nil;
|
||||
if (pickerBundle == nil) {
|
||||
/*
|
||||
先拿到最外面的 bundle。
|
||||
对 framework 链接方式来说就是 framework 的 bundle 根目录,
|
||||
对静态库链接方式来说就是 target client 的 main bundle,
|
||||
然后再去找下面名为 BRPickerView 的 bundle 对象。
|
||||
*/
|
||||
NSBundle *bundle = [NSBundle bundleForClass:[BRBaseView class]];
|
||||
NSURL *url = [bundle URLForResource:@"BRPickerView" withExtension:@"bundle"];
|
||||
pickerBundle = [NSBundle bundleWithURL:url];
|
||||
}
|
||||
return pickerBundle;
|
||||
}
|
||||
|
||||
#pragma mark - 获取城市JSON数据
|
||||
+ (NSArray *)br_addressJsonArray {
|
||||
static NSArray *cityArray = nil;
|
||||
if (cityArray == nil) {
|
||||
// 获取本地JSON文件
|
||||
NSString *filePath = [[self br_pickerBundle] pathForResource:@"BRCity" ofType:@"json"];
|
||||
NSData *data = [NSData dataWithContentsOfFile:filePath];
|
||||
cityArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
|
||||
}
|
||||
return cityArray;
|
||||
}
|
||||
|
||||
#pragma mark - 获取国际化后的文本
|
||||
+ (NSString *)br_localizedStringForKey:(NSString *)key language:(NSString *)language {
|
||||
return [self br_localizedStringForKey:key value:nil language:language];
|
||||
}
|
||||
|
||||
+ (NSString *)br_localizedStringForKey:(NSString *)key value:(NSString *)value language:(NSString *)language {
|
||||
static NSBundle *bundle = nil;
|
||||
if (bundle == nil) {
|
||||
// 如果没有手动设置语言,将随系统的语言自动改变
|
||||
if (!language) {
|
||||
// 系统首选语言
|
||||
language = [NSLocale preferredLanguages].firstObject;
|
||||
}
|
||||
|
||||
if ([language hasPrefix:@"en"]) {
|
||||
language = @"en";
|
||||
} else if ([language hasPrefix:@"zh"]) {
|
||||
if ([language rangeOfString:@"Hans"].location != NSNotFound) {
|
||||
language = @"zh-Hans"; // 简体中文
|
||||
} else { // zh-Hant、zh-HK、zh-TW
|
||||
language = @"zh-Hant"; // 繁體中文
|
||||
}
|
||||
} else {
|
||||
language = @"en";
|
||||
}
|
||||
|
||||
// 从 BRPickerView.bundle 中查找资源
|
||||
bundle = [NSBundle bundleWithPath:[[self br_pickerBundle] pathForResource:language ofType:@"lproj"]];
|
||||
}
|
||||
value = [bundle localizedStringForKey:key value:value table:nil];
|
||||
|
||||
return [[NSBundle mainBundle] localizedStringForKey:key value:value table:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
187
SweetParty/第三方库/BRPickerView/DatePickerView/BRDatePickerView.h
Executable file
187
SweetParty/第三方库/BRPickerView/DatePickerView/BRDatePickerView.h
Executable file
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// BRDatePickerView.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRBaseView.h"
|
||||
#import "NSDate+BRPickerView.h"
|
||||
|
||||
/// 日期选择器格式
|
||||
typedef NS_ENUM(NSInteger, BRDatePickerMode) {
|
||||
// ----- 以下4种是系统自带的样式 -----
|
||||
/** 【yyyy-MM-dd】UIDatePickerModeDate(默认) */
|
||||
BRDatePickerModeDate,
|
||||
/** 【yyyy-MM-dd HH:mm】 UIDatePickerModeDateAndTime */
|
||||
BRDatePickerModeDateAndTime,
|
||||
/** 【HH:mm】UIDatePickerModeTime */
|
||||
BRDatePickerModeTime,
|
||||
/** 【HH:mm】UIDatePickerModeCountDownTimer */
|
||||
BRDatePickerModeCountDownTimer,
|
||||
|
||||
// ----- 以下11种是自定义样式 -----
|
||||
/** 【yyyy-MM-dd HH:mm:ss】年月日时分秒 */
|
||||
BRDatePickerModeYMDHMS,
|
||||
/** 【yyyy-MM-dd HH:mm】年月日时分 */
|
||||
BRDatePickerModeYMDHM,
|
||||
/** 【yyyy-MM-dd HH】年月日时 */
|
||||
BRDatePickerModeYMDH,
|
||||
/** 【MM-dd HH:mm】月日时分 */
|
||||
BRDatePickerModeMDHM,
|
||||
/** 【yyyy-MM-dd】年月日 */
|
||||
BRDatePickerModeYMD,
|
||||
/** 【yyyy-MM】年月 */
|
||||
BRDatePickerModeYM,
|
||||
/** 【yyyy】年 */
|
||||
BRDatePickerModeY,
|
||||
/** 【MM-dd】月日 */
|
||||
BRDatePickerModeMD,
|
||||
/** 【HH:mm:ss】时分秒 */
|
||||
BRDatePickerModeHMS,
|
||||
/** 【HH:mm】时分 */
|
||||
BRDatePickerModeHM,
|
||||
/** 【mm:ss】分秒 */
|
||||
BRDatePickerModeMS
|
||||
};
|
||||
|
||||
/// 日期单位显示的位置
|
||||
typedef NS_ENUM(NSInteger, BRShowUnitType) {
|
||||
/** 日期单位显示全部行(默认) */
|
||||
BRShowUnitTypeAll,
|
||||
/** 日期单位只显示一行,默认在选择器的中间行 */
|
||||
BRShowUnitTypeSingleRow,
|
||||
/** 日期单位不显示 */
|
||||
BRShowUnitTypeNone
|
||||
};
|
||||
|
||||
typedef void (^BRDateResultBlock)(NSDate *selectDate, NSString *selectValue);
|
||||
|
||||
@interface BRDatePickerView : BRBaseView
|
||||
|
||||
/**
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// 【用法一】
|
||||
/// 特点:灵活,扩展性强(推荐使用!)
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/** 日期选择器显示类型 */
|
||||
@property (nonatomic, assign) BRDatePickerMode pickerMode;
|
||||
|
||||
/** 设置选中的时间(selectDate 优先级高于 selectValue,推荐使用 selectDate)*/
|
||||
@property (nonatomic, strong) NSDate *selectDate;
|
||||
@property (nonatomic, copy) NSString *selectValue;
|
||||
|
||||
/** 最小时间(可使用 NSDate+BRPickerView 分类中对应的方法进行创建)*/
|
||||
@property (nonatomic, strong) NSDate *minDate;
|
||||
/** 最大时间(可使用 NSDate+BRPickerView 分类中对应的方法进行创建)*/
|
||||
@property (nonatomic, strong) NSDate *maxDate;
|
||||
|
||||
/** 选择结果的回调 */
|
||||
@property (nonatomic, copy) BRDateResultBlock resultBlock;
|
||||
|
||||
/** 滚动选择时触发的回调 */
|
||||
@property (nonatomic, copy) BRDateResultBlock changeBlock;
|
||||
|
||||
/** 日期单位显示类型 */
|
||||
@property (nonatomic, assign) BRShowUnitType showUnitType;
|
||||
|
||||
/** 隐藏日期单位,默认为NO */
|
||||
@property (nonatomic, assign) BOOL hiddenDateUnit BRPickerViewDeprecated("请使用 showUnitType");
|
||||
|
||||
/** 是否显示【星期】,默认为 NO */
|
||||
@property (nonatomic, assign, getter=isShowWeek) BOOL showWeek;
|
||||
|
||||
/** 是否显示【今天】,默认为 NO */
|
||||
@property (nonatomic, assign, getter=isShowToday) BOOL showToday;
|
||||
|
||||
/** 是否添加【至今】,默认为 NO */
|
||||
@property (nonatomic, assign, getter=isAddToNow) BOOL addToNow;
|
||||
|
||||
/** 设置分的时间间隔,默认为1 */
|
||||
@property (nonatomic, assign) NSInteger minuteInterval;
|
||||
|
||||
/** 设置秒的时间间隔,默认为1 */
|
||||
@property (nonatomic, assign) NSInteger secondInterval;
|
||||
|
||||
/// 初始化时间选择器
|
||||
/// @param pickerMode 日期选择器显示类型
|
||||
- (instancetype)initWithPickerMode:(BRDatePickerMode)pickerMode;
|
||||
|
||||
/// 弹出选择器视图
|
||||
- (void)show;
|
||||
|
||||
/// 关闭选择器视图
|
||||
- (void)dismiss;
|
||||
|
||||
|
||||
|
||||
|
||||
//================================================= 华丽的分割线 =================================================
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// 【用法二】:快捷使用,直接选择下面其中的一个方法进行使用
|
||||
/// 特点:快捷,方便
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* 1.显示时间选择器
|
||||
*
|
||||
* @param mode 日期显示类型
|
||||
* @param title 选择器标题
|
||||
* @param selectValue 默认选中的时间(默认选中当前时间)
|
||||
* @param resultBlock 选择结果的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showDatePickerWithMode:(BRDatePickerMode)mode
|
||||
title:(NSString *)title
|
||||
selectValue:(NSString *)selectValue
|
||||
resultBlock:(BRDateResultBlock)resultBlock;
|
||||
|
||||
/**
|
||||
* 2.显示时间选择器
|
||||
*
|
||||
* @param mode 日期显示类型
|
||||
* @param title 选择器标题
|
||||
* @param selectValue 默认选中的时间(默认选中当前时间)
|
||||
* @param isAutoSelect 是否自动选择,即滚动选择器后就执行结果回调,默认为 NO
|
||||
* @param resultBlock 选择结果的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showDatePickerWithMode:(BRDatePickerMode)mode
|
||||
title:(NSString *)title
|
||||
selectValue:(NSString *)selectValue
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRDateResultBlock)resultBlock;
|
||||
|
||||
/**
|
||||
* 3.显示时间选择器
|
||||
*
|
||||
* @param mode 日期显示类型
|
||||
* @param title 选择器标题
|
||||
* @param selectValue 默认选中的时间(默认选中当前时间)
|
||||
* @param minDate 最小时间(可使用 NSDate+BRPickerView 分类中对应的方法进行创建)
|
||||
* @param maxDate 最大时间(可使用 NSDate+BRPickerView 分类中对应的方法进行创建)
|
||||
* @param isAutoSelect 是否自动选择,即滚动选择器后就执行结果回调,默认为 NO
|
||||
* @param resultBlock 选择结果的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showDatePickerWithMode:(BRDatePickerMode)mode
|
||||
title:(NSString *)title
|
||||
selectValue:(NSString *)selectValue
|
||||
minDate:(NSDate *)minDate
|
||||
maxDate:(NSDate *)maxDate
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRDateResultBlock)resultBlock;
|
||||
|
||||
@end
|
||||
1758
SweetParty/第三方库/BRPickerView/DatePickerView/BRDatePickerView.m
Executable file
1758
SweetParty/第三方库/BRPickerView/DatePickerView/BRDatePickerView.m
Executable file
File diff suppressed because it is too large
Load Diff
67
SweetParty/第三方库/BRPickerView/DatePickerView/NSDate+BRPickerView.h
Executable file
67
SweetParty/第三方库/BRPickerView/DatePickerView/NSDate+BRPickerView.h
Executable file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// NSDate+BRPickerView.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2018/3/15.
|
||||
// Copyright © 2018年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSDate (BRPickerView)
|
||||
/// 获取指定date的详细信息
|
||||
@property (readonly) NSInteger br_year; // 年
|
||||
@property (readonly) NSInteger br_month; // 月
|
||||
@property (readonly) NSInteger br_day; // 日
|
||||
@property (readonly) NSInteger br_hour; // 时
|
||||
@property (readonly) NSInteger br_minute; // 分
|
||||
@property (readonly) NSInteger br_second; // 秒
|
||||
@property (readonly) NSInteger br_weekday; // 星期
|
||||
@property (readonly) NSString *br_weekdayString;
|
||||
|
||||
/** 创建 date */
|
||||
/** yyyy */
|
||||
+ (nullable NSDate *)br_setYear:(NSInteger)year;
|
||||
/** yyyy-MM */
|
||||
+ (nullable NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month;
|
||||
/** yyyy-MM-dd */
|
||||
+ (nullable NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day;
|
||||
/** yyyy-MM-dd HH */
|
||||
+ (nullable NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour;
|
||||
/** yyyy-MM-dd HH:mm */
|
||||
+ (nullable NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute;
|
||||
/** yyyy-MM-dd HH:mm:ss */
|
||||
+ (nullable NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute second:(NSInteger)second;
|
||||
/** MM-dd HH:mm */
|
||||
+ (nullable NSDate *)br_setMonth:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute;
|
||||
/** MM-dd */
|
||||
+ (nullable NSDate *)br_setMonth:(NSInteger)month day:(NSInteger)day;
|
||||
/** HH:mm:ss */
|
||||
+ (nullable NSDate *)br_setHour:(NSInteger)hour minute:(NSInteger)minute second:(NSInteger)second;
|
||||
/** HH:mm */
|
||||
+ (nullable NSDate *)br_setHour:(NSInteger)hour minute:(NSInteger)minute;
|
||||
/** mm:ss */
|
||||
+ (nullable NSDate *)br_setMinute:(NSInteger)minute second:(NSInteger)second;
|
||||
|
||||
|
||||
/** 日期和字符串之间的转换:NSDate --> NSString */
|
||||
+ (nullable NSString *)br_getDateString:(NSDate *)date format:(NSString *)format;
|
||||
/** 日期和字符串之间的转换:NSString --> NSDate */
|
||||
+ (nullable NSDate *)br_getDate:(NSString *)dateString format:(NSString *)format;
|
||||
/** 获取某个月的天数(通过年月求每月天数)*/
|
||||
+ (NSUInteger)br_getDaysInYear:(NSInteger)year month:(NSInteger)month;
|
||||
|
||||
/** 获取 日期加上/减去某天数后的新日期 */
|
||||
- (nullable NSDate *)br_getNewDate:(NSDate *)date addDays:(NSTimeInterval)days;
|
||||
|
||||
/**
|
||||
* 比较两个时间大小(可以指定比较级数,即按指定格式进行比较)
|
||||
*/
|
||||
- (NSComparisonResult)br_compare:(NSDate *)targetDate format:(NSString *)format;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
277
SweetParty/第三方库/BRPickerView/DatePickerView/NSDate+BRPickerView.m
Executable file
277
SweetParty/第三方库/BRPickerView/DatePickerView/NSDate+BRPickerView.m
Executable file
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// NSDate+BRPickerView.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2018/3/15.
|
||||
// Copyright © 2018年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "NSDate+BRPickerView.h"
|
||||
#import "BRPickerViewMacro.h"
|
||||
|
||||
BRSYNTH_DUMMY_CLASS(NSDate_BRPickerView)
|
||||
|
||||
static const NSCalendarUnit unitFlags = (NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekOfMonth | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitWeekday | NSCalendarUnitWeekdayOrdinal);
|
||||
|
||||
@implementation NSDate (BRPickerView)
|
||||
|
||||
#pragma mark - 获取日历单例对象
|
||||
+ (NSCalendar *)calendar {
|
||||
static NSCalendar *sharedCalendar = nil;
|
||||
if (!sharedCalendar) {
|
||||
// 创建日历对象:返回当前客户端的逻辑日历(当每次修改系统日历设定,其实例化的对象也会随之改变)
|
||||
sharedCalendar = [NSCalendar autoupdatingCurrentCalendar];
|
||||
}
|
||||
return sharedCalendar;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的年份
|
||||
- (NSInteger)br_year {
|
||||
// NSDateComponent 可以获得日期的详细信息,即日期的组成
|
||||
NSDateComponents *components = [[NSDate calendar] components:unitFlags fromDate:self];
|
||||
return components.year;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的月份
|
||||
- (NSInteger)br_month {
|
||||
NSDateComponents *components = [[NSDate calendar] components:unitFlags fromDate:self];
|
||||
return components.month;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的天
|
||||
- (NSInteger)br_day {
|
||||
NSDateComponents *components = [[NSDate calendar] components:unitFlags fromDate:self];
|
||||
return components.day;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的小时
|
||||
- (NSInteger)br_hour {
|
||||
NSDateComponents *components = [[NSDate calendar] components:unitFlags fromDate:self];
|
||||
return components.hour;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的分钟
|
||||
- (NSInteger)br_minute {
|
||||
NSDateComponents *comps = [[NSDate calendar] components:unitFlags fromDate:self];
|
||||
return comps.minute;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的秒
|
||||
- (NSInteger)br_second {
|
||||
NSDateComponents *components = [[NSDate calendar] components:unitFlags fromDate:self];
|
||||
return components.second;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的星期
|
||||
- (NSInteger)br_weekday {
|
||||
NSDateComponents *components = [[NSDate calendar] components:unitFlags fromDate:self];
|
||||
return components.weekday;
|
||||
}
|
||||
|
||||
#pragma mark - 获取指定日期的星期
|
||||
- (NSString *)br_weekdayString {
|
||||
switch (self.br_weekday - 1) {
|
||||
case 0:
|
||||
{
|
||||
return @"周日";
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
return @"周一";
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
return @"周二";
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
return @"周三";
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
return @"周四";
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
{
|
||||
return @"周五";
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
{
|
||||
return @"周六";
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return @"";
|
||||
}
|
||||
|
||||
#pragma mark - 创建date
|
||||
+ (NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute second:(NSInteger)second {
|
||||
NSCalendar *calendar = [NSDate calendar];
|
||||
// 初始化日期组件
|
||||
NSDateComponents *components = [calendar components:unitFlags fromDate:[NSDate date]];
|
||||
if (year > 0) {
|
||||
components.year = year;
|
||||
}
|
||||
if (month > 0) {
|
||||
components.month = month;
|
||||
}
|
||||
if (day > 0) {
|
||||
components.day = day;
|
||||
}
|
||||
if (hour >= 0) {
|
||||
components.hour = hour;
|
||||
}
|
||||
if (minute >= 0) {
|
||||
components.minute = minute;
|
||||
}
|
||||
if (second >= 0) {
|
||||
components.second = second;
|
||||
}
|
||||
NSDate *date = [calendar dateFromComponents:components];
|
||||
return date;
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute {
|
||||
return [self br_setYear:year month:month day:day hour:hour minute:minute second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour {
|
||||
return [self br_setYear:year month:month day:day hour:hour minute:0 second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day {
|
||||
return [self br_setYear:year month:month day:day hour:0 minute:0 second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setYear:(NSInteger)year month:(NSInteger)month {
|
||||
return [self br_setYear:year month:month day:0 hour:0 minute:0 second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setYear:(NSInteger)year {
|
||||
return [self br_setYear:year month:0 day:0 hour:0 minute:0 second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setMonth:(NSInteger)month day:(NSInteger)day hour:(NSInteger)hour minute:(NSInteger)minute {
|
||||
return [self br_setYear:0 month:month day:day hour:hour minute:minute second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setMonth:(NSInteger)month day:(NSInteger)day {
|
||||
return [self br_setYear:0 month:month day:day hour:0 minute:0 second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setHour:(NSInteger)hour minute:(NSInteger)minute second:(NSInteger)second {
|
||||
return [self br_setYear:0 month:0 day:0 hour:hour minute:minute second:second];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setHour:(NSInteger)hour minute:(NSInteger)minute {
|
||||
return [self br_setYear:0 month:0 day:0 hour:hour minute:minute second:0];
|
||||
}
|
||||
|
||||
+ (NSDate *)br_setMinute:(NSInteger)minute second:(NSInteger)second {
|
||||
return [self br_setYear:0 month:0 day:0 hour:0 minute:minute second:second];
|
||||
}
|
||||
|
||||
#pragma mark - NSDate时间 和 字符串时间 之间的转换:NSDate 转 NSString
|
||||
+ (NSString *)br_getDateString:(NSDate *)date format:(NSString *)format {
|
||||
if (!date) {
|
||||
return nil;
|
||||
}
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
// 不设置会默认使用当前所在的时区
|
||||
// dateFormatter.timeZone = [NSTimeZone systemTimeZone];
|
||||
// 设置日期格式
|
||||
dateFormatter.dateFormat = format;
|
||||
NSString *dateString = [dateFormatter stringFromDate:date];
|
||||
|
||||
return dateString;
|
||||
}
|
||||
|
||||
#pragma mark - NSDate时间 和 字符串时间 之间的转换:NSString 转 NSDate
|
||||
+ (NSDate *)br_getDate:(NSString *)dateString format:(NSString *)format {
|
||||
if (!dateString || dateString.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
// 设置日期格式
|
||||
dateFormatter.dateFormat = format;
|
||||
// 一些夏令时时间(如:1949-05-01等),会导致 NSDateFormatter 格式化失败,返回null
|
||||
NSDate *date = [dateFormatter dateFromString:dateString];
|
||||
if (!date) {
|
||||
date = [NSDate date];
|
||||
}
|
||||
|
||||
// 设置转换后的目标日期时区
|
||||
NSTimeZone *toTimeZone = [NSTimeZone localTimeZone];
|
||||
// 转换后源日期与世界标准时间的偏移量(解决8小时时间差问题)
|
||||
NSInteger toGMTOffset = [toTimeZone secondsFromGMTForDate:date];
|
||||
// 设置时区:字符串时间是当前时区的时间,NSDate存储的是世界标准时间(零时区的时间)
|
||||
dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:toGMTOffset];
|
||||
|
||||
date = [dateFormatter dateFromString:dateString];
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
#pragma mark - 获取某个月的天数(通过年月求每月天数)
|
||||
+ (NSUInteger)br_getDaysInYear:(NSInteger)year month:(NSInteger)month {
|
||||
BOOL isLeapYear = year % 4 == 0 ? (year % 100 == 0 ? (year % 400 == 0 ? YES : NO) : YES) : NO;
|
||||
switch (month) {
|
||||
case 1:case 3:case 5:case 7:case 8:case 10:case 12:
|
||||
{
|
||||
return 31;
|
||||
break;
|
||||
}
|
||||
case 4:case 6:case 9:case 11:
|
||||
{
|
||||
return 30;
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if (isLeapYear) {
|
||||
return 29;
|
||||
break;
|
||||
} else {
|
||||
return 28;
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma mark - 获取 日期加上/减去某天数后的新日期
|
||||
- (NSDate *)br_getNewDate:(NSDate *)date addDays:(NSTimeInterval)days {
|
||||
// days 为正数时,表示几天之后的日期;负数表示几天之前的日期
|
||||
return [self dateByAddingTimeInterval:60 * 60 * 24 * days];
|
||||
}
|
||||
|
||||
#pragma mark - 比较两个时间大小(可以指定比较级数,即按指定格式进行比较)
|
||||
- (NSComparisonResult)br_compare:(NSDate *)targetDate format:(NSString *)format {
|
||||
NSString *dateString1 = [NSDate br_getDateString:self format:format];
|
||||
NSString *dateString2 = [NSDate br_getDateString:targetDate format:format];
|
||||
NSDate *date1 = [NSDate br_getDate:dateString1 format:format];
|
||||
NSDate *date2 = [NSDate br_getDate:dateString2 format:format];
|
||||
if ([date1 compare:date2] == NSOrderedDescending) {
|
||||
return 1;
|
||||
} else if ([date1 compare:date2] == NSOrderedAscending) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
23
SweetParty/第三方库/BRPickerView/StringPickerView/BRResultModel.h
Executable file
23
SweetParty/第三方库/BRPickerView/StringPickerView/BRResultModel.h
Executable file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// BRResultModel.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2019/10/2.
|
||||
// Copyright © 2019年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface BRResultModel : NSObject
|
||||
/** ID */
|
||||
@property (nonatomic, copy) NSString *ID;
|
||||
/** 名称 */
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
|
||||
/** 选择值对应的索引(行数) */
|
||||
@property (nonatomic, assign) NSInteger index;
|
||||
/** 选择的值 */
|
||||
@property (nonatomic, copy) NSString *selectValue;
|
||||
|
||||
@end
|
||||
18
SweetParty/第三方库/BRPickerView/StringPickerView/BRResultModel.m
Executable file
18
SweetParty/第三方库/BRPickerView/StringPickerView/BRResultModel.m
Executable file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// BRResultModel.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2019/10/2.
|
||||
// Copyright © 2019年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRResultModel.h"
|
||||
|
||||
@implementation BRResultModel
|
||||
|
||||
- (NSString *)selectValue {
|
||||
return _name;
|
||||
}
|
||||
|
||||
@end
|
||||
156
SweetParty/第三方库/BRPickerView/StringPickerView/BRStringPickerView.h
Executable file
156
SweetParty/第三方库/BRPickerView/StringPickerView/BRStringPickerView.h
Executable file
@@ -0,0 +1,156 @@
|
||||
//
|
||||
// BRStringPickerView.h
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRBaseView.h"
|
||||
#import "BRResultModel.h"
|
||||
|
||||
/// 字符串选择器类型
|
||||
typedef NS_ENUM(NSInteger, BRStringPickerMode) {
|
||||
/** 单列字符串选择 */
|
||||
BRStringPickerComponentSingle,
|
||||
/** 多列字符串选择(两列及两列以上) */
|
||||
BRStringPickerComponentMulti
|
||||
};
|
||||
|
||||
typedef void(^BRStringResultModelBlock)(BRResultModel *resultModel);
|
||||
|
||||
typedef void(^BRStringResultModelArrayBlock)(NSArray <BRResultModel *>*resultModelArr);
|
||||
|
||||
@interface BRStringPickerView : BRBaseView
|
||||
|
||||
/**
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// 【用法一】
|
||||
/// 特点:灵活,扩展性强(推荐使用!)
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/** 字符串选择器显示类型 */
|
||||
@property (nonatomic, assign) BRStringPickerMode pickerMode;
|
||||
|
||||
/**
|
||||
* 1.设置数据源
|
||||
* 单列:@[@"男", @"女", @"其他"]
|
||||
* 两列:@[@[@"语文", @"数学", @"英语"], @[@"优秀", @"良好", @"及格"]]
|
||||
* 多列:... ...
|
||||
*/
|
||||
@property (nonatomic, copy) NSArray *dataSourceArr;
|
||||
/**
|
||||
* 2.设置数据源
|
||||
* 直接传plist文件名:NSString类型(如:@"test.plist"),要带后缀名
|
||||
* 场景:可以将数据源数据(数组类型)放到plist文件中,直接传plist文件名更加简单
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *plistName;
|
||||
|
||||
/** 设置默认选中的位置【单列】 */
|
||||
@property (nonatomic, assign) NSInteger selectIndex;
|
||||
@property (nonatomic, copy) NSString *selectValue BRPickerViewDeprecated("推荐使用 selectIndex");
|
||||
|
||||
/** 设置默认选中的位置【多列】 */
|
||||
@property (nonatomic, copy) NSArray <NSNumber *>* selectIndexs;
|
||||
@property (nonatomic, copy) NSArray <NSString *>* selectValues BRPickerViewDeprecated("推荐使用 selectIndexs");
|
||||
|
||||
/** 选择结果的回调【单列】 */
|
||||
@property (nonatomic, copy) BRStringResultModelBlock resultModelBlock;
|
||||
/** 选择结果的回调【多列】 */
|
||||
@property (nonatomic, copy) BRStringResultModelArrayBlock resultModelArrayBlock;
|
||||
|
||||
/** 滚动选择时触发的回调【单列】 */
|
||||
@property (nonatomic, copy) BRStringResultModelBlock changeModelBlock;
|
||||
/** 滚动选择时触发的回调【多列】 */
|
||||
@property (nonatomic, copy) BRStringResultModelArrayBlock changeModelArrayBlock;
|
||||
|
||||
/// 初始化字符串选择器
|
||||
/// @param pickerMode 字符串选择器显示类型
|
||||
- (instancetype)initWithPickerMode:(BRStringPickerMode)pickerMode;
|
||||
|
||||
/// 弹出选择器视图
|
||||
- (void)show;
|
||||
|
||||
/// 关闭选择器视图
|
||||
- (void)dismiss;
|
||||
|
||||
|
||||
|
||||
|
||||
//================================================= 华丽的分割线 =================================================
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// 【用法二】:快捷使用,直接选择下面其中的一个方法进行使用
|
||||
/// 特点:快捷,方便
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* 1.显示【单列】字符串选择器
|
||||
*
|
||||
* @param title 选择器标题
|
||||
* @param dataSourceArr 数据源(如:@[@"男", @"女", @"其他"])
|
||||
* @param selectIndex 默认选中的位置
|
||||
* @param resultBlock 选择后的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndex:(NSInteger)selectIndex
|
||||
resultBlock:(BRStringResultModelBlock)resultBlock;
|
||||
|
||||
/**
|
||||
* 2.显示【单列】字符串选择器
|
||||
*
|
||||
* @param title 选择器标题
|
||||
* @param dataSourceArr 数据源(如:@[@"男", @"女", @"其他"])
|
||||
* @param selectIndex 默认选中的位置
|
||||
* @param isAutoSelect 是否自动选择,即滚动选择器后就执行结果回调,默认为 NO
|
||||
* @param resultBlock 选择后的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndex:(NSInteger)selectIndex
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRStringResultModelBlock)resultBlock;
|
||||
|
||||
/**
|
||||
* 3.显示【多列】字符串选择器
|
||||
*
|
||||
* @param title 选择器标题
|
||||
* @param dataSourceArr 数据源(如:@[@[@"语文", @"数学", @"英语"], @[@"优秀", @"良好", @"及格"]])
|
||||
* @param selectIndexs 默认选中的位置(传索引数组,如:@[@2, @1])
|
||||
* @param resultBlock 选择后的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showMultiPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
resultBlock:(BRStringResultModelArrayBlock)resultBlock;
|
||||
|
||||
/**
|
||||
* 4.显示【多列】字符串选择器
|
||||
*
|
||||
* @param title 选择器标题
|
||||
* @param dataSourceArr 数据源(如:@[@[@"语文", @"数学", @"英语"], @[@"优秀", @"良好", @"及格"]])
|
||||
* @param selectIndexs 默认选中的位置(传索引数组,如:@[@2, @1])
|
||||
* @param isAutoSelect 是否自动选择,即滚动选择器后就执行结果回调,默认为 NO
|
||||
* @param resultBlock 选择后的回调
|
||||
*
|
||||
*/
|
||||
+ (void)showMultiPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRStringResultModelArrayBlock)resultBlock;
|
||||
|
||||
@end
|
||||
419
SweetParty/第三方库/BRPickerView/StringPickerView/BRStringPickerView.m
Executable file
419
SweetParty/第三方库/BRPickerView/StringPickerView/BRStringPickerView.m
Executable file
@@ -0,0 +1,419 @@
|
||||
//
|
||||
// BRStringPickerView.m
|
||||
// BRPickerViewDemo
|
||||
//
|
||||
// Created by 任波 on 2017/8/11.
|
||||
// Copyright © 2017年 91renb. All rights reserved.
|
||||
//
|
||||
// 最新代码下载地址:https://github.com/91renb/BRPickerView
|
||||
|
||||
#import "BRStringPickerView.h"
|
||||
|
||||
@interface BRStringPickerView ()<UIPickerViewDelegate, UIPickerViewDataSource>
|
||||
{
|
||||
BOOL _isDataSourceValid; // 数据源是否合法
|
||||
}
|
||||
/** 字符串选择器 */
|
||||
@property (nonatomic, strong) UIPickerView *pickerView;
|
||||
/** 单列选择的值 */
|
||||
@property (nonatomic, copy) NSString *mSelectValue;
|
||||
/** 多列选择的值 */
|
||||
@property (nonatomic, copy) NSArray <NSString *>* mSelectValues;
|
||||
|
||||
@end
|
||||
|
||||
@implementation BRStringPickerView
|
||||
|
||||
#pragma mark - 1.显示【单列】字符串选择器
|
||||
+ (void)showPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndex:(NSInteger)selectIndex
|
||||
resultBlock:(BRStringResultModelBlock)resultBlock {
|
||||
[self showPickerWithTitle:title dataSourceArr:dataSourceArr selectIndex:selectIndex isAutoSelect:NO resultBlock:resultBlock];
|
||||
}
|
||||
|
||||
#pragma mark - 2.显示【单列】字符串选择器
|
||||
+ (void)showPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndex:(NSInteger)selectIndex
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRStringResultModelBlock)resultBlock {
|
||||
// 创建字符串选择器
|
||||
BRStringPickerView *strPickerView = [[BRStringPickerView alloc]init];
|
||||
strPickerView.pickerMode = BRStringPickerComponentSingle;
|
||||
strPickerView.title = title;
|
||||
strPickerView.dataSourceArr = dataSourceArr;
|
||||
strPickerView.selectIndex = selectIndex;
|
||||
strPickerView.isAutoSelect = isAutoSelect;
|
||||
strPickerView.resultModelBlock = resultBlock;
|
||||
|
||||
NSAssert(strPickerView->_isDataSourceValid, @"数据源不合法!请检查字符串选择器数据源的格式");
|
||||
if (strPickerView->_isDataSourceValid) {
|
||||
// 显示
|
||||
[strPickerView show];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 3.显示【多列】字符串选择器
|
||||
+ (void)showMultiPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
resultBlock:(BRStringResultModelArrayBlock)resultBlock {
|
||||
[self showMultiPickerWithTitle:title dataSourceArr:dataSourceArr selectIndexs:selectIndexs isAutoSelect:NO resultBlock:resultBlock];
|
||||
}
|
||||
|
||||
#pragma mark - 4.显示【多列】字符串选择器
|
||||
+ (void)showMultiPickerWithTitle:(NSString *)title
|
||||
dataSourceArr:(NSArray *)dataSourceArr
|
||||
selectIndexs:(NSArray <NSNumber *>*)selectIndexs
|
||||
isAutoSelect:(BOOL)isAutoSelect
|
||||
resultBlock:(BRStringResultModelArrayBlock)resultBlock {
|
||||
// 创建字符串选择器
|
||||
BRStringPickerView *strPickerView = [[BRStringPickerView alloc]init];
|
||||
strPickerView.pickerMode = BRStringPickerComponentMulti;
|
||||
strPickerView.title = title;
|
||||
strPickerView.dataSourceArr = dataSourceArr;
|
||||
strPickerView.selectIndexs = selectIndexs;
|
||||
strPickerView.isAutoSelect = isAutoSelect;
|
||||
strPickerView.resultModelArrayBlock = resultBlock;
|
||||
|
||||
NSAssert(strPickerView->_isDataSourceValid, @"数据源不合法!请检查字符串选择器数据源的格式");
|
||||
if (strPickerView->_isDataSourceValid) {
|
||||
// 显示
|
||||
[strPickerView show];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 初始化自定义字符串选择器
|
||||
- (instancetype)initWithPickerMode:(BRStringPickerMode)pickerMode {
|
||||
if (self = [super init]) {
|
||||
self.pickerMode = pickerMode;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - 设置默认选择的值
|
||||
- (void)handlerDefaultSelectValue {
|
||||
_isDataSourceValid = YES;
|
||||
if (self.dataSourceArr.count == 0) {
|
||||
_isDataSourceValid = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.pickerMode == BRStringPickerComponentSingle) {
|
||||
id element = [self.dataSourceArr firstObject];
|
||||
if ([element isKindOfClass:[NSArray class]]) {
|
||||
_isDataSourceValid = NO;
|
||||
return;
|
||||
}
|
||||
} else if (self.pickerMode == BRStringPickerComponentMulti) {
|
||||
id element = [self.dataSourceArr firstObject];
|
||||
if ([element isKindOfClass:[NSString class]]) {
|
||||
_isDataSourceValid = NO;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 给选择器设置默认值
|
||||
if (self.pickerMode == BRStringPickerComponentSingle) {
|
||||
if (self.selectIndex > 0) {
|
||||
self.selectIndex = (self.selectIndex < self.dataSourceArr.count ? self.selectIndex : 0);
|
||||
} else {
|
||||
if (self.mSelectValue && [self.dataSourceArr containsObject:self.mSelectValue]) {
|
||||
self.selectIndex = [self.dataSourceArr indexOfObject:self.mSelectValue];
|
||||
} else {
|
||||
self.selectIndex = 0;
|
||||
}
|
||||
}
|
||||
[self.pickerView selectRow:self.selectIndex inComponent:0 animated:NO];
|
||||
|
||||
} else if (self.pickerMode == BRStringPickerComponentMulti) {
|
||||
NSMutableArray *mSelectIndexs = [[NSMutableArray alloc]init];
|
||||
for (NSInteger i = 0; i < self.dataSourceArr.count; i++) {
|
||||
NSInteger row = 0;
|
||||
if (self.selectIndexs.count > 0) {
|
||||
if (i < self.selectIndexs.count) {
|
||||
NSInteger index = [self.selectIndexs[i] integerValue];
|
||||
row = ((index > 0 && index < [self.dataSourceArr[i] count]) ? index : 0);
|
||||
}
|
||||
} else {
|
||||
if (self.mSelectValues.count > 0 && i < self.mSelectValues.count) {
|
||||
NSString *value = self.mSelectValues[i];
|
||||
if ([self.dataSourceArr[i] containsObject:value]) {
|
||||
row = [self.dataSourceArr[i] indexOfObject:value];
|
||||
}
|
||||
}
|
||||
}
|
||||
[self.pickerView selectRow:row inComponent:i animated:NO];
|
||||
[mSelectIndexs addObject:@(row)];
|
||||
}
|
||||
|
||||
self.selectIndexs = [mSelectIndexs copy];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 字符串选择器
|
||||
- (UIPickerView *)pickerView {
|
||||
if (!_pickerView) {
|
||||
_pickerView = [[UIPickerView alloc]initWithFrame:CGRectMake(0, self.pickerStyle.titleBarHeight, SCREEN_WIDTH, self.pickerStyle.pickerHeight)];
|
||||
_pickerView.backgroundColor = self.pickerStyle.pickerColor;
|
||||
_pickerView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
|
||||
_pickerView.dataSource = self;
|
||||
_pickerView.delegate = self;
|
||||
_pickerView.showsSelectionIndicator = YES;
|
||||
}
|
||||
return _pickerView;
|
||||
}
|
||||
|
||||
#pragma mark - UIPickerViewDataSource
|
||||
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
|
||||
switch (self.pickerMode) {
|
||||
case BRStringPickerComponentSingle:
|
||||
return 1;
|
||||
break;
|
||||
case BRStringPickerComponentMulti:
|
||||
return self.dataSourceArr.count;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
|
||||
switch (self.pickerMode) {
|
||||
case BRStringPickerComponentSingle:
|
||||
return self.dataSourceArr.count;
|
||||
break;
|
||||
case BRStringPickerComponentMulti:
|
||||
return [self.dataSourceArr[component] count];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIPickerViewDelegate
|
||||
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
|
||||
switch (self.pickerMode) {
|
||||
case BRStringPickerComponentSingle:
|
||||
{
|
||||
self.selectIndex = row;
|
||||
|
||||
// 滚动选择时执行 changeModelBlock
|
||||
if (self.changeModelBlock) {
|
||||
self.changeModelBlock([self getResultModel]);
|
||||
}
|
||||
|
||||
// 设置自动选择时,滚动选择时就执行 resultModelBlock
|
||||
if (self.isAutoSelect) {
|
||||
if (self.resultModelBlock) {
|
||||
self.resultModelBlock([self getResultModel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BRStringPickerComponentMulti:
|
||||
{
|
||||
if (component < self.selectIndexs.count) {
|
||||
NSMutableArray *mutableArr = [self.selectIndexs mutableCopy];
|
||||
[mutableArr replaceObjectAtIndex:component withObject:@(row)];
|
||||
self.selectIndexs = [mutableArr copy];
|
||||
}
|
||||
|
||||
// 滚动选择时执行 changeModelArrayBlock
|
||||
if (self.changeModelArrayBlock) {
|
||||
self.changeModelArrayBlock([self getResultModelArr]);
|
||||
}
|
||||
|
||||
// 设置自动选择时,滚动选择时就执行 resultModelArrayBlock
|
||||
if (self.isAutoSelect) {
|
||||
if (self.resultModelArrayBlock) {
|
||||
self.resultModelArrayBlock([self getResultModelArr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 获取【单列】选择器选择的值
|
||||
- (BRResultModel *)getResultModel {
|
||||
BRResultModel *resultModel = [[BRResultModel alloc]init];
|
||||
resultModel.index = self.selectIndex;
|
||||
resultModel.name = self.selectIndex < self.dataSourceArr.count ? self.dataSourceArr[self.selectIndex] : nil;
|
||||
return resultModel;
|
||||
}
|
||||
|
||||
#pragma mark - 获取【多列】选择器选择的值
|
||||
- (NSArray *)getResultModelArr {
|
||||
NSMutableArray *resultModelArr = [[NSMutableArray alloc]init];
|
||||
for (NSInteger i = 0; i < self.selectIndexs.count; i++) {
|
||||
NSInteger index = [self.selectIndexs[i] integerValue];
|
||||
NSArray *dataArr = self.dataSourceArr[i];
|
||||
BRResultModel *resultModel = [[BRResultModel alloc]init];
|
||||
resultModel.index = index;
|
||||
resultModel.name = index < dataArr.count ? dataArr[index] : nil;
|
||||
[resultModelArr addObject:resultModel];
|
||||
}
|
||||
return [resultModelArr copy];
|
||||
}
|
||||
|
||||
// 自定义 pickerView 的 label
|
||||
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(nullable UIView *)view {
|
||||
|
||||
// 设置分割线的颜色
|
||||
for (UIView *subView in pickerView.subviews) {
|
||||
if (subView && [subView isKindOfClass:[UIView class]] && subView.frame.size.height <= 1) {
|
||||
subView.backgroundColor = self.pickerStyle.separatorColor;
|
||||
}
|
||||
}
|
||||
// 设置选中行的背景颜色
|
||||
NSArray *subviews = pickerView.subviews;
|
||||
if (subviews.count > 0) {
|
||||
NSArray *coloms = subviews.firstObject;
|
||||
if (coloms) {
|
||||
NSArray *subviewCache = [coloms valueForKey:@"subviewCache"];
|
||||
if (subviewCache.count > 0) {
|
||||
UIView *middleContainerView = [subviewCache.firstObject valueForKey:@"middleContainerView"];
|
||||
if (middleContainerView) {
|
||||
middleContainerView.backgroundColor = self.pickerStyle.selectedColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UILabel *label = (UILabel *)view;
|
||||
if (!label) {
|
||||
label = [[UILabel alloc]init];
|
||||
label.backgroundColor = [UIColor clearColor];
|
||||
label.textAlignment = NSTextAlignmentCenter;
|
||||
label.textColor = self.pickerStyle.pickerTextColor;
|
||||
label.font = self.pickerStyle.pickerTextFont;
|
||||
// 字体自适应属性
|
||||
label.adjustsFontSizeToFitWidth = YES;
|
||||
// 自适应最小字体缩放比例
|
||||
label.minimumScaleFactor = 0.5f;
|
||||
}
|
||||
if (self.pickerMode == BRStringPickerComponentSingle) {
|
||||
label.frame = CGRectMake(0, 0, self.pickerView.frame.size.width, self.pickerStyle.rowHeight);
|
||||
label.text = self.dataSourceArr[row];
|
||||
} else if (self.pickerMode == BRStringPickerComponentMulti) {
|
||||
label.frame = CGRectMake(0, 0, self.pickerView.frame.size.width / pickerView.numberOfComponents, self.pickerStyle.rowHeight);
|
||||
label.text = self.dataSourceArr[component][row];
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
// 设置行高
|
||||
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component {
|
||||
return self.pickerStyle.rowHeight;
|
||||
}
|
||||
|
||||
#pragma mark - 重写父类方法
|
||||
- (void)addPickerToView:(UIView *)view {
|
||||
[self handlerDefaultSelectValue];
|
||||
|
||||
NSAssert(_isDataSourceValid, @"数据源不合法!请检查字符串选择器数据源的格式");
|
||||
|
||||
if (_isDataSourceValid) {
|
||||
// 添加字符串选择器
|
||||
if (view) {
|
||||
// 立即刷新容器视图 view 的布局(防止 view 使用自动布局时,选择器视图无法正常显示)
|
||||
[view setNeedsLayout];
|
||||
[view layoutIfNeeded];
|
||||
|
||||
self.frame = view.bounds;
|
||||
self.pickerView.frame = view.bounds;
|
||||
[self addSubview:self.pickerView];
|
||||
} else {
|
||||
[self.alertView addSubview:self.pickerView];
|
||||
}
|
||||
}
|
||||
|
||||
@weakify(self)
|
||||
self.doneBlock = ^{
|
||||
@strongify(self)
|
||||
// 点击确定按钮后,执行block回调
|
||||
[self removePickerFromView:view];
|
||||
|
||||
if (self.pickerMode == BRStringPickerComponentSingle) {
|
||||
if (self.resultModelBlock) {
|
||||
self.resultModelBlock([self getResultModel]);
|
||||
}
|
||||
} else if (self.pickerMode == BRStringPickerComponentMulti) {
|
||||
if (self.resultModelArrayBlock) {
|
||||
self.resultModelArrayBlock([self getResultModelArr]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[super addPickerToView:view];
|
||||
}
|
||||
|
||||
#pragma mark - 重写父类方法
|
||||
- (void)addSubViewToPicker:(UIView *)customView {
|
||||
[self.pickerView addSubview:customView];
|
||||
}
|
||||
|
||||
#pragma mark - 弹出选择器视图
|
||||
- (void)show {
|
||||
[self addPickerToView:nil];
|
||||
}
|
||||
|
||||
#pragma mark - 关闭选择器视图
|
||||
- (void)dismiss {
|
||||
[self removePickerFromView:nil];
|
||||
}
|
||||
|
||||
#pragma mark - setter 方法
|
||||
- (void)setPickerMode:(BRStringPickerMode)pickerMode {
|
||||
_pickerMode = pickerMode;
|
||||
if (_pickerView) {
|
||||
[self handlerDefaultSelectValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPlistName:(NSString *)plistName {
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource:plistName ofType:nil];
|
||||
if (path && path.length > 0) {
|
||||
self.dataSourceArr = [[NSArray alloc] initWithContentsOfFile:path];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSelectValue:(NSString *)selectValue {
|
||||
self.mSelectValue = selectValue;
|
||||
}
|
||||
|
||||
- (void)setSelectValues:(NSArray<NSString *> *)selectValues {
|
||||
self.mSelectValues = selectValues;
|
||||
}
|
||||
|
||||
#pragma mark - getter 方法
|
||||
- (NSArray *)dataSourceArr {
|
||||
if (!_dataSourceArr) {
|
||||
_dataSourceArr = [NSArray array];
|
||||
}
|
||||
return _dataSourceArr;
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)selectIndexs {
|
||||
if (!_selectIndexs) {
|
||||
_selectIndexs = [NSArray array];
|
||||
}
|
||||
return _selectIndexs;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)mSelectValues {
|
||||
if (!_mSelectValues) {
|
||||
_mSelectValues = [NSArray array];
|
||||
}
|
||||
return _mSelectValues;
|
||||
}
|
||||
|
||||
@end
|
||||
24
SweetParty/第三方库/HXTagsView/HXTagAttribute.h
Executable file
24
SweetParty/第三方库/HXTagsView/HXTagAttribute.h
Executable file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// HXTagAttribute.h
|
||||
// HXTagsView https://github.com/huangxuan518/HXTagsView
|
||||
// 博客地址 http://blog.libuqing.com/
|
||||
// Created by Love on 16/6/30.
|
||||
// Copyright © 2016年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface HXTagAttribute : NSObject
|
||||
|
||||
@property (nonatomic,assign) CGFloat borderWidth;//标签边框宽度
|
||||
@property (nonatomic,strong) UIColor *borderColor;//标签边框颜色
|
||||
@property (nonatomic,assign) CGFloat cornerRadius;//标签圆角大小
|
||||
@property (nonatomic,strong) UIColor *normalBackgroundColor;//标签默认背景颜色
|
||||
@property (nonatomic,strong) UIColor *selectedBackgroundColor;//标签选中背景颜色
|
||||
@property (nonatomic,assign) CGFloat titleSize;//标签字体大小
|
||||
@property (nonatomic,strong) UIColor *textColor;//标签字体颜色
|
||||
@property (nonatomic,strong) UIColor *keyColor;//搜索关键词颜色
|
||||
@property (nonatomic,assign) CGFloat tagSpace;//标签内部左右间距(标题距离边框2边的距离和)
|
||||
|
||||
@end
|
||||
27
SweetParty/第三方库/HXTagsView/HXTagAttribute.m
Executable file
27
SweetParty/第三方库/HXTagsView/HXTagAttribute.m
Executable file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// HXTagAttribute.m
|
||||
// HXTagsView https://github.com/huangxuan518/HXTagsView
|
||||
// 博客地址 http://blog.libuqing.com/
|
||||
// Created by Love on 16/6/30.
|
||||
// Copyright © 2016年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HXTagAttribute.h"
|
||||
|
||||
@implementation HXTagAttribute
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_cornerRadius = 15.0;
|
||||
_normalBackgroundColor = [HEXCOLOR(0xF5F5F5) colorWithAlphaComponent:0.1];
|
||||
_selectedBackgroundColor = HEXCOLOR(0xB651F4);
|
||||
_titleSize = 12;
|
||||
_textColor = HEXCOLOR(0xFFFFFF);
|
||||
_tagSpace = 28;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
15
SweetParty/第三方库/HXTagsView/HXTagCollectionViewCell.h
Executable file
15
SweetParty/第三方库/HXTagsView/HXTagCollectionViewCell.h
Executable file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// HXTagCollectionViewCell.h
|
||||
// 黄轩 https://github.com/huangxuan518
|
||||
//
|
||||
// Created by 黄轩 on 16/1/13.
|
||||
// Copyright © 2015年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface HXTagCollectionViewCell : UICollectionViewCell
|
||||
|
||||
@property (nonatomic,strong) UILabel *titleLabel;
|
||||
|
||||
@end
|
||||
38
SweetParty/第三方库/HXTagsView/HXTagCollectionViewCell.m
Executable file
38
SweetParty/第三方库/HXTagsView/HXTagCollectionViewCell.m
Executable file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// HXTagCollectionViewCell.m
|
||||
// 黄轩 https://github.com/huangxuan518
|
||||
//
|
||||
// Created by 黄轩 on 16/1/13.
|
||||
// Copyright © 2015年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HXTagCollectionViewCell.h"
|
||||
|
||||
@implementation HXTagCollectionViewCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_titleLabel = [[UILabel alloc] initWithFrame:self.bounds];
|
||||
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self.contentView addSubview:_titleLabel];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
self.titleLabel.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse
|
||||
{
|
||||
[super prepareForReuse];
|
||||
|
||||
self.titleLabel.text = @"";
|
||||
}
|
||||
|
||||
@end
|
||||
13
SweetParty/第三方库/HXTagsView/HXTagCollectionViewFlowLayout.h
Executable file
13
SweetParty/第三方库/HXTagsView/HXTagCollectionViewFlowLayout.h
Executable file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// HXTagCollectionViewFlowLayout.h
|
||||
// 黄轩 https://github.com/huangxuan518
|
||||
//
|
||||
// Created by 黄轩 on 16/1/13.
|
||||
// Copyright © 2015年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface HXTagCollectionViewFlowLayout : UICollectionViewFlowLayout
|
||||
|
||||
@end
|
||||
136
SweetParty/第三方库/HXTagsView/HXTagCollectionViewFlowLayout.m
Executable file
136
SweetParty/第三方库/HXTagsView/HXTagCollectionViewFlowLayout.m
Executable file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// HXTagCollectionViewFlowLayout.m
|
||||
// 黄轩 https://github.com/huangxuan518
|
||||
//
|
||||
// Created by 黄轩 on 16/1/13.
|
||||
// Copyright © 2015年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HXTagCollectionViewFlowLayout.h"
|
||||
|
||||
@interface HXTagCollectionViewFlowLayout()
|
||||
|
||||
@property (nonatomic, weak) id<UICollectionViewDelegateFlowLayout> delegate;
|
||||
@property (nonatomic, strong) NSMutableArray *itemAttributes;
|
||||
@property (nonatomic, assign) CGFloat contentWidth;//滑动宽度 水平
|
||||
@property (nonatomic, assign) CGFloat contentHeight;//滑动高度 垂直
|
||||
|
||||
@end
|
||||
|
||||
@implementation HXTagCollectionViewFlowLayout
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
self.itemSize = CGSizeMake(50.0f, 30.0f);
|
||||
self.minimumInteritemSpacing = 15.0f;
|
||||
self.minimumLineSpacing = 15.0f;
|
||||
self.sectionInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)prepareLayout
|
||||
{
|
||||
[super prepareLayout];
|
||||
|
||||
[self.itemAttributes removeAllObjects];
|
||||
|
||||
//滑动的宽度 = 左边
|
||||
self.contentWidth = self.sectionInset.left;
|
||||
|
||||
//cell的高度 = 顶部 + 高度
|
||||
self.contentHeight = self.sectionInset.top + self.itemSize.height;
|
||||
|
||||
CGFloat originX = self.sectionInset.left;
|
||||
CGFloat originY = self.sectionInset.top;
|
||||
|
||||
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
|
||||
|
||||
for (NSInteger i = 0; i < itemCount; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
|
||||
CGSize itemSize = [self itemSizeForIndexPath:indexPath];
|
||||
|
||||
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
|
||||
//垂直滚动
|
||||
//当前CollectionViewCell的起点 + 当前CollectionViewCell的宽度 + 当前CollectionView距离右侧的间隔 > collectionView的宽度
|
||||
if ((originX + itemSize.width + self.sectionInset.right) > self.collectionView.frame.size.width) {
|
||||
originX = self.sectionInset.left;
|
||||
originY += itemSize.height + self.minimumLineSpacing;
|
||||
|
||||
self.contentHeight += itemSize.height + self.minimumLineSpacing;
|
||||
}
|
||||
} else {
|
||||
self.contentWidth += itemSize.width + self.minimumInteritemSpacing;
|
||||
}
|
||||
|
||||
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
|
||||
attributes.frame = CGRectMake(originX, originY, itemSize.width, itemSize.height);
|
||||
[self.itemAttributes addObject:attributes];
|
||||
|
||||
originX += itemSize.width + self.minimumInteritemSpacing;
|
||||
}
|
||||
|
||||
self.contentHeight += self.sectionInset.bottom;
|
||||
}
|
||||
|
||||
- (CGSize)collectionViewContentSize
|
||||
{
|
||||
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
|
||||
return CGSizeMake(self.collectionView.frame.size.width, self.contentHeight);
|
||||
} else {
|
||||
return CGSizeMake(self.contentWidth,self.collectionView.frame.size.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
|
||||
{
|
||||
return self.itemAttributes;
|
||||
}
|
||||
|
||||
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
|
||||
{
|
||||
CGRect oldBounds = self.collectionView.bounds;
|
||||
|
||||
if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (id<UICollectionViewDelegateFlowLayout>)delegate
|
||||
{
|
||||
if (_delegate == nil) {
|
||||
_delegate = (id<UICollectionViewDelegateFlowLayout>)self.collectionView.delegate;
|
||||
}
|
||||
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
- (CGSize)itemSizeForIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) {
|
||||
self.itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
return self.itemSize;
|
||||
}
|
||||
|
||||
#pragma mark - 懒加载
|
||||
|
||||
//UICollectionViewLayoutAttributes数组
|
||||
- (NSMutableArray *)itemAttributes {
|
||||
if (!_itemAttributes) {
|
||||
_itemAttributes = [NSMutableArray array];
|
||||
}
|
||||
return _itemAttributes;
|
||||
}
|
||||
|
||||
@end
|
||||
38
SweetParty/第三方库/HXTagsView/HXTagsCell.h
Executable file
38
SweetParty/第三方库/HXTagsView/HXTagsCell.h
Executable file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// HXTagsCell.h
|
||||
// HXTagsView https://github.com/huangxuan518/HXTagsView
|
||||
// 博客地址 http://blog.libuqing.com/
|
||||
// Created by Love on 16/6/30.
|
||||
// Copyright © 2016年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "HXTagCollectionViewFlowLayout.h"
|
||||
|
||||
@class HXTagAttribute;
|
||||
|
||||
@interface HXTagsCell : UITableViewCell
|
||||
|
||||
@property (nonatomic,strong) NSArray *tags;//传入的标签数组 字符串数组
|
||||
@property (nonatomic,strong) NSMutableArray *selectedTags; //选择的标签数组
|
||||
@property (nonatomic,strong) HXTagCollectionViewFlowLayout *layout;//布局layout
|
||||
@property (nonatomic,strong) HXTagAttribute *tagAttribute;//按钮样式对象
|
||||
@property (nonatomic,assign) BOOL isMultiSelect;//是否可以多选 默认:NO 单选
|
||||
@property (nonatomic,copy) NSString *key;//搜索关键词
|
||||
|
||||
@property (nonatomic,copy) void (^completion)(NSArray *selectTags,NSInteger currentIndex);//选中的标签数组,当前点击的index
|
||||
|
||||
//刷新界面
|
||||
- (void)reloadData;
|
||||
|
||||
/**
|
||||
* 计算Cell的高度
|
||||
*
|
||||
* @param tags 标签数组
|
||||
* @param layout 布局样式 默认则传nil
|
||||
* @param tagAttribute 标签样式 默认传nil 涉及到计算的主要是titleSize
|
||||
* @param width 计算的最大范围
|
||||
*/
|
||||
+ (CGFloat)getCellHeightWithTags:(NSArray *)tags layout:(HXTagCollectionViewFlowLayout *)layout tagAttribute:(HXTagAttribute *)tagAttribute width:(CGFloat)width;
|
||||
|
||||
@end
|
||||
226
SweetParty/第三方库/HXTagsView/HXTagsCell.m
Executable file
226
SweetParty/第三方库/HXTagsView/HXTagsCell.m
Executable file
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// HXTagsCell.m
|
||||
// HXTagsView https://github.com/huangxuan518/HXTagsView
|
||||
// 博客地址 http://blog.libuqing.com/
|
||||
// Created by Love on 16/6/30.
|
||||
// Copyright © 2016年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HXTagsCell.h"
|
||||
#import "HXTagCollectionViewCell.h"
|
||||
#import "HXTagAttribute.h"
|
||||
|
||||
@interface HXTagsCell () <UICollectionViewDelegate, UICollectionViewDataSource>
|
||||
|
||||
@property (nonatomic,strong) UICollectionView *collectionView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HXTagsCell
|
||||
|
||||
static NSString * const reuseIdentifier = @"HXTagCollectionViewCellId";
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
[self setup];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
if (self = [super initWithCoder:aDecoder]) {
|
||||
[self setup];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setup
|
||||
{
|
||||
//初始化样式
|
||||
_tagAttribute = [HXTagAttribute new];
|
||||
|
||||
_layout = [[HXTagCollectionViewFlowLayout alloc] init];
|
||||
[self addSubview:self.collectionView];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegate | UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return _tags.count;
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
HXTagCollectionViewFlowLayout *layout = (HXTagCollectionViewFlowLayout *)collectionView.collectionViewLayout;
|
||||
CGSize maxSize = CGSizeMake(collectionView.frame.size.width - layout.sectionInset.left - layout.sectionInset.right, layout.itemSize.height);
|
||||
|
||||
CGRect frame = [_tags[indexPath.item] boundingRectWithSize:maxSize options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:_tagAttribute.titleSize]} context:nil];
|
||||
|
||||
return CGSizeMake(frame.size.width + _tagAttribute.tagSpace, layout.itemSize.height);
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
HXTagCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
|
||||
cell.backgroundColor = _tagAttribute.normalBackgroundColor;
|
||||
cell.layer.borderColor = _tagAttribute.borderColor.CGColor;
|
||||
cell.layer.cornerRadius = _tagAttribute.cornerRadius;
|
||||
cell.layer.borderWidth = _tagAttribute.borderWidth;
|
||||
cell.titleLabel.textColor = _tagAttribute.textColor;
|
||||
cell.titleLabel.font = [UIFont systemFontOfSize:_tagAttribute.titleSize];
|
||||
|
||||
NSString *title = self.tags[indexPath.item];
|
||||
if (_key.length > 0) {
|
||||
cell.titleLabel.attributedText = [self searchTitle:title key:_key keyColor:_tagAttribute.keyColor];
|
||||
} else {
|
||||
cell.titleLabel.text = title;
|
||||
}
|
||||
|
||||
if ([self.selectedTags containsObject:self.tags[indexPath.item]]) {
|
||||
cell.backgroundColor = _tagAttribute.selectedBackgroundColor;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
HXTagCollectionViewCell *cell = (HXTagCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
||||
|
||||
if ([self.selectedTags containsObject:self.tags[indexPath.item]]) {
|
||||
cell.backgroundColor = _tagAttribute.normalBackgroundColor;
|
||||
[self.selectedTags removeObject:self.tags[indexPath.item]];
|
||||
}
|
||||
else {
|
||||
if (_isMultiSelect) {
|
||||
cell.backgroundColor = _tagAttribute.selectedBackgroundColor;
|
||||
[self.selectedTags addObject:self.tags[indexPath.item]];
|
||||
} else {
|
||||
[self.selectedTags removeAllObjects];
|
||||
[self.selectedTags addObject:self.tags[indexPath.item]];
|
||||
|
||||
[self reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
if (_completion) {
|
||||
_completion(self.selectedTags,indexPath.item);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置文字中关键字高亮
|
||||
- (NSMutableAttributedString *)searchTitle:(NSString *)title key:(NSString *)key keyColor:(UIColor *)keyColor {
|
||||
|
||||
NSMutableAttributedString *titleStr = [[NSMutableAttributedString alloc] initWithString:title];
|
||||
NSString *copyStr = title;
|
||||
|
||||
NSMutableString *xxstr = [NSMutableString new];
|
||||
for (int i = 0; i < key.length; i++) {
|
||||
[xxstr appendString:@"*"];
|
||||
}
|
||||
|
||||
while ([copyStr rangeOfString:key options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
|
||||
NSRange range = [copyStr rangeOfString:key options:NSCaseInsensitiveSearch];
|
||||
|
||||
[titleStr addAttribute:NSForegroundColorAttributeName value:keyColor range:range];
|
||||
copyStr = [copyStr stringByReplacingCharactersInRange:NSMakeRange(range.location, range.length) withString:xxstr];
|
||||
}
|
||||
return titleStr;
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_collectionView.frame = self.bounds;
|
||||
}
|
||||
|
||||
#pragma mark - 懒加载
|
||||
|
||||
- (NSMutableArray *)selectedTags
|
||||
{
|
||||
if (!_selectedTags) {
|
||||
_selectedTags = [NSMutableArray array];
|
||||
}
|
||||
return _selectedTags;
|
||||
}
|
||||
|
||||
- (UICollectionView *)collectionView {
|
||||
if (!_collectionView) {
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 362) collectionViewLayout:_layout];
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.backgroundColor = [UIColor colorWithRed:241/255.0 green:241/255.0 blue:241/255.0 alpha:1.0];
|
||||
[_collectionView registerClass:[HXTagCollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
|
||||
}
|
||||
|
||||
_collectionView.collectionViewLayout = _layout;
|
||||
|
||||
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
|
||||
//垂直
|
||||
_collectionView.showsVerticalScrollIndicator = YES;
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
} else {
|
||||
_collectionView.showsHorizontalScrollIndicator = YES;
|
||||
_collectionView.showsVerticalScrollIndicator = NO;
|
||||
}
|
||||
|
||||
_collectionView.frame = self.bounds;
|
||||
|
||||
return _collectionView;
|
||||
}
|
||||
|
||||
+ (CGFloat)getCellHeightWithTags:(NSArray *)tags layout:(HXTagCollectionViewFlowLayout *)layout tagAttribute:(HXTagAttribute *)tagAttribute width:(CGFloat)width
|
||||
{
|
||||
CGFloat contentHeight = 0;
|
||||
|
||||
if (!layout) {
|
||||
layout = [[HXTagCollectionViewFlowLayout alloc] init];
|
||||
}
|
||||
|
||||
if (tagAttribute.titleSize <= 0) {
|
||||
tagAttribute = [[HXTagAttribute alloc] init];
|
||||
}
|
||||
|
||||
//cell的高度 = 顶部 + 高度
|
||||
contentHeight = layout.sectionInset.top + layout.itemSize.height;
|
||||
|
||||
CGFloat originX = layout.sectionInset.left;
|
||||
CGFloat originY = layout.sectionInset.top;
|
||||
|
||||
NSInteger itemCount = tags.count;
|
||||
|
||||
for (NSInteger i = 0; i < itemCount; i++) {
|
||||
CGSize maxSize = CGSizeMake(width - layout.sectionInset.left - layout.sectionInset.right, layout.itemSize.height);
|
||||
|
||||
CGRect frame = [tags[i] boundingRectWithSize:maxSize options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:tagAttribute.titleSize]} context:nil];
|
||||
|
||||
CGSize itemSize = CGSizeMake(frame.size.width + tagAttribute.tagSpace, layout.itemSize.height);
|
||||
|
||||
if (layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
|
||||
//垂直滚动
|
||||
//当前CollectionViewCell的起点 + 当前CollectionViewCell的宽度 + 当前CollectionView距离右侧的间隔 > collectionView的宽度
|
||||
if ((originX + itemSize.width + layout.sectionInset.right) > width) {
|
||||
originX = layout.sectionInset.left;
|
||||
originY += itemSize.height + layout.minimumLineSpacing;
|
||||
|
||||
contentHeight += itemSize.height + layout.minimumLineSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
originX += itemSize.width + layout.minimumInteritemSpacing;
|
||||
}
|
||||
|
||||
contentHeight += layout.sectionInset.bottom;
|
||||
return contentHeight;
|
||||
}
|
||||
|
||||
@end
|
||||
39
SweetParty/第三方库/HXTagsView/HXTagsView.h
Executable file
39
SweetParty/第三方库/HXTagsView/HXTagsView.h
Executable file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// HXTagsView.h
|
||||
// 黄轩 https://github.com/huangxuan518
|
||||
//
|
||||
// Created by 黄轩 on 16/1/13.
|
||||
// Copyright © 2015年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "HXTagCollectionViewFlowLayout.h"
|
||||
|
||||
@class HXTagAttribute;
|
||||
|
||||
@interface HXTagsView : UIView
|
||||
|
||||
@property (nonatomic,strong) NSArray *tags;//传入的标签数组 字符串数组
|
||||
@property (nonatomic,strong) HXTagCollectionViewFlowLayout *layout;//布局layout
|
||||
@property (nonatomic,strong) HXTagAttribute *tagAttribute;//按钮样式对象
|
||||
@property (nonatomic,assign) BOOL isMultiSelect;//是否可以多选 默认:NO 单选
|
||||
@property (nonatomic,copy) NSString *key;//搜索关键词
|
||||
|
||||
@property (nonatomic,copy) void (^completion)(NSArray *selectTags,NSInteger currentIndex);//选中的标签数组,当前点击的index
|
||||
|
||||
@property (nonatomic, strong) NSArray *originalTags;
|
||||
|
||||
//刷新界面
|
||||
- (void)reloadData;
|
||||
|
||||
/**
|
||||
* 计算Cell的高度
|
||||
*
|
||||
* @param tags 标签数组
|
||||
* @param layout 布局样式 默认则传nil
|
||||
* @param tagAttribute 标签样式 默认传nil 涉及到计算的主要是titleSize
|
||||
* @param width 计算的最大范围
|
||||
*/
|
||||
+ (CGFloat)getHeightWithTags:(NSArray *)tags layout:(HXTagCollectionViewFlowLayout *)layout tagAttribute:(HXTagAttribute *)tagAttribute width:(CGFloat)width;
|
||||
|
||||
@end
|
||||
235
SweetParty/第三方库/HXTagsView/HXTagsView.m
Executable file
235
SweetParty/第三方库/HXTagsView/HXTagsView.m
Executable file
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// HXTagsView.m
|
||||
// 黄轩 https://github.com/huangxuan518
|
||||
//
|
||||
// Created by 黄轩 on 16/1/13.
|
||||
// Copyright © 2015年 IT小子. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HXTagsView.h"
|
||||
#import "HXTagCollectionViewCell.h"
|
||||
#import "HXTagAttribute.h"
|
||||
|
||||
@interface HXTagsView () <UICollectionViewDelegate, UICollectionViewDataSource>
|
||||
|
||||
@property (nonatomic,strong) NSMutableArray *selectedTags;
|
||||
@property (nonatomic,strong) UICollectionView *collectionView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HXTagsView
|
||||
|
||||
static NSString * const reuseIdentifier = @"HXTagCollectionViewCellId";
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self setup];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
if (self = [super initWithCoder:aDecoder]) {
|
||||
[self setup];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setup
|
||||
{
|
||||
//初始化样式
|
||||
_tagAttribute = [HXTagAttribute new];
|
||||
|
||||
_layout = [[HXTagCollectionViewFlowLayout alloc] init];
|
||||
[self addSubview:self.collectionView];
|
||||
}
|
||||
|
||||
- (void)setOriginalTags:(NSArray *)originalTags {
|
||||
_originalTags = originalTags;
|
||||
|
||||
[self.selectedTags removeAllObjects];
|
||||
[self.selectedTags addObjectsFromArray:originalTags];
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegate | UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
|
||||
{
|
||||
return _tags.count;
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
HXTagCollectionViewFlowLayout *layout = (HXTagCollectionViewFlowLayout *)collectionView.collectionViewLayout;
|
||||
CGSize maxSize = CGSizeMake(collectionView.frame.size.width - layout.sectionInset.left - layout.sectionInset.right, layout.itemSize.height);
|
||||
|
||||
CGRect frame = [_tags[indexPath.item] boundingRectWithSize:maxSize options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:_tagAttribute.titleSize]} context:nil];
|
||||
|
||||
return CGSizeMake(frame.size.width + _tagAttribute.tagSpace, layout.itemSize.height);
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
HXTagCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
|
||||
cell.backgroundColor = _tagAttribute.normalBackgroundColor;
|
||||
cell.layer.borderColor = _tagAttribute.borderColor.CGColor;
|
||||
cell.layer.cornerRadius = _tagAttribute.cornerRadius;
|
||||
cell.layer.borderWidth = _tagAttribute.borderWidth;
|
||||
cell.titleLabel.textColor = _tagAttribute.textColor;
|
||||
cell.titleLabel.font = [UIFont systemFontOfSize:_tagAttribute.titleSize];
|
||||
|
||||
NSString *title = self.tags[indexPath.item];
|
||||
if (_key.length > 0) {
|
||||
cell.titleLabel.attributedText = [self searchTitle:title key:_key keyColor:_tagAttribute.keyColor];
|
||||
} else {
|
||||
cell.titleLabel.text = title;
|
||||
}
|
||||
|
||||
if ([self.selectedTags containsObject:self.tags[indexPath.item]]) {
|
||||
cell.backgroundColor = _tagAttribute.selectedBackgroundColor;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
HXTagCollectionViewCell *cell = (HXTagCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
||||
|
||||
if ([self.selectedTags containsObject:self.tags[indexPath.item]]) {
|
||||
cell.backgroundColor = _tagAttribute.normalBackgroundColor;
|
||||
[self.selectedTags removeObject:self.tags[indexPath.item]];
|
||||
}
|
||||
else {
|
||||
if (_isMultiSelect) {
|
||||
cell.backgroundColor = _tagAttribute.selectedBackgroundColor;
|
||||
[self.selectedTags addObject:self.tags[indexPath.item]];
|
||||
} else {
|
||||
[self.selectedTags removeAllObjects];
|
||||
[self.selectedTags addObject:self.tags[indexPath.item]];
|
||||
|
||||
[self reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
if (_completion) {
|
||||
_completion(self.selectedTags,indexPath.item);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置文字中关键字高亮
|
||||
- (NSMutableAttributedString *)searchTitle:(NSString *)title key:(NSString *)key keyColor:(UIColor *)keyColor {
|
||||
|
||||
NSMutableAttributedString *titleStr = [[NSMutableAttributedString alloc] initWithString:title];
|
||||
NSString *copyStr = title;
|
||||
|
||||
NSMutableString *xxstr = [NSMutableString new];
|
||||
for (int i = 0; i < key.length; i++) {
|
||||
[xxstr appendString:@"*"];
|
||||
}
|
||||
|
||||
while ([copyStr rangeOfString:key options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
|
||||
NSRange range = [copyStr rangeOfString:key options:NSCaseInsensitiveSearch];
|
||||
|
||||
[titleStr addAttribute:NSForegroundColorAttributeName value:keyColor range:range];
|
||||
copyStr = [copyStr stringByReplacingCharactersInRange:NSMakeRange(range.location, range.length) withString:xxstr];
|
||||
}
|
||||
return titleStr;
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_collectionView.frame = self.bounds;
|
||||
}
|
||||
|
||||
#pragma mark - 懒加载
|
||||
|
||||
- (NSMutableArray *)selectedTags
|
||||
{
|
||||
if (!_selectedTags) {
|
||||
_selectedTags = [NSMutableArray array];
|
||||
}
|
||||
return _selectedTags;
|
||||
}
|
||||
|
||||
- (UICollectionView *)collectionView {
|
||||
if (!_collectionView) {
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:_layout];
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.backgroundColor = [UIColor clearColor];
|
||||
[_collectionView registerClass:[HXTagCollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
|
||||
}
|
||||
|
||||
_collectionView.collectionViewLayout = _layout;
|
||||
|
||||
if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
|
||||
//垂直
|
||||
_collectionView.showsVerticalScrollIndicator = YES;
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
} else {
|
||||
_collectionView.showsHorizontalScrollIndicator = YES;
|
||||
_collectionView.showsVerticalScrollIndicator = NO;
|
||||
}
|
||||
|
||||
_collectionView.frame = self.bounds;
|
||||
|
||||
return _collectionView;
|
||||
}
|
||||
|
||||
+ (CGFloat)getHeightWithTags:(NSArray *)tags layout:(HXTagCollectionViewFlowLayout *)layout tagAttribute:(HXTagAttribute *)tagAttribute width:(CGFloat)width
|
||||
{
|
||||
CGFloat contentHeight;
|
||||
|
||||
if (!layout) {
|
||||
layout = [[HXTagCollectionViewFlowLayout alloc] init];
|
||||
}
|
||||
|
||||
if (tagAttribute.titleSize <= 0) {
|
||||
tagAttribute = [[HXTagAttribute alloc] init];
|
||||
}
|
||||
|
||||
//cell的高度 = 顶部 + 高度
|
||||
contentHeight = layout.sectionInset.top + layout.itemSize.height;
|
||||
|
||||
CGFloat originX = layout.sectionInset.left;
|
||||
CGFloat originY = layout.sectionInset.top;
|
||||
|
||||
NSInteger itemCount = tags.count;
|
||||
|
||||
for (NSInteger i = 0; i < itemCount; i++) {
|
||||
CGSize maxSize = CGSizeMake(width - layout.sectionInset.left - layout.sectionInset.right, layout.itemSize.height);
|
||||
|
||||
CGRect frame = [tags[i] boundingRectWithSize:maxSize options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:tagAttribute.titleSize]} context:nil];
|
||||
|
||||
CGSize itemSize = CGSizeMake(frame.size.width + tagAttribute.tagSpace, layout.itemSize.height);
|
||||
|
||||
if (layout.scrollDirection == UICollectionViewScrollDirectionVertical) {
|
||||
//垂直滚动
|
||||
//当前CollectionViewCell的起点 + 当前CollectionViewCell的宽度 + 当前CollectionView距离右侧的间隔 > collectionView的宽度
|
||||
if ((originX + itemSize.width + layout.sectionInset.right) > width) {
|
||||
originX = layout.sectionInset.left;
|
||||
originY += itemSize.height + layout.minimumLineSpacing;
|
||||
|
||||
contentHeight += itemSize.height + layout.minimumLineSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
originX += itemSize.width + layout.minimumInteritemSpacing;
|
||||
}
|
||||
|
||||
contentHeight += layout.sectionInset.bottom;
|
||||
return contentHeight;
|
||||
}
|
||||
|
||||
@end
|
||||
29
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCell.h
Executable file
29
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCell.h
Executable file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// JXCategoryBaseCell.h
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/15.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JXCategoryBaseCellModel.h"
|
||||
#import "JXCategoryViewAnimator.h"
|
||||
#import "JXCategoryViewDefines.h"
|
||||
|
||||
@interface JXCategoryBaseCell : UICollectionViewCell
|
||||
|
||||
@property (nonatomic, strong, readonly) JXCategoryBaseCellModel *cellModel;
|
||||
@property (nonatomic, strong, readonly) JXCategoryViewAnimator *animator;
|
||||
|
||||
- (void)initializeViews NS_REQUIRES_SUPER;
|
||||
|
||||
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel NS_REQUIRES_SUPER;
|
||||
|
||||
- (BOOL)checkCanStartSelectedAnimation:(JXCategoryBaseCellModel *)cellModel;
|
||||
|
||||
- (void)addSelectedAnimationBlock:(JXCategoryCellSelectedAnimationBlock)block;
|
||||
|
||||
- (void)startSelectedAnimationIfNeeded:(JXCategoryBaseCellModel *)cellModel;
|
||||
|
||||
@end
|
||||
98
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCell.m
Executable file
98
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCell.m
Executable file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// JXCategoryBaseCell.m
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/15.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryBaseCell.h"
|
||||
#import "RTLManager.h"
|
||||
|
||||
@interface JXCategoryBaseCell ()
|
||||
@property (nonatomic, strong) JXCategoryBaseCellModel *cellModel;
|
||||
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
|
||||
@property (nonatomic, strong) NSMutableArray <JXCategoryCellSelectedAnimationBlock> *animationBlockArray;
|
||||
@end
|
||||
|
||||
@implementation JXCategoryBaseCell
|
||||
|
||||
#pragma mark - Initialize
|
||||
|
||||
- (void)dealloc {
|
||||
[self.animator stop];
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
|
||||
[self.animator stop];
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)initializeViews {
|
||||
_animationBlockArray = [NSMutableArray array];
|
||||
|
||||
[RTLManager horizontalFlipViewIfNeeded:self];
|
||||
[RTLManager horizontalFlipViewIfNeeded:self.contentView];
|
||||
}
|
||||
|
||||
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
|
||||
self.cellModel = cellModel;
|
||||
|
||||
if (cellModel.isSelectedAnimationEnabled) {
|
||||
[self.animationBlockArray removeLastObject];
|
||||
if ([self checkCanStartSelectedAnimation:cellModel]) {
|
||||
self.animator = [[JXCategoryViewAnimator alloc] init];
|
||||
self.animator.duration = cellModel.selectedAnimationDuration;
|
||||
} else {
|
||||
[self.animator stop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)checkCanStartSelectedAnimation:(JXCategoryBaseCellModel *)cellModel {
|
||||
BOOL canStartSelectedAnimation = ((cellModel.selectedType == JXCategoryCellSelectedTypeCode) || (cellModel.selectedType == JXCategoryCellSelectedTypeClick));
|
||||
return canStartSelectedAnimation;
|
||||
}
|
||||
|
||||
- (void)addSelectedAnimationBlock:(JXCategoryCellSelectedAnimationBlock)block {
|
||||
[self.animationBlockArray addObject:block];
|
||||
}
|
||||
|
||||
- (void)startSelectedAnimationIfNeeded:(JXCategoryBaseCellModel *)cellModel {
|
||||
if (cellModel.isSelectedAnimationEnabled && [self checkCanStartSelectedAnimation:cellModel]) {
|
||||
// 需要更新 isTransitionAnimating,用于处理在过滤时,禁止响应点击,避免界面异常。
|
||||
cellModel.transitionAnimating = YES;
|
||||
__weak typeof(self)weakSelf = self;
|
||||
self.animator.progressCallback = ^(CGFloat percent) {
|
||||
for (JXCategoryCellSelectedAnimationBlock block in weakSelf.animationBlockArray) {
|
||||
block(percent);
|
||||
}
|
||||
};
|
||||
self.animator.completeCallback = ^{
|
||||
cellModel.transitionAnimating = NO;
|
||||
[weakSelf.animationBlockArray removeAllObjects];
|
||||
};
|
||||
[self.animator start];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
31
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCellModel.h
Executable file
31
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCellModel.h
Executable file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// JXCategoryBaseCellModel.h
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/15.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JXCategoryViewDefines.h"
|
||||
|
||||
@interface JXCategoryBaseCellModel : NSObject
|
||||
|
||||
@property (nonatomic, assign) NSUInteger index;
|
||||
@property (nonatomic, assign) CGFloat cellWidth;
|
||||
@property (nonatomic, assign) CGFloat cellSpacing;
|
||||
@property (nonatomic, assign, getter=isSelected) BOOL selected;
|
||||
|
||||
@property (nonatomic, assign, getter=isCellWidthZoomEnabled) BOOL cellWidthZoomEnabled;
|
||||
@property (nonatomic, assign) CGFloat cellWidthNormalZoomScale;
|
||||
@property (nonatomic, assign) CGFloat cellWidthCurrentZoomScale;
|
||||
@property (nonatomic, assign) CGFloat cellWidthSelectedZoomScale;
|
||||
|
||||
@property (nonatomic, assign, getter=isSelectedAnimationEnabled) BOOL selectedAnimationEnabled;
|
||||
@property (nonatomic, assign) NSTimeInterval selectedAnimationDuration;
|
||||
@property (nonatomic, assign) JXCategoryCellSelectedType selectedType;
|
||||
|
||||
@property (nonatomic, assign, getter=isTransitionAnimating) BOOL transitionAnimating;
|
||||
|
||||
@end
|
||||
13
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCellModel.m
Executable file
13
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseCellModel.m
Executable file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// JXCategoryBaseCellModel.m
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/15.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryBaseCellModel.h"
|
||||
|
||||
@implementation JXCategoryBaseCellModel
|
||||
|
||||
@end
|
||||
222
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseView.h
Executable file
222
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseView.h
Executable file
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// 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 JXCategoryViewListContainer <NSObject>
|
||||
- (void)setDefaultSelectedIndex:(NSInteger)index;
|
||||
- (UIScrollView *)contentScrollView;
|
||||
- (void)reloadData;
|
||||
- (void)didClickSelectedItemAtIndex:(NSInteger)index;
|
||||
@end
|
||||
|
||||
@protocol JXCategoryViewDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
//为什么会把选中代理分为三个,因为有时候只关心点击选中的,有时候只关心滚动选中的,有时候只关心选中。所以具体情况,使用对应方法。
|
||||
/**
|
||||
点击选中或者滚动选中都会调用该方法。适用于只关心选中事件,不关心具体是点击还是滚动选中的。
|
||||
|
||||
@param categoryView categoryView对象
|
||||
@param index 选中的index
|
||||
*/
|
||||
- (void)categoryView:(JXCategoryBaseView *)categoryView didSelectedItemAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
点击选中的情况才会调用该方法
|
||||
|
||||
@param categoryView categoryView对象
|
||||
@param index 选中的index
|
||||
*/
|
||||
- (void)categoryView:(JXCategoryBaseView *)categoryView didClickSelectedItemAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
滚动选中的情况才会调用该方法
|
||||
|
||||
@param categoryView categoryView对象
|
||||
@param index 选中的index
|
||||
*/
|
||||
- (void)categoryView:(JXCategoryBaseView *)categoryView didScrollSelectedItemAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
控制能否点击Item
|
||||
|
||||
@param categoryView categoryView对象
|
||||
@param index 准备点击的index
|
||||
@return 能否点击
|
||||
*/
|
||||
- (BOOL)categoryView:(JXCategoryBaseView *)categoryView canClickItemAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
正在滚动中的回调
|
||||
|
||||
@param categoryView categoryView对象
|
||||
@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, readonly) JXCategoryCollectionView *collectionView;
|
||||
|
||||
@property (nonatomic, strong) NSArray <JXCategoryBaseCellModel *> *dataSource;
|
||||
|
||||
@property (nonatomic, weak) id<JXCategoryViewDelegate> delegate;
|
||||
|
||||
/**
|
||||
高封装度的列表容器,使用该类可以让列表拥有完成的生命周期、自动同步defaultSelectedIndex、自动调用reloadData。
|
||||
*/
|
||||
@property (nonatomic, weak) id<JXCategoryViewListContainer> listContainer;
|
||||
|
||||
/**
|
||||
推荐使用封装度更高的listContainer属性。如果使用contentScrollView请参考`LoadDataListCustomViewController`使用示例。
|
||||
*/
|
||||
@property (nonatomic, strong) UIScrollView *contentScrollView;
|
||||
|
||||
@property (nonatomic, assign) NSInteger defaultSelectedIndex; //修改初始化的时候默认选择的index
|
||||
|
||||
@property (nonatomic, assign, readonly) NSInteger selectedIndex;
|
||||
|
||||
@property (nonatomic, assign, getter=isContentScrollViewClickTransitionAnimationEnabled) BOOL contentScrollViewClickTransitionAnimationEnabled; //点击cell进行contentScrollView切换时是否需要动画。默认为YES
|
||||
|
||||
@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, getter=isAverageCellSpacingEnabled) BOOL averageCellSpacingEnabled; //当collectionView.contentSize.width小于JXCategoryBaseView的宽度,是否将cellSpacing均分。默认为YES。
|
||||
|
||||
//cell宽度是否缩放
|
||||
@property (nonatomic, assign, getter=isCellWidthZoomEnabled) BOOL cellWidthZoomEnabled; //默认为NO
|
||||
|
||||
@property (nonatomic, assign, getter=isCellWidthZoomScrollGradientEnabled) BOOL cellWidthZoomScrollGradientEnabled; //手势滚动过程中,是否需要更新cell的宽度。默认为YES
|
||||
|
||||
@property (nonatomic, assign) CGFloat cellWidthZoomScale; //默认1.2,cellWidthZoomEnabled为YES才生效
|
||||
|
||||
@property (nonatomic, assign, getter=isSelectedAnimationEnabled) BOOL selectedAnimationEnabled; //是否开启点击或代码选中动画。默认为NO。自定义的cell选中动画需要自己实现。(仅点击或调用selectItemAtIndex选中才有效,滚动选中无效)
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval selectedAnimationDuration; //cell选中动画的时间。默认0.25
|
||||
|
||||
/**
|
||||
选中目标index的item
|
||||
|
||||
@param index 目标index
|
||||
*/
|
||||
- (void)selectItemAtIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
初始化的时候无需调用。比如页面初始化之后,根据网络接口异步回调回来数据,重新配置categoryView,需要调用该方法进行刷新。
|
||||
*/
|
||||
- (void)reloadData;
|
||||
|
||||
/**
|
||||
重新配置categoryView但是不需要reload listContainer。特殊情况是该方法。
|
||||
*/
|
||||
- (void)reloadDataWithoutListContainer;
|
||||
|
||||
/**
|
||||
刷新指定的index的cell
|
||||
内部会触发`- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index`方法进行cellModel刷新
|
||||
|
||||
@param index 指定cell的index
|
||||
*/
|
||||
- (void)reloadCellAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface JXCategoryBaseView (UISubclassingBaseHooks)
|
||||
|
||||
/**
|
||||
获取目标cell当前的frame,反应当前真实的frame受到cellWidthSelectedZoomScale的影响。
|
||||
*/
|
||||
- (CGRect)getTargetCellFrame:(NSInteger)targetIndex;
|
||||
|
||||
/**
|
||||
获取目标cell的选中时的frame,其他cell的状态都当做普通状态处理。
|
||||
*/
|
||||
- (CGRect)getTargetSelectedCellFrame:(NSInteger)targetIndex selectedType:(JXCategoryCellSelectedType)selectedType;
|
||||
- (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;
|
||||
|
||||
/**
|
||||
选中某一个item的时候调用,该方法用于子类重载。
|
||||
如果外部要选中某个index,请使用`- (void)selectItemAtIndex:(NSUInteger)index;`
|
||||
|
||||
@param index 选中的index
|
||||
@param selectedType JXCategoryCellSelectedType
|
||||
@return 返回值为NO,表示触发内部某些判断(点击了同一个cell),子类无需后续操作。
|
||||
*/
|
||||
- (BOOL)selectCellAtIndex:(NSInteger)index selectedType:(JXCategoryCellSelectedType)selectedType 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 cellModel在数组中的index
|
||||
*/
|
||||
- (void)refreshCellModel:(JXCategoryBaseCellModel *)cellModel index:(NSInteger)index NS_REQUIRES_SUPER;
|
||||
|
||||
@end
|
||||
687
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseView.m
Executable file
687
SweetParty/第三方库/JXCategoryView/Base/JXCategoryBaseView.m
Executable file
@@ -0,0 +1,687 @@
|
||||
//
|
||||
// JXCategoryBaseView.m
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/15.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryBaseView.h"
|
||||
#import "JXCategoryFactory.h"
|
||||
#import "JXCategoryViewAnimator.h"
|
||||
#import "RTLManager.h"
|
||||
|
||||
struct DelegateFlags {
|
||||
unsigned int didSelectedItemAtIndexFlag : 1;
|
||||
unsigned int didClickSelectedItemAtIndexFlag : 1;
|
||||
unsigned int didScrollSelectedItemAtIndexFlag : 1;
|
||||
unsigned int canClickItemAtIndexFlag : 1;
|
||||
unsigned int scrollingFromLeftIndexToRightIndexFlag : 1;
|
||||
};
|
||||
|
||||
@interface JXCategoryBaseView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
|
||||
|
||||
@property (nonatomic, strong) JXCategoryCollectionView *collectionView;
|
||||
@property (nonatomic, assign) struct DelegateFlags delegateFlags;
|
||||
@property (nonatomic, assign) NSInteger selectedIndex;
|
||||
@property (nonatomic, assign) CGFloat innerCellSpacing;
|
||||
@property (nonatomic, assign) CGPoint lastContentViewContentOffset;
|
||||
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
|
||||
// 正在滚动中的目标index。用于处理正在滚动列表的时候,立即点击item,会导致界面显示异常。
|
||||
@property (nonatomic, assign) NSInteger scrollingTargetIndex;
|
||||
@property (nonatomic, assign, getter=isNeedReloadByBecomeActive) BOOL needReloadByBecomeActive;
|
||||
@property (nonatomic, assign, getter=isFirstLayoutSubviews) BOOL firstLayoutSubviews;
|
||||
@property (nonatomic, assign, getter=isNeedConfigAutomaticallyAdjustsScrollViewInsets) BOOL needConfigAutomaticallyAdjustsScrollViewInsets;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JXCategoryBaseView
|
||||
|
||||
- (void)dealloc {
|
||||
if (self.contentScrollView) {
|
||||
[self.contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[self.animator stop];
|
||||
}
|
||||
|
||||
- (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)willMoveToSuperview:(UIView *)newSuperview {
|
||||
[super willMoveToSuperview:newSuperview];
|
||||
|
||||
[self configAutomaticallyAdjustsScrollViewInsets:newSuperview];
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
[self reloadDataWithoutListContainer];
|
||||
[self.listContainer reloadData];
|
||||
}
|
||||
|
||||
- (void)reloadDataWithoutListContainer {
|
||||
[self refreshDataSource];
|
||||
[self refreshState];
|
||||
[self.collectionView.collectionViewLayout invalidateLayout];
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
|
||||
- (void)reloadCellAtIndex:(NSInteger)index {
|
||||
if (index < 0 || index >= self.dataSource.count) {
|
||||
return;
|
||||
}
|
||||
JXCategoryBaseCellModel *cellModel = self.dataSource[index];
|
||||
cellModel.selectedType = JXCategoryCellSelectedTypeUnknown;
|
||||
[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 selectedType:JXCategoryCellSelectedTypeCode];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
//部分使用者为了适配不同的手机屏幕尺寸,JXCategoryView的宽高比要求保持一样,所以它的高度就会因为不同宽度的屏幕而不一样。计算出来的高度,有时候会是位数很长的浮点数,如果把这个高度设置给UICollectionView就会触发内部的一个错误。所以,为了规避这个问题,在这里对高度统一向下取整。
|
||||
//如果向下取整导致了你的页面异常,请自己重新设置JXCategoryView的高度,保证为整数即可。
|
||||
CGRect targetFrame = CGRectMake(0, 0, self.bounds.size.width, floor(self.bounds.size.height));
|
||||
if (self.isFirstLayoutSubviews) {
|
||||
if (self.bounds.size.width == 0 || self.bounds.size.height == 0) {
|
||||
return;
|
||||
}
|
||||
if (self.isNeedConfigAutomaticallyAdjustsScrollViewInsets) {
|
||||
[self configAutomaticallyAdjustsScrollViewInsets:self.superview];
|
||||
}
|
||||
self.firstLayoutSubviews = NO;
|
||||
self.collectionView.frame = targetFrame;
|
||||
[self reloadDataWithoutListContainer];
|
||||
}else {
|
||||
if (!CGRectEqualToRect(self.collectionView.frame, targetFrame)) {
|
||||
self.collectionView.frame = targetFrame;
|
||||
[self refreshState];
|
||||
[self.collectionView.collectionViewLayout invalidateLayout];
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setter
|
||||
|
||||
- (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.canClickItemAtIndexFlag = [delegate respondsToSelector:@selector(categoryView:canClickItemAtIndex:)];
|
||||
_delegateFlags.scrollingFromLeftIndexToRightIndexFlag = [delegate respondsToSelector:@selector(categoryView:scrollingFromLeftIndex:toRightIndex:ratio:)];
|
||||
}
|
||||
|
||||
- (void)setDefaultSelectedIndex:(NSInteger)defaultSelectedIndex {
|
||||
_defaultSelectedIndex = defaultSelectedIndex;
|
||||
|
||||
self.selectedIndex = defaultSelectedIndex;
|
||||
[self.listContainer setDefaultSelectedIndex:defaultSelectedIndex];
|
||||
}
|
||||
|
||||
- (void)setContentScrollView:(UIScrollView *)contentScrollView {
|
||||
if (_contentScrollView != nil) {
|
||||
[_contentScrollView removeObserver:self forKeyPath:@"contentOffset"];
|
||||
}
|
||||
_contentScrollView = contentScrollView;
|
||||
|
||||
self.contentScrollView.scrollsToTop = NO;
|
||||
[self.contentScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
|
||||
}
|
||||
|
||||
- (void)setListContainer:(id<JXCategoryViewListContainer>)listContainer {
|
||||
_listContainer = listContainer;
|
||||
[listContainer setDefaultSelectedIndex:self.defaultSelectedIndex];
|
||||
self.contentScrollView = [listContainer contentScrollView];
|
||||
}
|
||||
|
||||
#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 {
|
||||
JXCategoryBaseCellModel *cellModel = self.dataSource[indexPath.item];
|
||||
cellModel.selectedType = JXCategoryCellSelectedTypeUnknown;
|
||||
[(JXCategoryBaseCell *)cell reloadData:cellModel];
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
BOOL isTransitionAnimating = NO;
|
||||
for (JXCategoryBaseCellModel *cellModel in self.dataSource) {
|
||||
if (cellModel.isTransitionAnimating) {
|
||||
isTransitionAnimating = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isTransitionAnimating) {
|
||||
//当前没有正在过渡的item,才允许点击选中
|
||||
[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 {
|
||||
return CGSizeMake(self.dataSource[indexPath.item].cellWidth, self.collectionView.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)) {
|
||||
//只处理用户滚动的情况
|
||||
[self contentOffsetOfContentScrollViewDidChanged:contentOffset];
|
||||
}
|
||||
self.lastContentViewContentOffset = contentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)configAutomaticallyAdjustsScrollViewInsets:(UIView *)view {
|
||||
UIResponder *next = view;
|
||||
while (next != nil) {
|
||||
if ([next isKindOfClass:[UIViewController class]]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
((UIViewController *)next).automaticallyAdjustsScrollViewInsets = NO;
|
||||
#pragma clang diagnostic pop
|
||||
self.needConfigAutomaticallyAdjustsScrollViewInsets = NO;
|
||||
break;
|
||||
}
|
||||
next = next.nextResponder;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)getContentEdgeInsetLeft {
|
||||
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
|
||||
return self.innerCellSpacing;
|
||||
}
|
||||
return self.contentEdgeInsetLeft;
|
||||
}
|
||||
|
||||
- (CGFloat)getContentEdgeInsetRight {
|
||||
if (self.contentEdgeInsetRight == JXCategoryViewAutomaticDimension) {
|
||||
return self.innerCellSpacing;
|
||||
}
|
||||
return self.contentEdgeInsetRight;
|
||||
}
|
||||
|
||||
- (CGFloat)getCellWidthAtIndex:(NSInteger)index {
|
||||
return [self preferredCellWidthAtIndex:index] + self.cellWidthIncrement;
|
||||
}
|
||||
|
||||
- (void)clickSelectItemAtIndex:(NSInteger)index {
|
||||
if (self.delegateFlags.canClickItemAtIndexFlag && ![self.delegate categoryView:self canClickItemAtIndex:index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self selectCellAtIndex:index selectedType:JXCategoryCellSelectedTypeClick];
|
||||
}
|
||||
|
||||
- (void)scrollSelectItemAtIndex:(NSInteger)index {
|
||||
[self selectCellAtIndex:index selectedType:JXCategoryCellSelectedTypeScroll];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
if (self.isNeedReloadByBecomeActive) {
|
||||
self.needReloadByBecomeActive = NO;
|
||||
[self reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation JXCategoryBaseView (UISubclassingBaseHooks)
|
||||
|
||||
- (CGRect)getTargetCellFrame:(NSInteger)targetIndex {
|
||||
CGFloat x = [self getContentEdgeInsetLeft];
|
||||
for (int i = 0; i < targetIndex; i ++) {
|
||||
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
|
||||
CGFloat cellWidth;
|
||||
if (cellModel.isTransitionAnimating && cellModel.isCellWidthZoomEnabled) {
|
||||
//正在进行动画的时候,cellWidthCurrentZoomScale是随着动画渐变的,而没有立即更新到目标值
|
||||
if (cellModel.isSelected) {
|
||||
cellWidth = [self getCellWidthAtIndex:cellModel.index]*cellModel.cellWidthSelectedZoomScale;
|
||||
}else {
|
||||
cellWidth = [self getCellWidthAtIndex:cellModel.index]*cellModel.cellWidthNormalZoomScale;
|
||||
}
|
||||
}else {
|
||||
cellWidth = cellModel.cellWidth;
|
||||
}
|
||||
x += cellWidth + self.innerCellSpacing;
|
||||
}
|
||||
CGFloat width;
|
||||
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
|
||||
if (selectedCellModel.isTransitionAnimating && selectedCellModel.isCellWidthZoomEnabled) {
|
||||
width = [self getCellWidthAtIndex:selectedCellModel.index]*selectedCellModel.cellWidthSelectedZoomScale;
|
||||
}else {
|
||||
width = selectedCellModel.cellWidth;
|
||||
}
|
||||
return CGRectMake(x, 0, width, self.bounds.size.height);
|
||||
}
|
||||
|
||||
- (CGRect)getTargetSelectedCellFrame:(NSInteger)targetIndex selectedType:(JXCategoryCellSelectedType)selectedType {
|
||||
CGFloat x = [self getContentEdgeInsetLeft];
|
||||
for (int i = 0; i < targetIndex; i ++) {
|
||||
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
|
||||
x += [self getCellWidthAtIndex:cellModel.index] + self.innerCellSpacing;
|
||||
}
|
||||
CGFloat cellWidth = 0;
|
||||
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
|
||||
if (selectedCellModel.cellWidthZoomEnabled) {
|
||||
cellWidth = [self getCellWidthAtIndex:targetIndex]*selectedCellModel.cellWidthSelectedZoomScale;
|
||||
}else {
|
||||
cellWidth = [self getCellWidthAtIndex:targetIndex];
|
||||
}
|
||||
return CGRectMake(x, 0, cellWidth, self.bounds.size.height);
|
||||
}
|
||||
|
||||
- (void)initializeData {
|
||||
_firstLayoutSubviews = YES;
|
||||
_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;
|
||||
_selectedAnimationEnabled = NO;
|
||||
_selectedAnimationDuration = 0.25;
|
||||
_scrollingTargetIndex = -1;
|
||||
_contentScrollViewClickTransitionAnimationEnabled = YES;
|
||||
_needReloadByBecomeActive = NO;
|
||||
}
|
||||
|
||||
- (void)initializeViews {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
_collectionView = [[JXCategoryCollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout];
|
||||
self.collectionView.backgroundColor = [UIColor clearColor];
|
||||
self.collectionView.showsHorizontalScrollIndicator = NO;
|
||||
self.collectionView.showsVerticalScrollIndicator = NO;
|
||||
self.collectionView.scrollsToTop = NO;
|
||||
self.collectionView.dataSource = self;
|
||||
self.collectionView.delegate = self;
|
||||
[self.collectionView registerClass:[self preferredCellClass] forCellWithReuseIdentifier:NSStringFromClass([self preferredCellClass])];
|
||||
if (@available(iOS 10.0, *)) {
|
||||
self.collectionView.prefetchingEnabled = NO;
|
||||
}
|
||||
if (@available(iOS 11.0, *)) {
|
||||
if ([self.collectionView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
|
||||
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
}
|
||||
if ([RTLManager supportRTL]) {
|
||||
self.collectionView.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
|
||||
[RTLManager horizontalFlipView:self.collectionView];
|
||||
}
|
||||
[self addSubview:self.collectionView];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)refreshDataSource {}
|
||||
|
||||
- (void)refreshState {
|
||||
if (self.selectedIndex < 0 || self.selectedIndex >= self.dataSource.count) {
|
||||
self.defaultSelectedIndex = 0;
|
||||
}
|
||||
|
||||
self.innerCellSpacing = self.cellSpacing;
|
||||
//总的内容宽度(左边距+cell总宽度+总cellSpacing+右边距)
|
||||
__block CGFloat totalItemWidth = [self getContentEdgeInsetLeft];
|
||||
//总的cell宽度
|
||||
CGFloat totalCellWidth = 0;
|
||||
for (int i = 0; i < self.dataSource.count; i++) {
|
||||
JXCategoryBaseCellModel *cellModel = self.dataSource[i];
|
||||
cellModel.index = i;
|
||||
cellModel.cellWidthZoomEnabled = self.cellWidthZoomEnabled;
|
||||
cellModel.cellWidthNormalZoomScale = 1;
|
||||
cellModel.cellWidthSelectedZoomScale = self.cellWidthZoomScale;
|
||||
cellModel.selectedAnimationEnabled = self.selectedAnimationEnabled;
|
||||
cellModel.selectedAnimationDuration = self.selectedAnimationDuration;
|
||||
cellModel.cellSpacing = self.innerCellSpacing;
|
||||
if (i == self.selectedIndex) {
|
||||
cellModel.selected = YES;
|
||||
cellModel.cellWidthCurrentZoomScale = cellModel.cellWidthSelectedZoomScale;
|
||||
}else {
|
||||
cellModel.selected = NO;
|
||||
cellModel.cellWidthCurrentZoomScale = cellModel.cellWidthNormalZoomScale;
|
||||
}
|
||||
if (self.isCellWidthZoomEnabled) {
|
||||
cellModel.cellWidth = [self getCellWidthAtIndex:i]*cellModel.cellWidthCurrentZoomScale;
|
||||
}else {
|
||||
cellModel.cellWidth = [self getCellWidthAtIndex:i];
|
||||
}
|
||||
totalCellWidth += cellModel.cellWidth;
|
||||
if (i == self.dataSource.count - 1) {
|
||||
totalItemWidth += cellModel.cellWidth + [self getContentEdgeInsetRight];
|
||||
}else {
|
||||
totalItemWidth += cellModel.cellWidth + self.innerCellSpacing;
|
||||
}
|
||||
[self refreshCellModel:cellModel index:i];
|
||||
}
|
||||
|
||||
if (self.isAverageCellSpacingEnabled && totalItemWidth < self.bounds.size.width) {
|
||||
//如果总的内容宽度都没有超过视图宽度,就将cellSpacing等分
|
||||
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 = self.innerCellSpacing;
|
||||
}];
|
||||
}
|
||||
|
||||
//---------------------定位collectionView到当前选中的位置----------------------
|
||||
//因为初始化的时候,collectionView并没有初始化完,cell都没有被加载出来。只有自己手动计算当前选中的index的位置,然后更新到contentOffset
|
||||
__block CGFloat frameXOfSelectedCell = self.innerCellSpacing;
|
||||
__block CGFloat selectedCellWidth = 0;
|
||||
totalItemWidth = [self getContentEdgeInsetLeft];
|
||||
[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;
|
||||
}
|
||||
if (idx == self.dataSource.count - 1) {
|
||||
totalItemWidth += cellModel.cellWidth + [self getContentEdgeInsetRight];
|
||||
}else {
|
||||
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];
|
||||
//---------------------定位collectionView到当前选中的位置----------------------
|
||||
|
||||
if (CGRectEqualToRect(self.contentScrollView.frame, CGRectZero) && self.contentScrollView.superview != nil) {
|
||||
//某些情况系统会出现JXCategoryView先布局,contentScrollView后布局。就会导致下面指定defaultSelectedIndex失效,所以发现contentScrollView的frame为zero时,强行触发其父视图链里面已经有frame的一个父视图的layoutSubviews方法。
|
||||
//比如JXSegmentedListContainerView会将contentScrollView包裹起来使用,该情况需要JXSegmentedListContainerView.superView触发布局更新
|
||||
UIView *parentView = self.contentScrollView.superview;
|
||||
while (parentView != nil && CGRectEqualToRect(parentView.frame, CGRectZero)) {
|
||||
parentView = parentView.superview;
|
||||
}
|
||||
[parentView setNeedsLayout];
|
||||
[parentView layoutIfNeeded];
|
||||
}
|
||||
//将contentScrollView的contentOffset定位到当前选中index的位置
|
||||
[self.contentScrollView setContentOffset:CGPointMake(self.selectedIndex*self.contentScrollView.bounds.size.width, 0) animated:NO];
|
||||
}
|
||||
|
||||
- (BOOL)selectCellAtIndex:(NSInteger)targetIndex selectedType:(JXCategoryCellSelectedType)selectedType {
|
||||
if (targetIndex < 0 || targetIndex >= self.dataSource.count) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
self.needReloadByBecomeActive = NO;
|
||||
if (self.selectedIndex == targetIndex) {
|
||||
//目标index和当前选中的index相等,就不需要处理后续的选中更新逻辑,只需要回调代理方法即可。
|
||||
if (selectedType == JXCategoryCellSelectedTypeCode) {
|
||||
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
||||
}else if (selectedType == JXCategoryCellSelectedTypeClick) {
|
||||
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
||||
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
|
||||
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
|
||||
}
|
||||
}else if (selectedType == JXCategoryCellSelectedTypeScroll) {
|
||||
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
|
||||
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
|
||||
}
|
||||
}
|
||||
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
|
||||
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
|
||||
}
|
||||
self.scrollingTargetIndex = -1;
|
||||
return NO;
|
||||
}
|
||||
|
||||
//通知子类刷新当前选中的和将要选中的cellModel
|
||||
JXCategoryBaseCellModel *lastCellModel = self.dataSource[self.selectedIndex];
|
||||
lastCellModel.selectedType = selectedType;
|
||||
JXCategoryBaseCellModel *selectedCellModel = self.dataSource[targetIndex];
|
||||
selectedCellModel.selectedType = selectedType;
|
||||
[self refreshSelectedCellModel:selectedCellModel unselectedCellModel:lastCellModel];
|
||||
|
||||
//刷新当前选中的和将要选中的cell
|
||||
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.scrollingTargetIndex != -1 && self.scrollingTargetIndex != targetIndex) {
|
||||
JXCategoryBaseCellModel *scrollingTargetCellModel = self.dataSource[self.scrollingTargetIndex];
|
||||
scrollingTargetCellModel.selected = NO;
|
||||
scrollingTargetCellModel.selectedType = selectedType;
|
||||
[self refreshSelectedCellModel:selectedCellModel unselectedCellModel:scrollingTargetCellModel];
|
||||
JXCategoryBaseCell *scrollingTargetCell = (JXCategoryBaseCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.scrollingTargetIndex inSection:0]];
|
||||
[scrollingTargetCell reloadData:scrollingTargetCellModel];
|
||||
}
|
||||
|
||||
if (self.isCellWidthZoomEnabled) {
|
||||
[self.collectionView.collectionViewLayout invalidateLayout];
|
||||
//延时为了解决cellwidth变化,点击最后几个cell,scrollToItem会出现位置偏移bu。需要等cellWidth动画渐变结束后再滚动到index的cell位置。
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.selectedAnimationDuration * 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 (selectedType == JXCategoryCellSelectedTypeClick ||
|
||||
selectedType == JXCategoryCellSelectedTypeCode) {
|
||||
[self.contentScrollView setContentOffset:CGPointMake(targetIndex*self.contentScrollView.bounds.size.width, 0) animated:self.isContentScrollViewClickTransitionAnimationEnabled];
|
||||
}
|
||||
|
||||
self.selectedIndex = targetIndex;
|
||||
if (selectedType == JXCategoryCellSelectedTypeCode) {
|
||||
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
||||
} else if (selectedType == JXCategoryCellSelectedTypeClick) {
|
||||
[self.listContainer didClickSelectedItemAtIndex:targetIndex];
|
||||
if (self.delegateFlags.didClickSelectedItemAtIndexFlag) {
|
||||
[self.delegate categoryView:self didClickSelectedItemAtIndex:targetIndex];
|
||||
}
|
||||
} else if(selectedType == JXCategoryCellSelectedTypeScroll) {
|
||||
if (self.delegateFlags.didScrollSelectedItemAtIndexFlag) {
|
||||
[self.delegate categoryView:self didScrollSelectedItemAtIndex:targetIndex];
|
||||
}
|
||||
}
|
||||
if (self.delegateFlags.didSelectedItemAtIndexFlag) {
|
||||
[self.delegate categoryView:self didSelectedItemAtIndex:targetIndex];
|
||||
}
|
||||
self.scrollingTargetIndex = -1;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)refreshSelectedCellModel:(JXCategoryBaseCellModel *)selectedCellModel unselectedCellModel:(JXCategoryBaseCellModel *)unselectedCellModel {
|
||||
selectedCellModel.selected = YES;
|
||||
unselectedCellModel.selected = NO;
|
||||
|
||||
if (self.isCellWidthZoomEnabled) {
|
||||
if (selectedCellModel.selectedType == JXCategoryCellSelectedTypeCode ||
|
||||
selectedCellModel.selectedType == JXCategoryCellSelectedTypeClick) {
|
||||
self.animator = [[JXCategoryViewAnimator alloc] init];
|
||||
self.animator.duration = self.selectedAnimationDuration;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.animator.progressCallback = ^(CGFloat percent) {
|
||||
selectedCellModel.transitionAnimating = YES;
|
||||
unselectedCellModel.transitionAnimating = YES;
|
||||
selectedCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:selectedCellModel.cellWidthNormalZoomScale to:selectedCellModel.cellWidthSelectedZoomScale percent:percent];
|
||||
selectedCellModel.cellWidth = [weakSelf getCellWidthAtIndex:selectedCellModel.index] * selectedCellModel.cellWidthCurrentZoomScale;
|
||||
unselectedCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:unselectedCellModel.cellWidthSelectedZoomScale to:unselectedCellModel.cellWidthNormalZoomScale percent:percent];
|
||||
unselectedCellModel.cellWidth = [weakSelf getCellWidthAtIndex:unselectedCellModel.index] * unselectedCellModel.cellWidthCurrentZoomScale;
|
||||
[weakSelf.collectionView.collectionViewLayout invalidateLayout];
|
||||
};
|
||||
self.animator.completeCallback = ^{
|
||||
selectedCellModel.transitionAnimating = NO;
|
||||
unselectedCellModel.transitionAnimating = NO;
|
||||
};
|
||||
[self.animator start];
|
||||
} else {
|
||||
selectedCellModel.cellWidthCurrentZoomScale = selectedCellModel.cellWidthSelectedZoomScale;
|
||||
selectedCellModel.cellWidth = [self getCellWidthAtIndex:selectedCellModel.index] * selectedCellModel.cellWidthCurrentZoomScale;
|
||||
unselectedCellModel.cellWidthCurrentZoomScale = unselectedCellModel.cellWidthNormalZoomScale;
|
||||
unselectedCellModel.cellWidth = [self getCellWidthAtIndex:unselectedCellModel.index] * unselectedCellModel.cellWidthCurrentZoomScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)contentOffsetOfContentScrollViewDidChanged:(CGPoint)contentOffset {
|
||||
if (self.dataSource.count == 0) {
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
//快速滑动翻页,用户一直在拖拽contentScrollView,需要更新选中状态
|
||||
//滑动一小段距离,然后放开回到原位,contentOffset同样的值会回调多次。例如在index为1的情况,滑动放开回到原位,contentOffset会多次回调CGPoint(width, 0)
|
||||
if (!(self.lastContentViewContentOffset.x == contentOffset.x && self.selectedIndex == baseIndex)) {
|
||||
[self scrollSelectItemAtIndex:baseIndex];
|
||||
}
|
||||
} else {
|
||||
self.needReloadByBecomeActive = YES;
|
||||
if (self.animator.isExecuting) {
|
||||
[self.animator invalid];
|
||||
//需要重置之前animator.progessCallback为处理完的状态
|
||||
for (JXCategoryBaseCellModel *model in self.dataSource) {
|
||||
if (model.isSelected) {
|
||||
model.cellWidthCurrentZoomScale = model.cellWidthSelectedZoomScale;
|
||||
model.cellWidth = [self getCellWidthAtIndex:model.index] * model.cellWidthCurrentZoomScale;
|
||||
}else {
|
||||
model.cellWidthCurrentZoomScale = model.cellWidthNormalZoomScale;
|
||||
model.cellWidth = [self getCellWidthAtIndex:model.index] * model.cellWidthCurrentZoomScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
//快速滑动翻页,当remainderRatio没有变成0,但是已经翻页了,需要通过下面的判断,触发选中
|
||||
if (fabs(ratio - self.selectedIndex) > 1) {
|
||||
NSInteger targetIndex = baseIndex;
|
||||
if (ratio < self.selectedIndex) {
|
||||
targetIndex = baseIndex + 1;
|
||||
}
|
||||
[self scrollSelectItemAtIndex:targetIndex];
|
||||
}
|
||||
|
||||
if (self.selectedIndex == baseIndex) {
|
||||
self.scrollingTargetIndex = baseIndex + 1;
|
||||
} else {
|
||||
self.scrollingTargetIndex = baseIndex;
|
||||
}
|
||||
|
||||
if (self.isCellWidthZoomEnabled && self.isCellWidthZoomScrollGradientEnabled) {
|
||||
JXCategoryBaseCellModel *leftCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex];
|
||||
JXCategoryBaseCellModel *rightCellModel = (JXCategoryBaseCellModel *)self.dataSource[baseIndex + 1];
|
||||
leftCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:leftCellModel.cellWidthSelectedZoomScale to:leftCellModel.cellWidthNormalZoomScale percent:remainderRatio];
|
||||
leftCellModel.cellWidth = [self getCellWidthAtIndex:leftCellModel.index] * leftCellModel.cellWidthCurrentZoomScale;
|
||||
rightCellModel.cellWidthCurrentZoomScale = [JXCategoryFactory interpolationFrom:rightCellModel.cellWidthNormalZoomScale to:rightCellModel.cellWidthSelectedZoomScale percent:remainderRatio];
|
||||
rightCellModel.cellWidth = [self getCellWidthAtIndex:rightCellModel.index] * rightCellModel.cellWidthCurrentZoomScale;
|
||||
[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 {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
24
SweetParty/第三方库/JXCategoryView/Common/JXCategoryCollectionView.h
Executable file
24
SweetParty/第三方库/JXCategoryView/Common/JXCategoryCollectionView.h
Executable file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// JXCategoryCollectionView.h
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/21.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JXCategoryIndicatorProtocol.h"
|
||||
@class JXCategoryCollectionView;
|
||||
|
||||
@protocol JXCategoryCollectionViewGestureDelegate <NSObject>
|
||||
@optional
|
||||
- (BOOL)categoryCollectionView:(JXCategoryCollectionView *)collectionView gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
|
||||
- (BOOL)categoryCollectionView:(JXCategoryCollectionView *)collectionView gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
|
||||
@end
|
||||
|
||||
@interface JXCategoryCollectionView : UICollectionView
|
||||
|
||||
@property (nonatomic, strong) NSArray <UIView<JXCategoryIndicatorProtocol> *> *indicators;
|
||||
@property (nonatomic, weak) id<JXCategoryCollectionViewGestureDelegate> gestureDelegate;
|
||||
|
||||
@end
|
||||
52
SweetParty/第三方库/JXCategoryView/Common/JXCategoryCollectionView.m
Executable file
52
SweetParty/第三方库/JXCategoryView/Common/JXCategoryCollectionView.m
Executable file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// JXCategoryCollectionView.m
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/21.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryCollectionView.h"
|
||||
|
||||
@interface JXCategoryCollectionView ()<UIGestureRecognizerDelegate>
|
||||
@end
|
||||
|
||||
@implementation JXCategoryCollectionView
|
||||
|
||||
- (void)setIndicators:(NSArray<UIView<JXCategoryIndicatorProtocol> *> *)indicators {
|
||||
for (UIView *indicator in _indicators) {
|
||||
//先移除之前的indicator
|
||||
[indicator removeFromSuperview];
|
||||
}
|
||||
|
||||
_indicators = indicators;
|
||||
|
||||
for (UIView *indicator in indicators) {
|
||||
[self addSubview:indicator];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
for (UIView<JXCategoryIndicatorProtocol> *view in self.indicators) {
|
||||
[self sendSubviewToBack:view];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
|
||||
if (self.gestureDelegate && [self.gestureDelegate respondsToSelector:@selector(categoryCollectionView:gestureRecognizerShouldBegin:)]) {
|
||||
return [self.gestureDelegate categoryCollectionView:self gestureRecognizerShouldBegin:gestureRecognizer];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
if (self.gestureDelegate && [self.gestureDelegate respondsToSelector:@selector(categoryCollectionView:gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
|
||||
return [self.gestureDelegate categoryCollectionView:self gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
18
SweetParty/第三方库/JXCategoryView/Common/JXCategoryFactory.h
Executable file
18
SweetParty/第三方库/JXCategoryView/Common/JXCategoryFactory.h
Executable file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// JXCategoryFactory.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/17.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface JXCategoryFactory : NSObject
|
||||
|
||||
+ (CGFloat)interpolationFrom:(CGFloat)from to:(CGFloat)to percent:(CGFloat)percent;
|
||||
|
||||
+ (UIColor *)interpolationColorFrom:(UIColor *)fromColor to:(UIColor *)toColor percent:(CGFloat)percent;
|
||||
|
||||
@end
|
||||
29
SweetParty/第三方库/JXCategoryView/Common/JXCategoryFactory.m
Executable file
29
SweetParty/第三方库/JXCategoryView/Common/JXCategoryFactory.m
Executable file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// JXCategoryFactory.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/17.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryFactory.h"
|
||||
#import "UIColor+JXAdd.h"
|
||||
|
||||
@implementation JXCategoryFactory
|
||||
|
||||
+ (CGFloat)interpolationFrom:(CGFloat)from to:(CGFloat)to percent:(CGFloat)percent
|
||||
{
|
||||
percent = MAX(0, MIN(1, percent));
|
||||
return from + (to - from)*percent;
|
||||
}
|
||||
|
||||
+ (UIColor *)interpolationColorFrom:(UIColor *)fromColor to:(UIColor *)toColor percent:(CGFloat)percent
|
||||
{
|
||||
CGFloat red = [self interpolationFrom:fromColor.jx_red to:toColor.jx_red percent:percent];
|
||||
CGFloat green = [self interpolationFrom:fromColor.jx_green to:toColor.jx_green percent:percent];
|
||||
CGFloat blue = [self interpolationFrom:fromColor.jx_blue to:toColor.jx_blue percent:percent];
|
||||
CGFloat alpha = [self interpolationFrom:fromColor.jx_alpha to:toColor.jx_alpha percent:percent];
|
||||
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
|
||||
}
|
||||
|
||||
@end
|
||||
31
SweetParty/第三方库/JXCategoryView/Common/JXCategoryIndicatorParamsModel.h
Executable file
31
SweetParty/第三方库/JXCategoryView/Common/JXCategoryIndicatorParamsModel.h
Executable file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// JXCategoryIndicatorParamsModel.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/12/13.
|
||||
// Copyright © 2018 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JXCategoryViewDefines.h"
|
||||
|
||||
/**
|
||||
指示器不同情况处理时传递的数据模型,不同情况会对不同的属性赋值,根据不同情况的 api 说明确认。
|
||||
|
||||
FAQ: 为什么会通过 model 传递数据?
|
||||
因为指示器处理逻辑以后会扩展不同的使用场景,会新增参数,如果不通过 model 传递,就会在 api 新增参数,一旦修改 api 改的地方就特别多了,而且会影响到之前自定义实现的开发者。
|
||||
*/
|
||||
@interface JXCategoryIndicatorParamsModel : NSObject
|
||||
|
||||
@property (nonatomic, assign) NSInteger selectedIndex; // 当前选中的 index
|
||||
@property (nonatomic, assign) CGRect selectedCellFrame; // 当前选中的 cellFrame
|
||||
@property (nonatomic, assign) NSInteger leftIndex; // 正在过渡中的两个 cell,相对位置在左边的 cell 的 index
|
||||
@property (nonatomic, assign) CGRect leftCellFrame; // 正在过渡中的两个 cell,相对位置在左边的 cell 的 frame
|
||||
@property (nonatomic, assign) NSInteger rightIndex; // 正在过渡中的两个 cell,相对位置在右边的 cell 的 index
|
||||
@property (nonatomic, assign) CGRect rightCellFrame; // 正在过渡中的两个 cell,相对位置在右边的 cell 的 frame
|
||||
@property (nonatomic, assign) CGFloat percent; // 正在过渡中的两个 cell,从左到右的百分比
|
||||
@property (nonatomic, assign) NSInteger lastSelectedIndex; // 之前选中的 index
|
||||
@property (nonatomic, assign) JXCategoryCellSelectedType selectedType; //cell 被选中类型
|
||||
|
||||
@end
|
||||
13
SweetParty/第三方库/JXCategoryView/Common/JXCategoryIndicatorParamsModel.m
Executable file
13
SweetParty/第三方库/JXCategoryView/Common/JXCategoryIndicatorParamsModel.m
Executable file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// JXCategoryIndicatorParamsModel.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/12/13.
|
||||
// Copyright © 2018 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryIndicatorParamsModel.h"
|
||||
|
||||
@implementation JXCategoryIndicatorParamsModel
|
||||
|
||||
@end
|
||||
49
SweetParty/第三方库/JXCategoryView/Common/JXCategoryIndicatorProtocol.h
Executable file
49
SweetParty/第三方库/JXCategoryView/Common/JXCategoryIndicatorProtocol.h
Executable file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// JXCategoryIndicatorProtocol.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/17.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JXCategoryViewDefines.h"
|
||||
#import "JXCategoryIndicatorParamsModel.h"
|
||||
|
||||
@protocol JXCategoryIndicatorProtocol <NSObject>
|
||||
|
||||
/**
|
||||
categoryView 重置状态时调用
|
||||
|
||||
param selectedIndex 当前选中的 index
|
||||
param selectedCellFrame 当前选中的 cellFrame
|
||||
@param model 数据模型
|
||||
*/
|
||||
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model;
|
||||
|
||||
/**
|
||||
contentScrollView在进行手势滑动时,处理指示器跟随手势变化UI逻辑;
|
||||
|
||||
param selectedIndex 当前选中的index
|
||||
param leftIndex 正在过渡中的两个cell,相对位置在左边的cell的index
|
||||
param leftCellFrame 正在过渡中的两个cell,相对位置在左边的cell的frame
|
||||
param rightIndex 正在过渡中的两个cell,相对位置在右边的cell的index
|
||||
param rightCellFrame 正在过渡中的两个cell,相对位置在右边的cell的frame
|
||||
param percent 过渡百分比
|
||||
@param model 数据模型
|
||||
*/
|
||||
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model;
|
||||
|
||||
/**
|
||||
选中了某一个cell
|
||||
|
||||
param lastSelectedIndex 之前选中的index
|
||||
param selectedIndex 选中的index
|
||||
param selectedCellFrame 选中的cellFrame
|
||||
param selectedType cell被选中类型
|
||||
@param model 数据模型
|
||||
*/
|
||||
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model;
|
||||
|
||||
@end
|
||||
16
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerRTLCell.h
Executable file
16
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerRTLCell.h
Executable file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// JXCategoryListContainerRTLCell.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2020/7/3.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JXCategoryListContainerRTLCell : UICollectionViewCell
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
23
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerRTLCell.m
Executable file
23
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerRTLCell.m
Executable file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// JXCategoryListContainerRTLCell.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2020/7/3.
|
||||
//
|
||||
|
||||
#import "JXCategoryListContainerRTLCell.h"
|
||||
#import "RTLManager.h"
|
||||
|
||||
@implementation JXCategoryListContainerRTLCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[RTLManager horizontalFlipViewIfNeeded:self];
|
||||
[RTLManager horizontalFlipViewIfNeeded:self.contentView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
122
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerView.h
Executable file
122
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerView.h
Executable file
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// JXCategoryListScrollView.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/9/12.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "JXCategoryViewDefines.h"
|
||||
#import "JXCategoryBaseView.h"
|
||||
@class JXCategoryListContainerView;
|
||||
|
||||
/**
|
||||
列表容器视图的类型
|
||||
|
||||
- ScrollView: UIScrollView。优势:没有其他副作用。劣势:视图内存占用相对大一点。
|
||||
- CollectionView: 使用UICollectionView。优势:因为列表被添加到cell上,视图的内存占用更少,适合内存要求特别高的场景。劣势:因为cell重用机制的问题,导致列表下拉刷新视图,会因为被removeFromSuperview而被隐藏。需要参考`LoadDataListCollectionListViewController`类做特殊处理。
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, JXCategoryListContainerType) {
|
||||
JXCategoryListContainerType_ScrollView,
|
||||
JXCategoryListContainerType_CollectionView,
|
||||
};
|
||||
|
||||
@protocol JXCategoryListContentViewDelegate <NSObject>
|
||||
|
||||
/**
|
||||
如果列表是VC,就返回VC.view
|
||||
如果列表是View,就返回View自己
|
||||
|
||||
@return 返回列表视图
|
||||
*/
|
||||
- (UIView *)listView;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
可选实现,列表将要显示的时候调用
|
||||
*/
|
||||
- (void)listWillAppear;
|
||||
|
||||
/**
|
||||
可选实现,列表显示的时候调用
|
||||
*/
|
||||
- (void)listDidAppear;
|
||||
|
||||
/**
|
||||
可选实现,列表将要消失的时候调用
|
||||
*/
|
||||
- (void)listWillDisappear;
|
||||
|
||||
/**
|
||||
可选实现,列表消失的时候调用
|
||||
*/
|
||||
- (void)listDidDisappear;
|
||||
|
||||
@end
|
||||
|
||||
@protocol JXCategoryListContainerViewDelegate <NSObject>
|
||||
/**
|
||||
返回list的数量
|
||||
|
||||
@param listContainerView 列表的容器视图
|
||||
@return list的数量
|
||||
*/
|
||||
- (NSInteger)numberOfListsInlistContainerView:(JXCategoryListContainerView *)listContainerView;
|
||||
|
||||
/**
|
||||
根据index返回一个对应列表实例,需要是遵从`JXCategoryListContentViewDelegate`协议的对象。
|
||||
你可以代理方法调用的时候初始化对应列表,达到懒加载的效果。这也是默认推荐的初始化列表方法。你也可以提前创建好列表,等该代理方法回调的时候再返回也可以,达到预加载的效果。
|
||||
如果列表是用自定义UIView封装的,就让自定义UIView遵从`JXCategoryListContentViewDelegate`协议,该方法返回自定义UIView即可。
|
||||
如果列表是用自定义UIViewController封装的,就让自定义UIViewController遵从`JXCategoryListContentViewDelegate`协议,该方法返回自定义UIViewController即可。
|
||||
|
||||
@param listContainerView 列表的容器视图
|
||||
@param index 目标下标
|
||||
@return 遵从JXCategoryListContentViewDelegate协议的list实例
|
||||
*/
|
||||
- (id<JXCategoryListContentViewDelegate>)listContainerView:(JXCategoryListContainerView *)listContainerView initListForIndex:(NSInteger)index;
|
||||
|
||||
@optional
|
||||
/**
|
||||
返回自定义UIScrollView或UICollectionView的Class
|
||||
某些特殊情况需要自己处理UIScrollView内部逻辑。比如项目用了FDFullscreenPopGesture,需要处理手势相关代理。
|
||||
|
||||
@param listContainerView JXCategoryListContainerView
|
||||
@return 自定义UIScrollView实例
|
||||
*/
|
||||
- (Class)scrollViewClassInlistContainerView:(JXCategoryListContainerView *)listContainerView;
|
||||
|
||||
/**
|
||||
控制能否初始化对应index的列表。有些业务需求,需要在某些情况才允许初始化某些列表,通过通过该代理实现控制。
|
||||
*/
|
||||
- (BOOL)listContainerView:(JXCategoryListContainerView *)listContainerView canInitListAtIndex:(NSInteger)index;
|
||||
|
||||
- (void)listContainerViewDidScroll:(UIScrollView *)scrollView;
|
||||
- (void)listContainerViewWillBeginDragging:(UIScrollView *)scrollView;
|
||||
- (void)listContainerViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
|
||||
- (void)listContainerViewWillBeginDecelerating:(UIScrollView *)scrollView;
|
||||
- (void)listContainerViewDidEndDecelerating:(UIScrollView *)scrollView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface JXCategoryListContainerView : UIView <JXCategoryViewListContainer>
|
||||
|
||||
@property (nonatomic, assign, readonly) JXCategoryListContainerType containerType;
|
||||
@property (nonatomic, strong, readonly) UIScrollView *scrollView;
|
||||
@property (nonatomic, strong, readonly) NSDictionary <NSNumber *, id<JXCategoryListContentViewDelegate>> *validListDict; //已经加载过的列表字典。key是index,value是对应的列表
|
||||
@property (nonatomic, strong) UIColor *listCellBackgroundColor; //默认:[UIColor whiteColor]
|
||||
/**
|
||||
滚动切换的时候,滚动距离超过一页的多少百分比,就触发列表的初始化。默认0.01(即列表显示了一点就触发加载)。范围0~1,开区间不包括0和1
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat initListPercent;
|
||||
@property (nonatomic, assign) BOOL bounces; //默认NO
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithType:(JXCategoryListContainerType)type delegate:(id<JXCategoryListContainerViewDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
554
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerView.m
Executable file
554
SweetParty/第三方库/JXCategoryView/Common/JXCategoryListContainerView.m
Executable file
@@ -0,0 +1,554 @@
|
||||
//
|
||||
// JXCategoryListContainerView.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/9/12.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryListContainerView.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "RTLManager.h"
|
||||
|
||||
@interface JXCategoryListContainerViewController : UIViewController
|
||||
@property (copy) void(^viewWillAppearBlock)(void);
|
||||
@property (copy) void(^viewDidAppearBlock)(void);
|
||||
@property (copy) void(^viewWillDisappearBlock)(void);
|
||||
@property (copy) void(^viewDidDisappearBlock)(void);
|
||||
@end
|
||||
|
||||
@implementation JXCategoryListContainerViewController
|
||||
- (void)dealloc
|
||||
{
|
||||
self.viewWillAppearBlock = nil;
|
||||
self.viewDidAppearBlock = nil;
|
||||
self.viewWillDisappearBlock = nil;
|
||||
self.viewDidDisappearBlock = nil;
|
||||
}
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
self.viewWillAppearBlock();
|
||||
}
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
self.viewDidAppearBlock();
|
||||
}
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
self.viewWillDisappearBlock();
|
||||
}
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
self.viewDidDisappearBlock();
|
||||
}
|
||||
- (BOOL)shouldAutomaticallyForwardAppearanceMethods { return NO; }
|
||||
@end
|
||||
|
||||
@interface JXCategoryListContainerView () <UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>
|
||||
@property (nonatomic, weak) id<JXCategoryListContainerViewDelegate> delegate;
|
||||
@property (nonatomic, strong) UIScrollView *scrollView;
|
||||
@property (nonatomic, assign) NSInteger currentIndex;
|
||||
@property (nonatomic, strong) NSMutableDictionary <NSNumber *, id<JXCategoryListContentViewDelegate>> *validListDict;
|
||||
@property (nonatomic, assign) NSInteger willAppearIndex;
|
||||
@property (nonatomic, assign) NSInteger willDisappearIndex;
|
||||
@property (nonatomic, strong) UICollectionView *collectionView;
|
||||
@property (nonatomic, strong) JXCategoryListContainerViewController *containerVC;
|
||||
@end
|
||||
|
||||
@implementation JXCategoryListContainerView
|
||||
|
||||
- (instancetype)initWithType:(JXCategoryListContainerType)type delegate:(id<JXCategoryListContainerViewDelegate>)delegate{
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
_containerType = type;
|
||||
_delegate = delegate;
|
||||
_validListDict = [NSMutableDictionary dictionary];
|
||||
_willAppearIndex = -1;
|
||||
_willDisappearIndex = -1;
|
||||
_initListPercent = 0.01;
|
||||
[self initializeViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initializeViews {
|
||||
_listCellBackgroundColor = [UIColor whiteColor];
|
||||
_containerVC = [[JXCategoryListContainerViewController alloc] init];
|
||||
self.containerVC.view.backgroundColor = [UIColor clearColor];
|
||||
[self addSubview:self.containerVC.view];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.containerVC.viewWillAppearBlock = ^{
|
||||
[weakSelf listWillAppear:weakSelf.currentIndex];
|
||||
};
|
||||
self.containerVC.viewDidAppearBlock = ^{
|
||||
[weakSelf listDidAppear:weakSelf.currentIndex];
|
||||
};
|
||||
self.containerVC.viewWillDisappearBlock = ^{
|
||||
[weakSelf listWillDisappear:weakSelf.currentIndex];
|
||||
};
|
||||
self.containerVC.viewDidDisappearBlock = ^{
|
||||
[weakSelf listDidDisappear:weakSelf.currentIndex];
|
||||
};
|
||||
if (self.containerType == JXCategoryListContainerType_ScrollView) {
|
||||
if (self.delegate &&
|
||||
[self.delegate respondsToSelector:@selector(scrollViewClassInlistContainerView:)] &&
|
||||
[[self.delegate scrollViewClassInlistContainerView:self] isKindOfClass:object_getClass([UIScrollView class])]) {
|
||||
_scrollView = (UIScrollView *)[[[self.delegate scrollViewClassInlistContainerView:self] alloc] init];
|
||||
}else {
|
||||
_scrollView = [[UIScrollView alloc] init];
|
||||
}
|
||||
self.scrollView.delegate = self;
|
||||
self.scrollView.pagingEnabled = YES;
|
||||
self.scrollView.showsHorizontalScrollIndicator = NO;
|
||||
self.scrollView.showsVerticalScrollIndicator = NO;
|
||||
self.scrollView.scrollsToTop = NO;
|
||||
self.scrollView.bounces = NO;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
if ([self.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
}
|
||||
[RTLManager horizontalFlipViewIfNeeded:self.scrollView];
|
||||
[self.containerVC.view addSubview:self.scrollView];
|
||||
}else {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.minimumLineSpacing = 0;
|
||||
layout.minimumInteritemSpacing = 0;
|
||||
if (self.delegate &&
|
||||
[self.delegate respondsToSelector:@selector(scrollViewClassInlistContainerView:)] &&
|
||||
[[self.delegate scrollViewClassInlistContainerView:self] isKindOfClass:object_getClass([UICollectionView class])]) {
|
||||
_collectionView = (UICollectionView *)[[[self.delegate scrollViewClassInlistContainerView:self] alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
}else {
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
}
|
||||
self.collectionView.pagingEnabled = YES;
|
||||
self.collectionView.showsHorizontalScrollIndicator = NO;
|
||||
self.collectionView.showsVerticalScrollIndicator = NO;
|
||||
self.collectionView.scrollsToTop = NO;
|
||||
self.collectionView.bounces = NO;
|
||||
self.collectionView.dataSource = self;
|
||||
self.collectionView.delegate = self;
|
||||
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
|
||||
if (@available(iOS 10.0, *)) {
|
||||
self.collectionView.prefetchingEnabled = NO;
|
||||
}
|
||||
if (@available(iOS 11.0, *)) {
|
||||
if ([self.collectionView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
|
||||
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
}
|
||||
if ([RTLManager supportRTL]) {
|
||||
self.collectionView.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
|
||||
[RTLManager horizontalFlipView:self.collectionView];
|
||||
}
|
||||
[self.containerVC.view addSubview:self.collectionView];
|
||||
//让外部统一访问scrollView
|
||||
_scrollView = _collectionView;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willMoveToSuperview:(UIView *)newSuperview {
|
||||
[super willMoveToSuperview:newSuperview];
|
||||
|
||||
UIResponder *next = newSuperview;
|
||||
while (next != nil) {
|
||||
if ([next isKindOfClass:[UIViewController class]]) {
|
||||
[((UIViewController *)next) addChildViewController:self.containerVC];
|
||||
break;
|
||||
}
|
||||
next = next.nextResponder;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
self.containerVC.view.frame = self.bounds;
|
||||
if (self.containerType == JXCategoryListContainerType_ScrollView) {
|
||||
if (CGRectEqualToRect(self.scrollView.frame, CGRectZero) || !CGSizeEqualToSize(self.scrollView.bounds.size, self.bounds.size)) {
|
||||
self.scrollView.frame = self.bounds;
|
||||
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
|
||||
[_validListDict enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull index, id<JXCategoryListContentViewDelegate> _Nonnull list, BOOL * _Nonnull stop) {
|
||||
[list listView].frame = CGRectMake(index.intValue*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
|
||||
}];
|
||||
self.scrollView.contentOffset = CGPointMake(self.currentIndex*self.scrollView.bounds.size.width, 0);
|
||||
}else {
|
||||
self.scrollView.frame = self.bounds;
|
||||
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
|
||||
}
|
||||
}else {
|
||||
if (CGRectEqualToRect(self.collectionView.frame, CGRectZero) || !CGSizeEqualToSize(self.collectionView.bounds.size, self.bounds.size)) {
|
||||
[self.collectionView.collectionViewLayout invalidateLayout];
|
||||
self.collectionView.frame = self.bounds;
|
||||
[self.collectionView reloadData];
|
||||
[self.collectionView setContentOffset:CGPointMake(self.collectionView.bounds.size.width*self.currentIndex, 0) animated:NO];
|
||||
}else {
|
||||
self.collectionView.frame = self.bounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)setinitListPercent:(CGFloat)initListPercent {
|
||||
_initListPercent = initListPercent;
|
||||
if (initListPercent <= 0 || initListPercent >= 1) {
|
||||
NSAssert(NO, @"initListPercent值范围为开区间(0,1),即不包括0和1");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBounces:(BOOL)bounces {
|
||||
_bounces = bounces;
|
||||
self.scrollView.bounces = bounces;
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegate, UICollectionViewDataSource
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return [self.delegate numberOfListsInlistContainerView:self];
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
|
||||
cell.contentView.backgroundColor = self.listCellBackgroundColor;
|
||||
|
||||
UIView* listView = nil;
|
||||
id<JXCategoryListContentViewDelegate> list = _validListDict[@(indexPath.item)];
|
||||
if (list != nil) {
|
||||
//fixme:如果list是UIViewController,如果这里的frame修改是`[list listView].frame = cell.bounds;`。那么就必须给list vc添加如下代码:
|
||||
//- (void)loadView {
|
||||
// self.view = [[UIView alloc] init];
|
||||
//}
|
||||
//所以,总感觉是把UIViewController当做普通view使用,导致了系统内部的bug。所以,缓兵之计就是用下面的方法,暂时解决问题。
|
||||
listView = [list listView];
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
listView.frame = cell.contentView.bounds;
|
||||
} else {
|
||||
listView.frame = cell.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL isAdded = NO;
|
||||
for (UIView *subview in cell.contentView.subviews) {
|
||||
if( listView != subview ) {
|
||||
[subview removeFromSuperview];
|
||||
} else {
|
||||
isAdded = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if( !isAdded && listView ) {
|
||||
[cell.contentView addSubview:listView];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return self.bounds.size;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewDidScroll:)]) {
|
||||
[self.delegate listContainerViewDidScroll:scrollView];
|
||||
}
|
||||
|
||||
if (!scrollView.isDragging && !scrollView.isTracking && !scrollView.isDecelerating) {
|
||||
return;
|
||||
}
|
||||
CGFloat ratio = scrollView.contentOffset.x/scrollView.bounds.size.width;
|
||||
NSInteger maxCount = round(scrollView.contentSize.width/scrollView.bounds.size.width);
|
||||
NSInteger leftIndex = floorf(ratio);
|
||||
leftIndex = MAX(0, MIN(maxCount - 1, leftIndex));
|
||||
NSInteger rightIndex = leftIndex + 1;
|
||||
if (ratio < 0 || rightIndex >= maxCount) {
|
||||
[self listDidAppearOrDisappear:scrollView];
|
||||
return;
|
||||
}
|
||||
CGFloat remainderRatio = ratio - leftIndex;
|
||||
if (rightIndex == self.currentIndex) {
|
||||
//当前选中的在右边,用户正在从右边往左边滑动
|
||||
if (self.validListDict[@(leftIndex)] == nil && remainderRatio < (1 - self.initListPercent)) {
|
||||
[self initListIfNeededAtIndex:leftIndex];
|
||||
}else if (self.validListDict[@(leftIndex)] != nil) {
|
||||
if (self.willAppearIndex == -1) {
|
||||
self.willAppearIndex = leftIndex;
|
||||
[self listWillAppear:self.willAppearIndex];
|
||||
}
|
||||
}
|
||||
if (self.willDisappearIndex == -1) {
|
||||
self.willDisappearIndex = rightIndex;
|
||||
[self listWillDisappear:self.willDisappearIndex];
|
||||
}
|
||||
}else {
|
||||
//当前选中的在左边,用户正在从左边往右边滑动
|
||||
if (self.validListDict[@(rightIndex)] == nil && remainderRatio > self.initListPercent) {
|
||||
[self initListIfNeededAtIndex:rightIndex];
|
||||
}else if (self.validListDict[@(rightIndex)] != nil) {
|
||||
if (self.willAppearIndex == -1) {
|
||||
self.willAppearIndex = rightIndex;
|
||||
[self listWillAppear:self.willAppearIndex];
|
||||
}
|
||||
}
|
||||
if (self.willDisappearIndex == -1) {
|
||||
self.willDisappearIndex = leftIndex;
|
||||
[self listWillDisappear:self.willDisappearIndex];
|
||||
}
|
||||
}
|
||||
[self listDidAppearOrDisappear:scrollView];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
//滑动到一半又取消滑动处理
|
||||
if (self.willDisappearIndex != -1) {
|
||||
[self listWillAppear:self.willDisappearIndex];
|
||||
[self listWillDisappear:self.willAppearIndex];
|
||||
[self listDidAppear:self.willDisappearIndex];
|
||||
[self listDidDisappear:self.willAppearIndex];
|
||||
self.willDisappearIndex = -1;
|
||||
self.willAppearIndex = -1;
|
||||
}
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewDidEndDecelerating:)]) {
|
||||
[self.delegate listContainerViewDidEndDecelerating:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
||||
{
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWillBeginDragging:)]) {
|
||||
[self.delegate listContainerViewWillBeginDragging:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
|
||||
{
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewDidEndDragging:willDecelerate:)]) {
|
||||
[self.delegate listContainerViewDidEndDragging:scrollView willDecelerate:decelerate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
|
||||
{
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerViewWillBeginDecelerating:)]) {
|
||||
[self.delegate listContainerViewWillBeginDecelerating:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - JXCategoryViewListContainer
|
||||
|
||||
- (UIScrollView *)contentScrollView {
|
||||
return self.scrollView;
|
||||
}
|
||||
|
||||
- (void)setDefaultSelectedIndex:(NSInteger)index {
|
||||
self.currentIndex = index;
|
||||
}
|
||||
|
||||
- (void)didClickSelectedItemAtIndex:(NSInteger)index {
|
||||
if (![self checkIndexValid:index]) {
|
||||
return;
|
||||
}
|
||||
self.willAppearIndex = -1;
|
||||
self.willDisappearIndex = -1;
|
||||
if (self.currentIndex != index) {
|
||||
[self listWillDisappear:self.currentIndex];
|
||||
[self listDidDisappear:self.currentIndex];
|
||||
[self listWillAppear:index];
|
||||
[self listDidAppear:index];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
for (id<JXCategoryListContentViewDelegate> list in _validListDict.allValues) {
|
||||
[[list listView] removeFromSuperview];
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
[(UIViewController *)list removeFromParentViewController];
|
||||
}
|
||||
}
|
||||
[_validListDict removeAllObjects];
|
||||
|
||||
if (self.containerType == JXCategoryListContainerType_ScrollView) {
|
||||
self.scrollView.contentSize = CGSizeMake(self.scrollView.bounds.size.width*[self.delegate numberOfListsInlistContainerView:self], self.scrollView.bounds.size.height);
|
||||
}else {
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
[self listWillAppear:self.currentIndex];
|
||||
[self listDidAppear:self.currentIndex];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)initListIfNeededAtIndex:(NSInteger)index {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerView:canInitListAtIndex:)]) {
|
||||
BOOL canInitList = [self.delegate listContainerView:self canInitListAtIndex:index];
|
||||
if (!canInitList) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
id<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
|
||||
if (list != nil) {
|
||||
//列表已经创建好了
|
||||
return;
|
||||
}
|
||||
list = [self.delegate listContainerView:self initListForIndex:index];
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
[self.containerVC addChildViewController:(UIViewController *)list];
|
||||
}
|
||||
_validListDict[@(index)] = list;
|
||||
|
||||
if (self.containerType == JXCategoryListContainerType_ScrollView) {
|
||||
[list listView].frame = CGRectMake(index*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
|
||||
[self.scrollView addSubview:[list listView]];
|
||||
[RTLManager horizontalFlipViewIfNeeded:[list listView]];
|
||||
}else {
|
||||
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
|
||||
for (UIView *subview in cell.contentView.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
[list listView].frame = cell.contentView.bounds;
|
||||
[cell.contentView addSubview:[list listView]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listWillAppear:(NSInteger)index {
|
||||
if (![self checkIndexValid:index]) {
|
||||
return;
|
||||
}
|
||||
id<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
|
||||
if (list != nil) {
|
||||
if (list && [list respondsToSelector:@selector(listWillAppear)]) {
|
||||
[list listWillAppear];
|
||||
}
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
UIViewController *listVC = (UIViewController *)list;
|
||||
[listVC beginAppearanceTransition:YES animated:NO];
|
||||
}
|
||||
}else {
|
||||
//当前列表未被创建(页面初始化或通过点击触发的listWillAppear)
|
||||
BOOL canInitList = YES;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(listContainerView:canInitListAtIndex:)]) {
|
||||
canInitList = [self.delegate listContainerView:self canInitListAtIndex:index];
|
||||
}
|
||||
if (canInitList) {
|
||||
id<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
|
||||
if (list == nil) {
|
||||
list = [self.delegate listContainerView:self initListForIndex:index];
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
[self.containerVC addChildViewController:(UIViewController *)list];
|
||||
}
|
||||
_validListDict[@(index)] = list;
|
||||
}
|
||||
if (self.containerType == JXCategoryListContainerType_ScrollView) {
|
||||
if ([list listView].superview == nil) {
|
||||
[list listView].frame = CGRectMake(index*self.scrollView.bounds.size.width, 0, self.scrollView.bounds.size.width, self.scrollView.bounds.size.height);
|
||||
[self.scrollView addSubview:[list listView]];
|
||||
[RTLManager horizontalFlipViewIfNeeded:[list listView]];
|
||||
|
||||
if (list && [list respondsToSelector:@selector(listWillAppear)]) {
|
||||
[list listWillAppear];
|
||||
}
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
UIViewController *listVC = (UIViewController *)list;
|
||||
[listVC beginAppearanceTransition:YES animated:NO];
|
||||
}
|
||||
}
|
||||
}else {
|
||||
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]];
|
||||
for (UIView *subview in cell.contentView.subviews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
[list listView].frame = cell.contentView.bounds;
|
||||
[cell.contentView addSubview:[list listView]];
|
||||
|
||||
if (list && [list respondsToSelector:@selector(listWillAppear)]) {
|
||||
[list listWillAppear];
|
||||
}
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
UIViewController *listVC = (UIViewController *)list;
|
||||
[listVC beginAppearanceTransition:YES animated:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listDidAppear:(NSInteger)index {
|
||||
if (![self checkIndexValid:index]) {
|
||||
return;
|
||||
}
|
||||
self.currentIndex = index;
|
||||
id<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
|
||||
if (list && [list respondsToSelector:@selector(listDidAppear)]) {
|
||||
[list listDidAppear];
|
||||
}
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
UIViewController *listVC = (UIViewController *)list;
|
||||
[listVC endAppearanceTransition];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listWillDisappear:(NSInteger)index {
|
||||
if (![self checkIndexValid:index]) {
|
||||
return;
|
||||
}
|
||||
id<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
|
||||
if (list && [list respondsToSelector:@selector(listWillDisappear)]) {
|
||||
[list listWillDisappear];
|
||||
}
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
UIViewController *listVC = (UIViewController *)list;
|
||||
[listVC beginAppearanceTransition:NO animated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)listDidDisappear:(NSInteger)index {
|
||||
if (![self checkIndexValid:index]) {
|
||||
return;
|
||||
}
|
||||
id<JXCategoryListContentViewDelegate> list = _validListDict[@(index)];
|
||||
if (list && [list respondsToSelector:@selector(listDidDisappear)]) {
|
||||
[list listDidDisappear];
|
||||
}
|
||||
if ([list isKindOfClass:[UIViewController class]]) {
|
||||
UIViewController *listVC = (UIViewController *)list;
|
||||
[listVC endAppearanceTransition];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)checkIndexValid:(NSInteger)index {
|
||||
NSUInteger count = [self.delegate numberOfListsInlistContainerView:self];
|
||||
if (count <= 0 || index >= count) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)listDidAppearOrDisappear:(UIScrollView *)scrollView {
|
||||
CGFloat currentIndexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width;
|
||||
if (self.willAppearIndex != -1 || self.willDisappearIndex != -1) {
|
||||
NSInteger disappearIndex = self.willDisappearIndex;
|
||||
NSInteger appearIndex = self.willAppearIndex;
|
||||
if (self.willAppearIndex > self.willDisappearIndex) {
|
||||
//将要出现的列表在右边
|
||||
if (currentIndexPercent >= self.willAppearIndex) {
|
||||
self.willDisappearIndex = -1;
|
||||
self.willAppearIndex = -1;
|
||||
[self listDidDisappear:disappearIndex];
|
||||
[self listDidAppear:appearIndex];
|
||||
}
|
||||
}else {
|
||||
//将要出现的列表在左边
|
||||
if (currentIndexPercent <= self.willAppearIndex) {
|
||||
self.willDisappearIndex = -1;
|
||||
self.willAppearIndex = -1;
|
||||
[self listDidDisappear:disappearIndex];
|
||||
[self listDidAppear:appearIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
24
SweetParty/第三方库/JXCategoryView/Common/JXCategoryViewAnimator.h
Executable file
24
SweetParty/第三方库/JXCategoryView/Common/JXCategoryViewAnimator.h
Executable file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// JXCategoryViewAnimator.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2019/1/24.
|
||||
// Copyright © 2019 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface JXCategoryViewAnimator : NSObject
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval duration;
|
||||
@property (nonatomic, copy) void(^progressCallback)(CGFloat percent);
|
||||
@property (nonatomic, copy) void(^completeCallback)(void);
|
||||
@property (readonly, getter=isExecuting) BOOL executing;
|
||||
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
- (void)invalid;
|
||||
|
||||
@end
|
||||
|
||||
75
SweetParty/第三方库/JXCategoryView/Common/JXCategoryViewAnimator.m
Executable file
75
SweetParty/第三方库/JXCategoryView/Common/JXCategoryViewAnimator.m
Executable file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// JXCategoryViewAnimator.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2019/1/24.
|
||||
// Copyright © 2019 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryViewAnimator.h"
|
||||
|
||||
@interface JXCategoryViewAnimator ()
|
||||
@property (nonatomic, strong) CADisplayLink *displayLink;
|
||||
@property (nonatomic, assign) CFTimeInterval firstTimestamp;
|
||||
@property (readwrite, getter=isExecuting) BOOL executing;
|
||||
@end
|
||||
|
||||
@implementation JXCategoryViewAnimator
|
||||
|
||||
#pragma mark - Initialize
|
||||
|
||||
- (void)dealloc {
|
||||
self.progressCallback = nil;
|
||||
self.completeCallback = nil;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_executing = NO;
|
||||
_duration = 0.25;
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(processDisplayLink:)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)start {
|
||||
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
self.executing = YES;
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
!self.progressCallback ?: self.progressCallback(1);
|
||||
[self.displayLink invalidate];
|
||||
!self.completeCallback ?: self.completeCallback();
|
||||
self.executing = NO;
|
||||
}
|
||||
|
||||
- (void)invalid {
|
||||
[self.displayLink invalidate];
|
||||
!self.completeCallback ?: self.completeCallback();
|
||||
self.executing = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)processDisplayLink:(CADisplayLink *)sender {
|
||||
if (self.firstTimestamp == 0) {
|
||||
self.firstTimestamp = sender.timestamp;
|
||||
return;
|
||||
}
|
||||
CGFloat percent = (sender.timestamp - self.firstTimestamp)/self.duration;
|
||||
if (percent >= 1) {
|
||||
!self.progressCallback ?: self.progressCallback(percent);
|
||||
[self.displayLink invalidate];
|
||||
!self.completeCallback ?: self.completeCallback();
|
||||
self.executing = NO;
|
||||
}else {
|
||||
!self.progressCallback ?: self.progressCallback(percent);
|
||||
self.executing = YES;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
43
SweetParty/第三方库/JXCategoryView/Common/JXCategoryViewDefines.h
Executable file
43
SweetParty/第三方库/JXCategoryView/Common/JXCategoryViewDefines.h
Executable file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// JXCategoryViewDefines.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/17.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
static const CGFloat JXCategoryViewAutomaticDimension = -1;
|
||||
|
||||
typedef void(^JXCategoryCellSelectedAnimationBlock)(CGFloat percent);
|
||||
|
||||
// 指示器的位置
|
||||
typedef NS_ENUM(NSUInteger, JXCategoryComponentPosition) {
|
||||
JXCategoryComponentPosition_Bottom,
|
||||
JXCategoryComponentPosition_Top
|
||||
};
|
||||
|
||||
// cell 被选中的类型
|
||||
typedef NS_ENUM(NSUInteger, JXCategoryCellSelectedType) {
|
||||
JXCategoryCellSelectedTypeUnknown, // 未知,不是选中(cellForRow方法里面、两个cell过渡时)
|
||||
JXCategoryCellSelectedTypeClick, // 点击选中
|
||||
JXCategoryCellSelectedTypeCode, // 调用方法 selectItemAtIndex: 选中
|
||||
JXCategoryCellSelectedTypeScroll // 通过滚动到某个 cell 选中
|
||||
};
|
||||
|
||||
// cell 标题锚点位置
|
||||
typedef NS_ENUM(NSUInteger, JXCategoryTitleLabelAnchorPointStyle) {
|
||||
JXCategoryTitleLabelAnchorPointStyleCenter,
|
||||
JXCategoryTitleLabelAnchorPointStyleTop,
|
||||
JXCategoryTitleLabelAnchorPointStyleBottom
|
||||
};
|
||||
|
||||
// 指示器滚动样式
|
||||
typedef NS_ENUM(NSUInteger, JXCategoryIndicatorScrollStyle) {
|
||||
JXCategoryIndicatorScrollStyleSimple, // 简单滚动,即从当前位置过渡到目标位置
|
||||
JXCategoryIndicatorScrollStyleSameAsUserScroll // 和用户左右滚动列表时的效果一样
|
||||
};
|
||||
|
||||
#define JXCategoryViewDeprecated(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead)
|
||||
18
SweetParty/第三方库/JXCategoryView/Common/UIColor+JXAdd.h
Executable file
18
SweetParty/第三方库/JXCategoryView/Common/UIColor+JXAdd.h
Executable file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// UIColor+JXAdd.h
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/21.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIColor (JXAdd)
|
||||
|
||||
@property (nonatomic, assign, readonly) CGFloat jx_red;
|
||||
@property (nonatomic, assign, readonly) CGFloat jx_green;
|
||||
@property (nonatomic, assign, readonly) CGFloat jx_blue;
|
||||
@property (nonatomic, assign, readonly) CGFloat jx_alpha;
|
||||
|
||||
@end
|
||||
35
SweetParty/第三方库/JXCategoryView/Common/UIColor+JXAdd.m
Executable file
35
SweetParty/第三方库/JXCategoryView/Common/UIColor+JXAdd.m
Executable file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// UIColor+JXAdd.m
|
||||
// UI系列测试
|
||||
//
|
||||
// Created by jiaxin on 2018/3/21.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIColor+JXAdd.h"
|
||||
|
||||
@implementation UIColor (JXAdd)
|
||||
|
||||
- (CGFloat)jx_red {
|
||||
CGFloat r = 0, g, b, a;
|
||||
[self getRed:&r green:&g blue:&b alpha:&a];
|
||||
return r;
|
||||
}
|
||||
|
||||
- (CGFloat)jx_green {
|
||||
CGFloat r, g = 0, b, a;
|
||||
[self getRed:&r green:&g blue:&b alpha:&a];
|
||||
return g;
|
||||
}
|
||||
|
||||
- (CGFloat)jx_blue {
|
||||
CGFloat r, g, b = 0, a;
|
||||
[self getRed:&r green:&g blue:&b alpha:&a];
|
||||
return b;
|
||||
}
|
||||
|
||||
- (CGFloat)jx_alpha {
|
||||
return CGColorGetAlpha(self.CGColor);
|
||||
}
|
||||
|
||||
@end
|
||||
13
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCell.h
Executable file
13
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCell.h
Executable file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// JXCategoryDotCell.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/20.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryTitleCell.h"
|
||||
|
||||
@interface JXCategoryDotCell : JXCategoryTitleCell
|
||||
|
||||
@end
|
||||
64
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCell.m
Executable file
64
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCell.m
Executable file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// 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) UIView *dot;
|
||||
@end
|
||||
|
||||
@implementation JXCategoryDotCell
|
||||
|
||||
- (void)initializeViews {
|
||||
[super initializeViews];
|
||||
|
||||
_dot = [[UIView alloc] init];
|
||||
[self.contentView addSubview:self.dot];
|
||||
self.dot.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
}
|
||||
|
||||
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
|
||||
[super reloadData:cellModel];
|
||||
|
||||
JXCategoryDotCellModel *myCellModel = (JXCategoryDotCellModel *)cellModel;
|
||||
self.dot.hidden = !myCellModel.dotHidden;
|
||||
self.dot.backgroundColor = myCellModel.dotColor;
|
||||
self.dot.layer.cornerRadius = myCellModel.dotCornerRadius;
|
||||
[NSLayoutConstraint deactivateConstraints:self.dot.constraints];
|
||||
[self.dot.widthAnchor constraintEqualToConstant:myCellModel.dotSize.width].active = YES;
|
||||
[self.dot.heightAnchor constraintEqualToConstant:myCellModel.dotSize.height].active = YES;
|
||||
switch (myCellModel.relativePosition) {
|
||||
case JXCategoryDotRelativePosition_TopLeft:
|
||||
{
|
||||
[self.dot.centerXAnchor constraintEqualToAnchor:self.titleLabel.leadingAnchor constant:myCellModel.dotOffset.x].active = YES;
|
||||
[self.dot.centerYAnchor constraintEqualToAnchor:self.titleLabel.topAnchor constant:myCellModel.dotOffset.y].active = YES;
|
||||
}
|
||||
break;
|
||||
case JXCategoryDotRelativePosition_TopRight:
|
||||
{
|
||||
[self.dot.centerXAnchor constraintEqualToAnchor:self.titleLabel.trailingAnchor constant:myCellModel.dotOffset.x].active = YES;
|
||||
[self.dot.centerYAnchor constraintEqualToAnchor:self.titleLabel.topAnchor constant:myCellModel.dotOffset.y].active = YES;
|
||||
}
|
||||
break;
|
||||
case JXCategoryDotRelativePosition_BottomLeft:
|
||||
{
|
||||
[self.dot.centerXAnchor constraintEqualToAnchor:self.titleLabel.leadingAnchor constant:myCellModel.dotOffset.x].active = YES;
|
||||
[self.dot.centerYAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor constant:myCellModel.dotOffset.y].active = YES;
|
||||
}
|
||||
break;
|
||||
case JXCategoryDotRelativePosition_BottomRight:
|
||||
{
|
||||
[self.dot.centerXAnchor constraintEqualToAnchor:self.titleLabel.trailingAnchor constant:myCellModel.dotOffset.x].active = YES;
|
||||
[self.dot.centerYAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor constant:myCellModel.dotOffset.y].active = YES;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
27
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCellModel.h
Executable file
27
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCellModel.h
Executable file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// 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;
|
||||
@property (nonatomic, assign) CGPoint dotOffset;
|
||||
|
||||
@end
|
||||
13
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCellModel.m
Executable file
13
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotCellModel.m
Executable file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// JXCategoryDotCellModel.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/20.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryDotCellModel.h"
|
||||
|
||||
@implementation JXCategoryDotCellModel
|
||||
|
||||
@end
|
||||
30
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotView.h
Executable file
30
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotView.h
Executable file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
//相对于titleLabel的位置,默认:JXCategoryDotRelativePosition_TopRight
|
||||
@property (nonatomic, assign) JXCategoryDotRelativePosition relativePosition;
|
||||
//@[@(布尔值)]数组,控制红点是否显示
|
||||
@property (nonatomic, strong) NSArray <NSNumber *> *dotStates;
|
||||
//红点的尺寸。默认:CGSizeMake(10, 10)
|
||||
@property (nonatomic, assign) CGSize dotSize;
|
||||
//红点的圆角值。默认:JXCategoryViewAutomaticDimension(self.dotSize.height/2)
|
||||
@property (nonatomic, assign) CGFloat dotCornerRadius;
|
||||
//红点的颜色。默认:[UIColor redColor]
|
||||
@property (nonatomic, strong) UIColor *dotColor;
|
||||
/**
|
||||
红点 x,y方向的偏移 (+值:水平方向向右,竖直方向向下)
|
||||
*/
|
||||
@property (nonatomic, assign) CGPoint dotOffset;
|
||||
|
||||
@end
|
||||
52
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotView.m
Executable file
52
SweetParty/第三方库/JXCategoryView/Dot/JXCategoryDotView.m
Executable file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// 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];
|
||||
_dotOffset = CGPointZero;
|
||||
}
|
||||
|
||||
- (Class)preferredCellClass {
|
||||
return [JXCategoryDotCell class];
|
||||
}
|
||||
|
||||
- (void)refreshDataSource {
|
||||
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:self.titles.count];
|
||||
for (int i = 0; i < self.titles.count; i++) {
|
||||
JXCategoryDotCellModel *cellModel = [[JXCategoryDotCellModel alloc] init];
|
||||
[tempArray addObject:cellModel];
|
||||
}
|
||||
self.dataSource = [NSArray arrayWithArray: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;
|
||||
myCellModel.dotOffset = self.dotOffset;
|
||||
if (self.dotCornerRadius == JXCategoryViewAutomaticDimension) {
|
||||
myCellModel.dotCornerRadius = self.dotSize.height/2;
|
||||
}else {
|
||||
myCellModel.dotCornerRadius = self.dotCornerRadius;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
15
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCell.h
Executable file
15
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCell.h
Executable file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// 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
|
||||
82
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCell.m
Executable file
82
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCell.m
Executable file
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// JXCategoryImageCell.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/20.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryImageCell.h"
|
||||
#import "JXCategoryImageCellModel.h"
|
||||
|
||||
@interface JXCategoryImageCell()
|
||||
@property (nonatomic, strong) NSString *currentImageName;
|
||||
@property (nonatomic, strong) NSURL *currentImageURL;
|
||||
@end
|
||||
|
||||
@implementation JXCategoryImageCell
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
|
||||
self.currentImageName = nil;
|
||||
self.currentImageURL = nil;
|
||||
}
|
||||
|
||||
- (void)initializeViews {
|
||||
[super initializeViews];
|
||||
|
||||
_imageView = [[UIImageView alloc] init];
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[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;
|
||||
if (myCellModel.imageCornerRadius && (myCellModel.imageCornerRadius != 0)) {
|
||||
self.imageView.layer.cornerRadius = myCellModel.imageCornerRadius;
|
||||
self.imageView.layer.masksToBounds = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadData:(JXCategoryBaseCellModel *)cellModel {
|
||||
[super reloadData:cellModel];
|
||||
|
||||
JXCategoryImageCellModel *myCellModel = (JXCategoryImageCellModel *)cellModel;
|
||||
//因为`- (void)reloadData:(JXCategoryBaseCellModel *)cellModel`方法会回调多次,尤其是左右滚动的时候会调用无数次,如果每次都触发图片加载,会非常消耗性能。所以只会在图片发生了变化的时候,才进行图片加载。
|
||||
NSString *currentImageName;
|
||||
NSURL *currentImageURL;
|
||||
if (myCellModel.imageName) {
|
||||
currentImageName = myCellModel.imageName;
|
||||
} else if (myCellModel.imageURL) {
|
||||
currentImageURL = myCellModel.imageURL;
|
||||
}
|
||||
if (myCellModel.isSelected) {
|
||||
if (myCellModel.selectedImageName) {
|
||||
currentImageName = myCellModel.selectedImageName;
|
||||
} else if (myCellModel.selectedImageURL) {
|
||||
currentImageURL = myCellModel.selectedImageURL;
|
||||
}
|
||||
}
|
||||
if (currentImageName && ![currentImageName isEqualToString:self.currentImageName]) {
|
||||
self.currentImageName = currentImageName;
|
||||
self.imageView.image = [UIImage imageNamed:currentImageName];
|
||||
} else if (currentImageURL && ![currentImageURL.absoluteString isEqualToString:self.currentImageURL.absoluteString]) {
|
||||
self.currentImageURL = currentImageURL;
|
||||
if (myCellModel.loadImageCallback) {
|
||||
myCellModel.loadImageCallback(self.imageView, currentImageURL);
|
||||
}
|
||||
}
|
||||
|
||||
if (myCellModel.isImageZoomEnabled) {
|
||||
self.imageView.transform = CGAffineTransformMakeScale(myCellModel.imageZoomScale, myCellModel.imageZoomScale);
|
||||
}else {
|
||||
self.imageView.transform = CGAffineTransformIdentity;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
31
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCellModel.h
Executable file
31
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCellModel.h
Executable file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// 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, getter=isImageZoomEnabled) BOOL imageZoomEnabled;
|
||||
|
||||
@property (nonatomic, assign) CGFloat imageZoomScale;
|
||||
|
||||
@end
|
||||
13
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCellModel.m
Executable file
13
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageCellModel.m
Executable file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// JXCategoryImageCellModel.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/20.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryImageCellModel.h"
|
||||
|
||||
@implementation JXCategoryImageCellModel
|
||||
|
||||
@end
|
||||
33
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageView.h
Executable file
33
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageView.h
Executable file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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, getter=isImageZoomEnabled) BOOL imageZoomEnabled; //默认值为 NO
|
||||
|
||||
@property (nonatomic, assign) CGFloat imageZoomScale; //默认值为 1.2,imageZoomEnabled 为 YES 时才生效
|
||||
|
||||
@end
|
||||
91
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageView.m
Executable file
91
SweetParty/第三方库/JXCategoryView/Image/JXCategoryImageView.m
Executable file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// 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 {
|
||||
NSUInteger count = (self.imageNames.count > 0) ? self.imageNames.count : (self.imageURLs.count > 0 ? self.imageURLs.count : 0);
|
||||
NSMutableArray *tempArray = [NSMutableArray arrayWithCapacity:count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
JXCategoryImageCellModel *cellModel = [[JXCategoryImageCellModel alloc] init];
|
||||
[tempArray addObject:cellModel];
|
||||
}
|
||||
self.dataSource = [NSArray arrayWithArray: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 && self.imageNames.count != 0) {
|
||||
myCellModel.imageName = self.imageNames[index];
|
||||
} else if (self.imageURLs && self.imageURLs.count != 0) {
|
||||
myCellModel.imageURL = self.imageURLs[index];
|
||||
}
|
||||
if (self.selectedImageNames && self.selectedImageNames != 0) {
|
||||
myCellModel.selectedImageName = self.selectedImageNames[index];
|
||||
} else if (self.selectedImageURLs && self.selectedImageURLs != 0) {
|
||||
myCellModel.selectedImageURL = self.selectedImageURLs[index];
|
||||
}
|
||||
myCellModel.imageZoomEnabled = self.imageZoomEnabled;
|
||||
myCellModel.imageZoomScale = ((index == self.selectedIndex) ? self.imageZoomScale : 1.0);
|
||||
}
|
||||
|
||||
- (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.isImageZoomEnabled) {
|
||||
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 {
|
||||
if (self.cellWidth == JXCategoryViewAutomaticDimension) {
|
||||
return self.imageSize.width;
|
||||
}
|
||||
return self.cellWidth;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// JXCategoryIndicatorBackgroundView.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/17.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryIndicatorComponentView.h"
|
||||
|
||||
/// BackgroundView 样式的指示器
|
||||
@interface JXCategoryIndicatorBackgroundView : JXCategoryIndicatorComponentView
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// JXCategoryIndicatorBackgroundView.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/17.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryIndicatorBackgroundView.h"
|
||||
#import "JXCategoryFactory.h"
|
||||
|
||||
@implementation JXCategoryIndicatorBackgroundView
|
||||
|
||||
#pragma mark - Initialize
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self configureDefaulteValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self configureDefaulteValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configureDefaulteValue {
|
||||
self.indicatorWidth = JXCategoryViewAutomaticDimension;
|
||||
self.indicatorHeight = JXCategoryViewAutomaticDimension;
|
||||
self.indicatorCornerRadius = JXCategoryViewAutomaticDimension;
|
||||
self.indicatorColor = [UIColor lightGrayColor];
|
||||
self.indicatorWidthIncrement = 10;
|
||||
}
|
||||
|
||||
#pragma mark - JXCategoryIndicatorProtocol
|
||||
|
||||
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
|
||||
self.layer.cornerRadius = [self indicatorCornerRadiusValue:model.selectedCellFrame];
|
||||
self.backgroundColor = self.indicatorColor;
|
||||
|
||||
CGFloat width = [self indicatorWidthValue:model.selectedCellFrame];
|
||||
CGFloat height = [self indicatorHeightValue:model.selectedCellFrame];
|
||||
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
|
||||
CGFloat y = (model.selectedCellFrame.size.height - height)/2 - self.verticalMargin;
|
||||
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 indicatorWidthValue:leftCellFrame];
|
||||
|
||||
if (percent == 0) {
|
||||
targetX = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidth)/2.0;
|
||||
}else {
|
||||
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;
|
||||
|
||||
targetX = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:percent];
|
||||
|
||||
if (self.indicatorWidth == JXCategoryViewAutomaticDimension) {
|
||||
targetWidth = [JXCategoryFactory interpolationFrom:leftWidth to:rightWidth percent:percent];
|
||||
}
|
||||
}
|
||||
|
||||
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
|
||||
if (self.isScrollEnabled == YES || (self.isScrollEnabled == NO && percent == 0)) {
|
||||
CGRect toFrame = self.frame;
|
||||
toFrame.origin.x = targetX;
|
||||
toFrame.size.width = targetWidth;
|
||||
self.frame = toFrame;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)jx_selectedCell:(JXCategoryIndicatorParamsModel *)model {
|
||||
CGFloat width = [self indicatorWidthValue:model.selectedCellFrame];
|
||||
CGRect toFrame = self.frame;
|
||||
toFrame.origin.x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - width)/2;
|
||||
toFrame.size.width = width;
|
||||
|
||||
if (self.isScrollEnabled) {
|
||||
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
|
||||
self.frame = toFrame;
|
||||
} completion:^(BOOL finished) {
|
||||
}];
|
||||
}else {
|
||||
self.frame = toFrame;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// JXCategoryIndicatorBallView.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/21.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryIndicatorComponentView.h"
|
||||
|
||||
/// QQ 小红点样式的指示器
|
||||
@interface JXCategoryIndicatorBallView : JXCategoryIndicatorComponentView
|
||||
|
||||
// 球沿的 X 轴方向上的偏移量。默认值为 20
|
||||
@property (nonatomic, assign) CGFloat ballScrollOffsetX;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
#pragma mark - Initialize
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self configureIndicatorBall];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self configureIndicatorBall];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configureIndicatorBall {
|
||||
self.indicatorWidth = 15;
|
||||
self.indicatorHeight = 15;
|
||||
_ballScrollOffsetX = 20;
|
||||
|
||||
_smallBall = [[UIView alloc] init];
|
||||
[self addSubview:self.smallBall];
|
||||
|
||||
_bigBall = [[UIView alloc] init];
|
||||
[self addSubview:self.bigBall];
|
||||
|
||||
_shapeLayer = [CAShapeLayer layer];
|
||||
[self.layer addSublayer:self.shapeLayer];
|
||||
}
|
||||
|
||||
#pragma mark - JXCategoryIndicatorProtocol
|
||||
|
||||
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
|
||||
CGFloat ballWidth = [self indicatorWidthValue:model.selectedCellFrame];
|
||||
CGFloat ballHeight = [self indicatorHeightValue:model.selectedCellFrame];
|
||||
|
||||
[CATransaction begin];
|
||||
[CATransaction setDisableActions:YES];
|
||||
self.shapeLayer.fillColor = self.indicatorColor.CGColor;
|
||||
[CATransaction commit];
|
||||
|
||||
self.smallBall.backgroundColor = self.indicatorColor;
|
||||
self.smallBall.layer.cornerRadius = ballHeight/2;
|
||||
self.bigBall.backgroundColor = self.indicatorColor;
|
||||
self.bigBall.layer.cornerRadius = ballHeight/2;
|
||||
|
||||
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - ballWidth)/2;
|
||||
CGFloat y = self.superview.bounds.size.height - ballHeight - self.verticalMargin;
|
||||
if (self.componentPosition == JXCategoryComponentPosition_Top) {
|
||||
y = self.verticalMargin;
|
||||
}
|
||||
self.smallBall.frame = CGRectMake(x, y, ballWidth, ballHeight);
|
||||
self.bigBall.frame = CGRectMake(x, y, ballWidth, ballHeight);
|
||||
}
|
||||
|
||||
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
|
||||
CGFloat ballWidth = [self indicatorWidthValue:model.leftCellFrame];
|
||||
CGFloat ballHeight = [self indicatorHeightValue:model.leftCellFrame];
|
||||
CGRect rightCellFrame = model.rightCellFrame;
|
||||
CGRect leftCellFrame = model.leftCellFrame;
|
||||
CGFloat percent = model.percent;
|
||||
CGFloat targetXOfBigBall = 0;
|
||||
CGFloat targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - ballWidth)/2;
|
||||
CGFloat targetWidthOfSmallBall = ballWidth;
|
||||
|
||||
if (percent == 0) {
|
||||
targetXOfBigBall = leftCellFrame.origin.x + (leftCellFrame.size.width - ballWidth)/2.0;
|
||||
targetXOfSmallBall = leftCellFrame.origin.x + (leftCellFrame.size.width - targetWidthOfSmallBall)/2.0;
|
||||
}else {
|
||||
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - ballWidth)/2;
|
||||
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - ballWidth)/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:ballWidth to:ballWidth/2 percent:percent*2];
|
||||
}else {
|
||||
targetXOfBigBall = [JXCategoryFactory interpolationFrom:(rightX - self.ballScrollOffsetX) to:rightX percent:(percent - 0.5)*2];
|
||||
targetWidthOfSmallBall = [JXCategoryFactory interpolationFrom:ballWidth/2 to:0 percent:(percent - 0.5)*2];
|
||||
targetXOfSmallBall = [JXCategoryFactory interpolationFrom:leftX to:rightX percent:(percent - 0.5)*2];
|
||||
}
|
||||
}
|
||||
|
||||
//允许变动frame的情况:1、允许滚动;2、不允许滚动,但是已经通过手势滚动切换一页内容了;
|
||||
if (self.isScrollEnabled == YES || (self.isScrollEnabled == 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 - ballHeight/2 - targetWidthOfSmallBall/2 - self.verticalMargin;
|
||||
if (self.componentPosition == JXCategoryComponentPosition_Top) {
|
||||
targetYOfSmallBall = ballHeight/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 ballWidth = [self indicatorWidthValue:model.selectedCellFrame];
|
||||
CGFloat ballHeight = [self indicatorHeightValue:model.selectedCellFrame];
|
||||
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - ballWidth)/2;
|
||||
CGFloat y = self.superview.bounds.size.height - ballHeight - self.verticalMargin;
|
||||
if (self.componentPosition == JXCategoryComponentPosition_Top) {
|
||||
y = self.verticalMargin;
|
||||
}
|
||||
CGRect toFrame = CGRectMake(x, y, ballWidth, ballHeight);
|
||||
|
||||
if (self.isScrollEnabled) {
|
||||
[UIView animateWithDuration:self.scrollAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
|
||||
self.smallBall.frame = toFrame;
|
||||
self.bigBall.frame = toFrame;
|
||||
self.smallBall.layer.cornerRadius = ballHeight/2;
|
||||
self.bigBall.layer.cornerRadius = ballHeight/2;
|
||||
} completion:^(BOOL finished) {
|
||||
|
||||
}];
|
||||
}else {
|
||||
self.smallBall.frame = toFrame;
|
||||
self.bigBall.frame = toFrame;
|
||||
self.smallBall.layer.cornerRadius = ballHeight/2;
|
||||
self.bigBall.layer.cornerRadius = ballHeight/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
|
||||
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// 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>
|
||||
|
||||
/**
|
||||
指示器的位置
|
||||
|
||||
可设置的枚举类型:
|
||||
- 底部:JXCategoryComponentPosition_Bottom
|
||||
- 顶部:JXCategoryComponentPosition_Top
|
||||
*/
|
||||
@property (nonatomic, assign) JXCategoryComponentPosition componentPosition;
|
||||
|
||||
/**
|
||||
指示器的宽度
|
||||
|
||||
默认值为 JXCategoryViewAutomaticDimension(表示与 cell 的宽度相等)。
|
||||
内部通过 `- (CGFloat)indicatorWidthValue:(CGRect)cellFrame` 方法获取实际的值。
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat indicatorWidth;
|
||||
|
||||
/**
|
||||
指示器的宽度增量
|
||||
|
||||
例如:需求是指示器宽度比 cell 宽度多 10pt。就可以将该属性赋值为 10。
|
||||
最终指示器的宽度 = indicatorWidth + indicatorWidthIncrement。
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat indicatorWidthIncrement;
|
||||
|
||||
/**
|
||||
指示器的高度
|
||||
|
||||
默认值为 3。
|
||||
内部通过 `- (CGFloat)indicatorHeightValue:(CGRect)cellFrame` 方法获取实际的值。
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat indicatorHeight;
|
||||
|
||||
/**
|
||||
指示器的 CornerRadius 圆角半径
|
||||
|
||||
默认值为 JXCategoryViewAutomaticDimension (等于 indicatorHeight/2)。
|
||||
内部通过 `- (CGFloat)indicatorCornerRadiusValue:(CGRect)cellFrame` 方法获取实际的值。
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat indicatorCornerRadius;
|
||||
|
||||
/**
|
||||
指示器的颜色
|
||||
*/
|
||||
@property (nonatomic, strong) UIColor *indicatorColor;
|
||||
|
||||
/**
|
||||
指示器在垂直方向上的偏移量
|
||||
|
||||
数值越大越靠近中心。默认值为 0。
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat verticalMargin;
|
||||
|
||||
/**
|
||||
指示器在水平方向上的偏移量
|
||||
|
||||
数值越大越靠近右边。默认值为 0。
|
||||
|
||||
自定义的
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat horizontalMargin;
|
||||
|
||||
/**
|
||||
是否允许手势滚动
|
||||
|
||||
点击切换的时候,是否允许滚动,默认值为 YES。
|
||||
*/
|
||||
@property (nonatomic, assign, getter=isScrollEnabled) BOOL scrollEnabled;
|
||||
|
||||
/**
|
||||
指示器滚动样式
|
||||
|
||||
点击切换的时候,如果允许滚动,分为简单滚动和复杂滚动。
|
||||
默认值为 JXCategoryIndicatorScrollStyleSimple
|
||||
目前仅JXCategoryIndicatorLineView、JXCategoryIndicatorDotLineView支持,其他子类暂不支持。
|
||||
*/
|
||||
@property (nonatomic, assign) JXCategoryIndicatorScrollStyle scrollStyle;
|
||||
|
||||
/**
|
||||
滚动动画的时间,默认值为 0.25s
|
||||
*/
|
||||
@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
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// JXCategoryComponentBaseView.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/17.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryIndicatorComponentView.h"
|
||||
|
||||
@implementation JXCategoryIndicatorComponentView
|
||||
|
||||
#pragma mark - Initialize
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self configureDefaultValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self configureDefaultValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configureDefaultValue {
|
||||
_componentPosition = JXCategoryComponentPosition_Bottom;
|
||||
_scrollEnabled = YES;
|
||||
_verticalMargin = 0;
|
||||
_horizontalMargin = 0;
|
||||
_scrollAnimationDuration = 0.25;
|
||||
_indicatorWidth = JXCategoryViewAutomaticDimension;
|
||||
_indicatorWidthIncrement = 0;
|
||||
_indicatorHeight = 3;
|
||||
_indicatorCornerRadius = JXCategoryViewAutomaticDimension;
|
||||
_indicatorColor = [UIColor redColor];
|
||||
_scrollStyle = JXCategoryIndicatorScrollStyleSimple;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (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
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// JXCategoryIndicatorDotLineView.h
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/22.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryIndicatorComponentView.h"
|
||||
|
||||
/// 点线效果的指示器
|
||||
@interface JXCategoryIndicatorDotLineView : JXCategoryIndicatorComponentView
|
||||
|
||||
// 线状态的最大宽度,默认值为 50
|
||||
@property (nonatomic, assign) CGFloat lineWidth;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// JXCategoryIndicatorDotLineView.m
|
||||
// JXCategoryView
|
||||
//
|
||||
// Created by jiaxin on 2018/8/22.
|
||||
// Copyright © 2018年 jiaxin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "JXCategoryIndicatorDotLineView.h"
|
||||
#import "JXCategoryFactory.h"
|
||||
#import "JXCategoryViewAnimator.h"
|
||||
|
||||
@interface JXCategoryIndicatorDotLineView ()
|
||||
@property (nonatomic, strong) JXCategoryViewAnimator *animator;
|
||||
@end
|
||||
|
||||
@implementation JXCategoryIndicatorDotLineView
|
||||
|
||||
#pragma mark - Initialize
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self configureDefaulteValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self configureDefaulteValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configureDefaulteValue {
|
||||
self.indicatorWidth = 10;
|
||||
self.indicatorHeight = 10;
|
||||
_lineWidth = 50;
|
||||
}
|
||||
|
||||
#pragma mark - JXCategoryIndicatorProtocol
|
||||
|
||||
- (void)jx_refreshState:(JXCategoryIndicatorParamsModel *)model {
|
||||
CGFloat dotWidth = [self indicatorWidthValue:model.selectedCellFrame];
|
||||
CGFloat dotHeight = [self indicatorHeightValue:model.selectedCellFrame];
|
||||
self.backgroundColor = self.indicatorColor;
|
||||
self.layer.cornerRadius = [self indicatorHeightValue:model.selectedCellFrame]/2;
|
||||
|
||||
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;
|
||||
CGFloat y = self.superview.bounds.size.height - dotHeight - self.verticalMargin;
|
||||
if (self.componentPosition == JXCategoryComponentPosition_Top) {
|
||||
y = self.verticalMargin;
|
||||
}
|
||||
self.frame = CGRectMake(x, y, dotWidth, dotHeight);
|
||||
}
|
||||
|
||||
- (void)jx_contentScrollViewDidScroll:(JXCategoryIndicatorParamsModel *)model {
|
||||
if (self.animator.isExecuting) {
|
||||
[self.animator invalid];
|
||||
self.animator = nil;
|
||||
}
|
||||
CGFloat dotWidth = [self indicatorWidthValue:model.selectedCellFrame];
|
||||
CGRect rightCellFrame = model.rightCellFrame;
|
||||
CGRect leftCellFrame = model.leftCellFrame;
|
||||
CGFloat percent = model.percent;
|
||||
CGFloat targetX = 0;
|
||||
CGFloat targetWidth = dotWidth;
|
||||
CGFloat leftWidth = dotWidth;
|
||||
CGFloat rightWidth = dotWidth;
|
||||
CGFloat leftX = leftCellFrame.origin.x + (leftCellFrame.size.width - leftWidth)/2;
|
||||
CGFloat rightX = rightCellFrame.origin.x + (rightCellFrame.size.width - rightWidth)/2;
|
||||
CGFloat centerX = leftX + (rightX - leftX - self.lineWidth)/2;
|
||||
|
||||
//前50%,移动x,增加宽度;后50%,移动x并减小width
|
||||
if (percent <= 0.5) {
|
||||
targetX = [JXCategoryFactory interpolationFrom:leftX to:centerX percent:percent*2];
|
||||
targetWidth = [JXCategoryFactory interpolationFrom:dotWidth to:self.lineWidth percent:percent*2];
|
||||
}else {
|
||||
targetX = [JXCategoryFactory interpolationFrom:centerX to:rightX percent:(percent - 0.5)*2];
|
||||
targetWidth = [JXCategoryFactory interpolationFrom:self.lineWidth to:dotWidth 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 {
|
||||
CGFloat dotWidth = [self indicatorWidthValue:model.selectedCellFrame];
|
||||
CGFloat x = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;
|
||||
CGRect targetIndicatorFrame = self.frame;
|
||||
targetIndicatorFrame.origin.x = x;
|
||||
if (self.isScrollEnabled) {
|
||||
if (self.scrollStyle == JXCategoryIndicatorScrollStyleSameAsUserScroll && (model.selectedType == JXCategoryCellSelectedTypeClick | model.selectedType == JXCategoryCellSelectedTypeCode)) {
|
||||
if (self.animator.isExecuting) {
|
||||
[self.animator invalid];
|
||||
self.animator = nil;
|
||||
}
|
||||
CGFloat leftX = 0;
|
||||
CGFloat rightX = 0;
|
||||
BOOL isNeedReversePercent = NO;
|
||||
if (self.frame.origin.x > model.selectedCellFrame.origin.x) {
|
||||
leftX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;;
|
||||
rightX = self.frame.origin.x;
|
||||
isNeedReversePercent = YES;
|
||||
}else {
|
||||
leftX = self.frame.origin.x;
|
||||
rightX = model.selectedCellFrame.origin.x + (model.selectedCellFrame.size.width - dotWidth)/2;
|
||||
}
|
||||
CGFloat centerX = leftX + (rightX - leftX - self.lineWidth)/2;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
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:centerX percent:percent*2];
|
||||
targetWidth = [JXCategoryFactory interpolationFrom:dotWidth to:self.lineWidth percent:percent*2];
|
||||
}else {
|
||||
targetX = [JXCategoryFactory interpolationFrom:centerX to:rightX percent:(percent - 0.5)*2];
|
||||
targetWidth = [JXCategoryFactory interpolationFrom:self.lineWidth to:dotWidth 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
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// 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;
|
||||
// 图片是否开启滚动,默认值为 NO
|
||||
@property (nonatomic, assign) BOOL indicatorImageViewRollEnabled;
|
||||
// 图片的尺寸,默认值为 CGSizeMake(30, 20)
|
||||
@property (nonatomic, assign) CGSize indicatorImageViewSize;
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user