首次提交

This commit is contained in:
启星
2025-08-08 11:05:33 +08:00
parent 1b3bb91b4a
commit adc1a2a25d
8803 changed files with 708874 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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
*/

View 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

View 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
*
*
*/

View 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

View 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
*
*/

View 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

View 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
*/

View 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

View File

@@ -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
*/

View File

@@ -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

View 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
*
*/

View 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

View 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
*
*/

View 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

View 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
*/

View 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

View 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
*
*/

View 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

View 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

View 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

View 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

View 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

View 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 */

View 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

View 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

View 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;
////////////////////////// 标题栏中间labeltitleLabel///////////////////////////
/** 设置 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

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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";

View File

@@ -0,0 +1,28 @@
/*
Localizable.strings
BRPickerViewDemo
Created by 任波 on 2019/10/30.
Copyright © 2019 91renb. All rights reserved.
*/
"确定" = "确定";
"取消" = "取消";
"完成" = "完成";
"年" = "年";
"月" = "月";
"日" = "日";
"时" = "时";
"分" = "分";
"秒" = "秒";
"至今" = "至今";
"今天" = "今天";
"周一" = "周一";
"周二" = "周二";
"周三" = "周三";
"周四" = "周四";
"周五" = "周五";
"周六" = "周六";
"周日" = "周日";

View File

@@ -0,0 +1,28 @@
/*
Localizable.strings
BRPickerViewDemo
Created by 任波 on 2019/10/30.
Copyright © 2019 91renb. All rights reserved.
*/
"确定" = "確定";
"取消" = "取消";
"完成" = "完成";
"年" = "年";
"月" = "月";
"日" = "日";
"时" = "時";
"分" = "分";
"秒" = "秒";
"至今" = "至今";
"今天" = "今天";
"周一" = "周壹";
"周二" = "周二";
"周三" = "周三";
"周四" = "周四";
"周五" = "周五";
"周六" = "周六";
"周日" = "周日";

View 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 */

View 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

View 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-Hantzh-HKzh-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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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.2cellWidthZoomEnabled为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

View 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;
// indexitem
@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];
//使JXCategoryViewUICollectionView
//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;
//Automatic1
if (self.contentEdgeInsetLeft == JXCategoryViewAutomaticDimension) {
cellSpacingItemCount += 1;
}else {
totalCellSpacingWidth -= self.contentEdgeInsetLeft;
}
//Automatic1
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----------------------
//collectionViewcellindexcontentOffset
__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) {
//JXCategoryViewcontentScrollViewdefaultSelectedIndexcontentScrollViewframezeroframelayoutSubviews
//JXSegmentedListContainerViewcontentScrollView使JXSegmentedListContainerView.superView
UIView *parentView = self.contentScrollView.superview;
while (parentView != nil && CGRectEqualToRect(parentView.frame, CGRectZero)) {
parentView = parentView.superview;
}
[parentView setNeedsLayout];
[parentView layoutIfNeeded];
}
//contentScrollViewcontentOffsetindex
[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) {
//indexindex
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];
//cellwidthcellscrollToItembucellWidthindexcell
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.x0
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.xmaxContentOffsetX
return;
}
ratio = MAX(0, MIN(self.dataSource.count - 1, ratio));
NSInteger baseIndex = floorf(ratio);
CGFloat remainderRatio = ratio - baseIndex;
if (remainderRatio == 0) {
//contentScrollView
//contentOffsetindex1contentOffsetCGPoint(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;
}
}
}
//remainderRatio0
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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:listUIViewControllerframe`[list listView].frame = cell.bounds;`list vc:
//- (void)loadView {
// self.view = [[UIView alloc] init];
//}
//UIViewControllerview使bug
listView = [list listView];
if ([list isKindOfClass:[UIViewController class]]) {
listView.frame = cell.contentView.bounds;
} else {
listView.frame = cell.bounds;
}
}
BOOL isAdded = NO;
for (UIView *subview in cell.contentView.subviews) {
if( listView != subview ) {
[subview removeFromSuperview];
} else {
isAdded = YES;
}
}
if( !isAdded && listView ) {
[cell.contentView addSubview:listView];
}
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

View File

@@ -0,0 +1,24 @@
//
// JXCategoryViewAnimator.h
// JXCategoryView
//
// Created by jiaxin on 2019/1/24.
// Copyright © 2019 jiaxin. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface JXCategoryViewAnimator : NSObject
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, copy) void(^progressCallback)(CGFloat percent);
@property (nonatomic, copy) void(^completeCallback)(void);
@property (readonly, getter=isExecuting) BOOL executing;
- (void)start;
- (void)stop;
- (void)invalid;
@end

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View 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

View 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

View 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;
//红点的圆角值。默认JXCategoryViewAutomaticDimensionself.dotSize.height/2
@property (nonatomic, assign) CGFloat dotCornerRadius;
//红点的颜色。默认:[UIColor redColor]
@property (nonatomic, strong) UIColor *dotColor;
/**
红点 x,y方向的偏移 +值:水平方向向右,竖直方向向下)
*/
@property (nonatomic, assign) CGPoint dotOffset;
@end

View 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

View 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

View 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

View 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

View 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

View 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.2imageZoomEnabled 为 YES 时才生效
@end

View 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

View File

@@ -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

View File

@@ -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];
}
}
//frame12
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

View File

@@ -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

View File

@@ -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%bigBallxsmallBall50%bigBallxsmallBallsmallBallx
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];
}
}
//frame12
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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%x50%xwidth
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];
}
//frame12
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

View File

@@ -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