增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

22
Pods/YYCategories/LICENSE generated Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 ibireme <ibireme@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

145
Pods/YYCategories/README.md generated Executable file
View File

@@ -0,0 +1,145 @@
YYCategories
==============
[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/ibireme/YYCategories/master/LICENSE)&nbsp;
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)&nbsp;
[![CocoaPods](http://img.shields.io/cocoapods/v/YYCategories.svg?style=flat)](http://cocoapods.org/?q=YYCategories)&nbsp;
[![CocoaPods](http://img.shields.io/cocoapods/p/YYCategories.svg?style=flat)](http://cocoapods.org/?q=YYCategories)&nbsp;
[![Support](https://img.shields.io/badge/support-iOS%206%2B%20-blue.svg?style=flat)](https://www.apple.com/nl/ios/)&nbsp;
[![Build Status](https://travis-ci.org/ibireme/YYCategories.svg?branch=master)](https://travis-ci.org/ibireme/YYCategories)
A set of useful categories for Foundation and UIKit.<br/>
(It's a component of [YYKit](https://github.com/ibireme/YYKit))
Documentation
==============
You can build and install docset use `Docset` scheme in Xcode, `appledoc` need to be pre-installed.
Or your can read the [Documentation](http://github.ibireme.com/doc/YYCategories/index.html) online.
Installation
==============
### CocoaPods
1. Add `pod 'YYCategories'` to your Podfile.
2. Run `pod install` or `pod update`.
3. Import \<YYCategories/YYCategories.h\>.
### Carthage
1. Add `github "ibireme/YYCategories"` to your Cartfile.
2. Run `carthage update --platform ios` and add the framework to your project.
3. Import \<YYCategories/YYCategories.h\>.
### Manually
1. Download all the files in the YYCategories subdirectory.
2. Add the source files to your Xcode project.
3. Add `-fno-objc-arc` compiler flag to `NSObject+YYAddForARC.m` and `NSThread+YYAdd.m`.
4. Link with required frameworks:
* UIKit
* CoreGraphics
* QuartzCore
* Accelerate
* ImageIO
* CoreText
* CoreFoundation
* libz
5. Import `YYCategories.h`.
Documentation
==============
Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYCategories/).<br/>
You can also install documentation locally using [appledoc](https://github.com/tomaz/appledoc).
Requirements
==============
This library requires `iOS 6.0+` and `Xcode 7.0+`.
Notice
==============
I want to use the APIs as if it was provided by system, so I don't add prefix in
these categories. This may cause some potential problemssuch as conflict with other libraries), so if you just need some pieces of code
in this project, pick them out and don't import the whole library.
License
==============
YYCategories is provided under the MIT license. See LICENSE file for details.
<br/><br/>
---
中文介绍
==============
功能丰富的 Category 类型工具库。<br/>
(该项目是 [YYKit](https://github.com/ibireme/YYKit) 组件之一)
文档
==============
你可以用 `Docset` scheme 来生成文档 (需要预先安装 appledoc),或者[在线查看](http://github.ibireme.com/doc/YYCategories/index.html)。
安装
==============
### CocoaPods
1. 在 Podfile 中添加 `pod 'YYCategories'`
2. 执行 `pod install``pod update`
3. 导入 \<YYCategories/YYCategories.h\>。
### Carthage
1. 在 Cartfile 中添加 `github "ibireme/YYCategories"`
2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。
3. 导入 \<YYCategories/YYCategories.h\>。
### 手动安装
1. 下载 YYCategories 文件夹内的所有内容。
2. 将 YYCategories 内的源文件添加(拖放)到你的工程。
3.`NSObject+YYAddForARC.m``NSThread+YYAdd.m` 添加编译参数 `-fno-objc-arc`
4. 链接以下 frameworks:
* UIKit
* CoreGraphics
* QuartzCore
* Accelerate
* ImageIO
* CoreText
* CoreFoundation
* libz
5. 导入 `YYCategories.h`
文档
==============
你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYCategories/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。
系统要求
==============
该项目最低支持 `iOS 6.0``Xcode 7.0`
注意
==============
我希望调用 API 时,有着和调用系统自带 API 一样的体验,所以我并没有为 Category 方法添加前缀。我已经用工具扫描过这个项目中的 API确保没有对系统 API 产生影响。我知道没有前缀的 Category 可能会带来麻烦(比如可能和其他某些类库产生冲突),所以如果你只需要其中少量代码,那最好将那段代码取出来,而不是导入整个库。
许可证
==============
YYCategories 使用 MIT 许可证,详情见 LICENSE 文件。

View File

@@ -0,0 +1,196 @@
//
// NSArray+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide some some common method for `NSArray`.
*/
@interface NSArray (YYAdd)
/**
Creates and returns an array from a specified property list data.
@param plist A property list data whose root object is an array.
@return A new array created from the plist data, or nil if an error occurs.
*/
+ (nullable NSArray *)arrayWithPlistData:(NSData *)plist;
/**
Creates and returns an array from a specified property list xml string.
@param plist A property list xml string whose root object is an array.
@return A new array created from the plist string, or nil if an error occurs.
*/
+ (nullable NSArray *)arrayWithPlistString:(NSString *)plist;
/**
Serialize the array to a binary property list data.
@return A bplist data, or nil if an error occurs.
*/
- (nullable NSData *)plistData;
/**
Serialize the array to a xml property list string.
@return A plist xml string, or nil if an error occurs.
*/
- (nullable NSString *)plistString;
/**
Returns the object located at a random index.
@return The object in the array with a random index value.
If the array is empty, returns nil.
*/
- (nullable id)randomObject;
/**
Returns the object located at index, or return nil when out of bounds.
It's similar to `objectAtIndex:`, but it never throw exception.
@param index The object located at index.
*/
- (nullable id)objectOrNilAtIndex:(NSUInteger)index;
/**
Convert object to json string. return nil if an error occurs.
NSString/NSNumber/NSDictionary/NSArray
*/
- (nullable NSString *)jsonStringEncoded;
/**
Convert object to json string formatted. return nil if an error occurs.
*/
- (nullable NSString *)jsonPrettyStringEncoded;
@end
/**
Provide some some common method for `NSMutableArray`.
*/
@interface NSMutableArray (YYAdd)
/**
Creates and returns an array from a specified property list data.
@param plist A property list data whose root object is an array.
@return A new array created from the plist data, or nil if an error occurs.
*/
+ (nullable NSMutableArray *)arrayWithPlistData:(NSData *)plist;
/**
Creates and returns an array from a specified property list xml string.
@param plist A property list xml string whose root object is an array.
@return A new array created from the plist string, or nil if an error occurs.
*/
+ (nullable NSMutableArray *)arrayWithPlistString:(NSString *)plist;
/**
Removes the object with the lowest-valued index in the array.
If the array is empty, this method has no effect.
@discussion Apple has implemented this method, but did not make it public.
Override for safe.
*/
- (void)removeFirstObject;
/**
Removes the object with the highest-valued index in the array.
If the array is empty, this method has no effect.
@discussion Apple's implementation said it raises an NSRangeException if the
array is empty, but in fact nothing will happen. Override for safe.
*/
- (void)removeLastObject;
/**
Removes and returns the object with the lowest-valued index in the array.
If the array is empty, it just returns nil.
@return The first object, or nil.
*/
- (nullable id)popFirstObject;
/**
Removes and returns the object with the highest-valued index in the array.
If the array is empty, it just returns nil.
@return The first object, or nil.
*/
- (nullable id)popLastObject;
/**
Inserts a given object at the end of the array.
@param anObject The object to add to the end of the array's content.
This value must not be nil. Raises an NSInvalidArgumentException if anObject is nil.
*/
- (void)appendObject:(id)anObject;
/**
Inserts a given object at the beginning of the array.
@param anObject The object to add to the end of the array's content.
This value must not be nil. Raises an NSInvalidArgumentException if anObject is nil.
*/
- (void)prependObject:(id)anObject;
/**
Adds the objects contained in another given array to the end of the receiving
array's content.
@param objects An array of objects to add to the end of the receiving array's
content. If the objects is empty or nil, this method has no effect.
*/
- (void)appendObjects:(NSArray *)objects;
/**
Adds the objects contained in another given array to the beginnin of the receiving
array's content.
@param objects An array of objects to add to the beginning of the receiving array's
content. If the objects is empty or nil, this method has no effect.
*/
- (void)prependObjects:(NSArray *)objects;
/**
Adds the objects contained in another given array at the index of the receiving
array's content.
@param objects An array of objects to add to the receiving array's
content. If the objects is empty or nil, this method has no effect.
@param index The index in the array at which to insert objects. This value must
not be greater than the count of elements in the array. Raises an
NSRangeException if index is greater than the number of elements in the array.
*/
- (void)insertObjects:(NSArray *)objects atIndex:(NSUInteger)index;
/**
Reverse the index of object in this array.
Example: Before @[ @1, @2, @3 ], After @[ @3, @2, @1 ].
*/
- (void)reverse;
/**
Sort the object in this array randomly.
*/
- (void)shuffle;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,172 @@
//
// NSArray+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSArray+YYAdd.h"
#import "YYCategoriesMacro.h"
#import "NSData+YYAdd.h"
YYSYNTH_DUMMY_CLASS(NSArray_YYAdd)
@implementation NSArray (YYAdd)
+ (NSArray *)arrayWithPlistData:(NSData *)plist {
if (!plist) return nil;
NSArray *array = [NSPropertyListSerialization propertyListWithData:plist options:NSPropertyListImmutable format:NULL error:NULL];
if ([array isKindOfClass:[NSArray class]]) return array;
return nil;
}
+ (NSArray *)arrayWithPlistString:(NSString *)plist {
if (!plist) return nil;
NSData* data = [plist dataUsingEncoding:NSUTF8StringEncoding];
return [self arrayWithPlistData:data];
}
- (NSData *)plistData {
return [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListBinaryFormat_v1_0 options:kNilOptions error:NULL];
}
- (NSString *)plistString {
NSData *xmlData = [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListXMLFormat_v1_0 options:kNilOptions error:NULL];
if (xmlData) return xmlData.utf8String;
return nil;
}
- (id)randomObject {
if (self.count) {
return self[arc4random_uniform((u_int32_t)self.count)];
}
return nil;
}
- (id)objectOrNilAtIndex:(NSUInteger)index {
return index < self.count ? self[index] : nil;
}
- (NSString *)jsonStringEncoded {
if ([NSJSONSerialization isValidJSONObject:self]) {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:0 error:&error];
NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return json;
}
return nil;
}
- (NSString *)jsonPrettyStringEncoded {
if ([NSJSONSerialization isValidJSONObject:self]) {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted error:&error];
NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return json;
}
return nil;
}
@end
@implementation NSMutableArray (YYAdd)
+ (NSMutableArray *)arrayWithPlistData:(NSData *)plist {
if (!plist) return nil;
NSMutableArray *array = [NSPropertyListSerialization propertyListWithData:plist options:NSPropertyListMutableContainersAndLeaves format:NULL error:NULL];
if ([array isKindOfClass:[NSMutableArray class]]) return array;
return nil;
}
+ (NSMutableArray *)arrayWithPlistString:(NSString *)plist {
if (!plist) return nil;
NSData* data = [plist dataUsingEncoding:NSUTF8StringEncoding];
return [self arrayWithPlistData:data];
}
- (void)removeFirstObject {
if (self.count) {
[self removeObjectAtIndex:0];
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (void)removeLastObject {
if (self.count) {
[self removeObjectAtIndex:self.count - 1];
}
}
#pragma clang diagnostic pop
- (id)popFirstObject {
id obj = nil;
if (self.count) {
obj = self.firstObject;
[self removeFirstObject];
}
return obj;
}
- (id)popLastObject {
id obj = nil;
if (self.count) {
obj = self.lastObject;
[self removeLastObject];
}
return obj;
}
- (void)appendObject:(id)anObject {
[self addObject:anObject];
}
- (void)prependObject:(id)anObject {
[self insertObject:anObject atIndex:0];
}
- (void)appendObjects:(NSArray *)objects {
if (!objects) return;
[self addObjectsFromArray:objects];
}
- (void)prependObjects:(NSArray *)objects {
if (!objects) return;
NSUInteger i = 0;
for (id obj in objects) {
[self insertObject:obj atIndex:i++];
}
}
- (void)insertObjects:(NSArray *)objects atIndex:(NSUInteger)index {
NSUInteger i = index;
for (id obj in objects) {
[self insertObject:obj atIndex:i++];
}
}
- (void)reverse {
NSUInteger count = self.count;
int mid = floor(count / 2.0);
for (NSUInteger i = 0; i < mid; i++) {
[self exchangeObjectAtIndex:i withObjectAtIndex:(count - (i + 1))];
}
}
- (void)shuffle {
for (NSUInteger i = self.count; i > 1; i--) {
[self exchangeObjectAtIndex:(i - 1)
withObjectAtIndex:arc4random_uniform((u_int32_t)i)];
}
}
@end

View File

@@ -0,0 +1,92 @@
//
// NSBundle+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `NSBundle` to get resource by @2x or @3x...
Example: ico.png, ico@2x.png, ico@3x.png. Call scaledResource:@"ico" ofType:@"png"
on iPhone6 will return "ico@2x.png"'s path.
*/
@interface NSBundle (YYAdd)
/**
An array of NSNumber objects, shows the best order for path scale search.
e.g. iPhone3GS:@[@1,@2,@3] iPhone5:@[@2,@3,@1] iPhone6 Plus:@[@3,@2,@1]
*/
+ (NSArray<NSNumber *> *)preferredScales;
/**
Returns the full pathname for the resource file identified by the specified
name and extension and residing in a given bundle directory. It first search
the file with current screen's scale (such as @2x), then search from higher
scale to lower scale.
@param name The name of a resource file contained in the directory
specified by bundlePath.
@param ext If extension is an empty string or nil, the extension is
assumed not to exist and the file is the first file encountered that exactly matches name.
@param bundlePath The path of a top-level bundle directory. This must be a
valid path. For example, to specify the bundle directory for a Mac app, you
might specify the path /Applications/MyApp.app.
@return The full pathname for the resource file or nil if the file could not be
located. This method also returns nil if the bundle specified by the bundlePath
parameter does not exist or is not a readable directory.
*/
+ (nullable NSString *)pathForScaledResource:(NSString *)name
ofType:(nullable nullable NSString *)ext
inDirectory:(NSString *)bundlePath;
/**
Returns the full pathname for the resource identified by the specified name and
file extension. It first search the file with current screen's scale (such as @2x),
then search from higher scale to lower scale.
@param name The name of the resource file. If name is an empty string or
nil, returns the first file encountered of the supplied type.
@param ext If extension is an empty string or nil, the extension is
assumed not to exist and the file is the first file encountered that exactly matches name.
@return The full pathname for the resource file or nil if the file could not be located.
*/
- (nullable NSString *)pathForScaledResource:(NSString *)name ofType:(nullable NSString *)ext;
/**
Returns the full pathname for the resource identified by the specified name and
file extension and located in the specified bundle subdirectory. It first search
the file with current screen's scale (such as @2x), then search from higher
scale to lower scale.
@param name The name of the resource file.
@param ext If extension is an empty string or nil, all the files in
subpath and its subdirectories are returned. If an extension is provided the
subdirectories are not searched.
@param subpath The name of the bundle subdirectory. Can be nil.
@return The full pathname for the resource file or nil if the file could not be located.
*/
- (nullable NSString *)pathForScaledResource:(NSString *)name
ofType:(nullable NSString *)ext
inDirectory:(nullable NSString *)subpath;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,87 @@
//
// NSBundle+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSBundle+YYAdd.h"
#import "NSString+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(NSBundle_YYAdd)
@implementation NSBundle (YYAdd)
+ (NSArray *)preferredScales {
static NSArray *scales;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CGFloat screenScale = [UIScreen mainScreen].scale;
if (screenScale <= 1) {
scales = @[@1,@2,@3];
} else if (screenScale <= 2) {
scales = @[@2,@3,@1];
} else {
scales = @[@3,@2,@1];
}
});
return scales;
}
+ (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)bundlePath {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return [self pathForResource:name ofType:ext inDirectory:bundlePath];
NSString *path = nil;
NSArray *scales = [self preferredScales];
for (int s = 0; s < scales.count; s++) {
CGFloat scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = ext.length ? [name stringByAppendingNameScale:scale]
: [name stringByAppendingPathScale:scale];
path = [self pathForResource:scaledName ofType:ext inDirectory:bundlePath];
if (path) break;
}
return path;
}
- (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return [self pathForResource:name ofType:ext];
NSString *path = nil;
NSArray *scales = [NSBundle preferredScales];
for (int s = 0; s < scales.count; s++) {
CGFloat scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = ext.length ? [name stringByAppendingNameScale:scale]
: [name stringByAppendingPathScale:scale];
path = [self pathForResource:scaledName ofType:ext];
if (path) break;
}
return path;
}
- (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)subpath {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return [self pathForResource:name ofType:ext];
NSString *path = nil;
NSArray *scales = [NSBundle preferredScales];
for (int s = 0; s < scales.count; s++) {
CGFloat scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = ext.length ? [name stringByAppendingNameScale:scale]
: [name stringByAppendingPathScale:scale];
path = [self pathForResource:scaledName ofType:ext inDirectory:subpath];
if (path) break;
}
return path;
}
@end

View File

@@ -0,0 +1,310 @@
//
// NSData+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide hash, encrypt, encode and some common method for `NSData`.
*/
@interface NSData (YYAdd)
#pragma mark - Hash
///=============================================================================
/// @name Hash
///=============================================================================
/**
Returns a lowercase NSString for md2 hash.
*/
- (NSString *)md2String;
/**
Returns an NSData for md2 hash.
*/
- (NSData *)md2Data;
/**
Returns a lowercase NSString for md4 hash.
*/
- (NSString *)md4String;
/**
Returns an NSData for md4 hash.
*/
- (NSData *)md4Data;
/**
Returns a lowercase NSString for md5 hash.
*/
- (NSString *)md5String;
/**
Returns an NSData for md5 hash.
*/
- (NSData *)md5Data;
/**
Returns a lowercase NSString for sha1 hash.
*/
- (NSString *)sha1String;
/**
Returns an NSData for sha1 hash.
*/
- (NSData *)sha1Data;
/**
Returns a lowercase NSString for sha224 hash.
*/
- (NSString *)sha224String;
/**
Returns an NSData for sha224 hash.
*/
- (NSData *)sha224Data;
/**
Returns a lowercase NSString for sha256 hash.
*/
- (NSString *)sha256String;
/**
Returns an NSData for sha256 hash.
*/
- (NSData *)sha256Data;
/**
Returns a lowercase NSString for sha384 hash.
*/
- (NSString *)sha384String;
/**
Returns an NSData for sha384 hash.
*/
- (NSData *)sha384Data;
/**
Returns a lowercase NSString for sha512 hash.
*/
- (NSString *)sha512String;
/**
Returns an NSData for sha512 hash.
*/
- (NSData *)sha512Data;
/**
Returns a lowercase NSString for hmac using algorithm md5 with key.
@param key The hmac key.
*/
- (NSString *)hmacMD5StringWithKey:(NSString *)key;
/**
Returns an NSData for hmac using algorithm md5 with key.
@param key The hmac key.
*/
- (NSData *)hmacMD5DataWithKey:(NSData *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha1 with key.
@param key The hmac key.
*/
- (NSString *)hmacSHA1StringWithKey:(NSString *)key;
/**
Returns an NSData for hmac using algorithm sha1 with key.
@param key The hmac key.
*/
- (NSData *)hmacSHA1DataWithKey:(NSData *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha224 with key.
@param key The hmac key.
*/
- (NSString *)hmacSHA224StringWithKey:(NSString *)key;
/**
Returns an NSData for hmac using algorithm sha224 with key.
@param key The hmac key.
*/
- (NSData *)hmacSHA224DataWithKey:(NSData *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha256 with key.
@param key The hmac key.
*/
- (NSString *)hmacSHA256StringWithKey:(NSString *)key;
/**
Returns an NSData for hmac using algorithm sha256 with key.
@param key The hmac key.
*/
- (NSData *)hmacSHA256DataWithKey:(NSData *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha384 with key.
@param key The hmac key.
*/
- (NSString *)hmacSHA384StringWithKey:(NSString *)key;
/**
Returns an NSData for hmac using algorithm sha384 with key.
@param key The hmac key.
*/
- (NSData *)hmacSHA384DataWithKey:(NSData *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha512 with key.
@param key The hmac key.
*/
- (NSString *)hmacSHA512StringWithKey:(NSString *)key;
/**
Returns an NSData for hmac using algorithm sha512 with key.
@param key The hmac key.
*/
- (NSData *)hmacSHA512DataWithKey:(NSData *)key;
/**
Returns a lowercase NSString for crc32 hash.
*/
- (NSString *)crc32String;
/**
Returns crc32 hash.
*/
- (uint32_t)crc32;
#pragma mark - Encrypt and Decrypt
///=============================================================================
/// @name Encrypt and Decrypt
///=============================================================================
/**
Returns an encrypted NSData using AES.
@param key A key length of 16, 24 or 32 (128, 192 or 256bits).
@param iv An initialization vector length of 16(128bits).
Pass nil when you don't want to use iv.
@return An NSData encrypted, or nil if an error occurs.
*/
- (nullable NSData *)aes256EncryptWithKey:(NSData *)key iv:(nullable NSData *)iv;
/**
Returns an decrypted NSData using AES.
@param key A key length of 16, 24 or 32 (128, 192 or 256bits).
@param iv An initialization vector length of 16(128bits).
Pass nil when you don't want to use iv.
@return An NSData decrypted, or nil if an error occurs.
*/
- (nullable NSData *)aes256DecryptWithkey:(NSData *)key iv:(nullable NSData *)iv;
#pragma mark - Encode and decode
///=============================================================================
/// @name Encode and decode
///=============================================================================
/**
Returns string decoded in UTF8.
*/
- (nullable NSString *)utf8String;
/**
Returns a uppercase NSString in HEX.
*/
- (nullable NSString *)hexString;
/**
Returns an NSData from hex string.
@param hexString The hex string which is case insensitive.
@return a new NSData, or nil if an error occurs.
*/
+ (nullable NSData *)dataWithHexString:(NSString *)hexString;
/**
Returns an NSString for base64 encoded.
*/
- (nullable NSString *)base64EncodedString;
/**
Returns an NSData from base64 encoded string.
@warning This method has been implemented in iOS7.
@param base64EncodedString The encoded string.
*/
+ (nullable NSData *)dataWithBase64EncodedString:(NSString *)base64EncodedString;
/**
Returns an NSDictionary or NSArray for decoded self.
Returns nil if an error occurs.
*/
- (nullable id)jsonValueDecoded;
#pragma mark - Inflate and deflate
///=============================================================================
/// @name Inflate and deflate
///=============================================================================
/**
Decompress data from gzip data.
@return Inflated data.
*/
- (nullable NSData *)gzipInflate;
/**
Comperss data to gzip in default compresssion level.
@return Deflated data.
*/
- (nullable NSData *)gzipDeflate;
/**
Decompress data from zlib-compressed data.
@return Inflated data.
*/
- (nullable NSData *)zlibInflate;
/**
Comperss data to zlib-compressed in default compresssion level.
@return Deflated data.
*/
- (nullable NSData *)zlibDeflate;
#pragma mark - Others
///=============================================================================
/// @name Others
///=============================================================================
/**
Create data from the file in main bundle (similar to [UIImage imageNamed:]).
@param name The file name (in main bundle).
@return A new data create from the file.
*/
+ (nullable NSData *)dataNamed:(NSString *)name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,642 @@
//
// NSData+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSData+YYAdd.h"
#import "YYCategoriesMacro.h"
#include <CommonCrypto/CommonCrypto.h>
#include <zlib.h>
YYSYNTH_DUMMY_CLASS(NSData_YYAdd)
@implementation NSData (YYAdd)
- (NSString *)md2String {
unsigned char result[CC_MD2_DIGEST_LENGTH];
CC_MD2(self.bytes, (CC_LONG)self.length, result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
- (NSData *)md2Data {
unsigned char result[CC_MD2_DIGEST_LENGTH];
CC_MD2(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_MD2_DIGEST_LENGTH];
}
- (NSString *)md4String {
unsigned char result[CC_MD4_DIGEST_LENGTH];
CC_MD4(self.bytes, (CC_LONG)self.length, result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
- (NSData *)md4Data {
unsigned char result[CC_MD4_DIGEST_LENGTH];
CC_MD4(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_MD4_DIGEST_LENGTH];
}
- (NSString *)md5String {
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(self.bytes, (CC_LONG)self.length, result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
- (NSData *)md5Data {
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}
- (NSString *)sha1String {
unsigned char result[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(self.bytes, (CC_LONG)self.length, result);
NSMutableString *hash = [NSMutableString
stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
- (NSData *)sha1Data {
unsigned char result[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH];
}
- (NSString *)sha224String {
unsigned char result[CC_SHA224_DIGEST_LENGTH];
CC_SHA224(self.bytes, (CC_LONG)self.length, result);
NSMutableString *hash = [NSMutableString
stringWithCapacity:CC_SHA224_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA224_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
- (NSData *)sha224Data {
unsigned char result[CC_SHA224_DIGEST_LENGTH];
CC_SHA224(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_SHA224_DIGEST_LENGTH];
}
- (NSString *)sha256String {
unsigned char result[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(self.bytes, (CC_LONG)self.length, result);
NSMutableString *hash = [NSMutableString
stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
- (NSData *)sha256Data {
unsigned char result[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_SHA256_DIGEST_LENGTH];
}
- (NSString *)sha384String {
unsigned char result[CC_SHA384_DIGEST_LENGTH];
CC_SHA384(self.bytes, (CC_LONG)self.length, result);
NSMutableString *hash = [NSMutableString
stringWithCapacity:CC_SHA384_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA384_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
- (NSData *)sha384Data {
unsigned char result[CC_SHA384_DIGEST_LENGTH];
CC_SHA384(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_SHA384_DIGEST_LENGTH];
}
- (NSString *)sha512String {
unsigned char result[CC_SHA512_DIGEST_LENGTH];
CC_SHA512(self.bytes, (CC_LONG)self.length, result);
NSMutableString *hash = [NSMutableString
stringWithCapacity:CC_SHA512_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA512_DIGEST_LENGTH; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
- (NSData *)sha512Data {
unsigned char result[CC_SHA512_DIGEST_LENGTH];
CC_SHA512(self.bytes, (CC_LONG)self.length, result);
return [NSData dataWithBytes:result length:CC_SHA512_DIGEST_LENGTH];
}
- (NSString *)hmacStringUsingAlg:(CCHmacAlgorithm)alg withKey:(NSString *)key {
size_t size;
switch (alg) {
case kCCHmacAlgMD5: size = CC_MD5_DIGEST_LENGTH; break;
case kCCHmacAlgSHA1: size = CC_SHA1_DIGEST_LENGTH; break;
case kCCHmacAlgSHA224: size = CC_SHA224_DIGEST_LENGTH; break;
case kCCHmacAlgSHA256: size = CC_SHA256_DIGEST_LENGTH; break;
case kCCHmacAlgSHA384: size = CC_SHA384_DIGEST_LENGTH; break;
case kCCHmacAlgSHA512: size = CC_SHA512_DIGEST_LENGTH; break;
default: return nil;
}
unsigned char result[size];
const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding];
CCHmac(alg, cKey, strlen(cKey), self.bytes, self.length, result);
NSMutableString *hash = [NSMutableString stringWithCapacity:size * 2];
for (int i = 0; i < size; i++) {
[hash appendFormat:@"%02x", result[i]];
}
return hash;
}
- (NSData *)hmacDataUsingAlg:(CCHmacAlgorithm)alg withKey:(NSData *)key {
size_t size;
switch (alg) {
case kCCHmacAlgMD5: size = CC_MD5_DIGEST_LENGTH; break;
case kCCHmacAlgSHA1: size = CC_SHA1_DIGEST_LENGTH; break;
case kCCHmacAlgSHA224: size = CC_SHA224_DIGEST_LENGTH; break;
case kCCHmacAlgSHA256: size = CC_SHA256_DIGEST_LENGTH; break;
case kCCHmacAlgSHA384: size = CC_SHA384_DIGEST_LENGTH; break;
case kCCHmacAlgSHA512: size = CC_SHA512_DIGEST_LENGTH; break;
default: return nil;
}
unsigned char result[size];
CCHmac(alg, [key bytes], key.length, self.bytes, self.length, result);
return [NSData dataWithBytes:result length:size];
}
- (NSString *)hmacMD5StringWithKey:(NSString *)key {
return [self hmacStringUsingAlg:kCCHmacAlgMD5 withKey:key];
}
- (NSData *)hmacMD5DataWithKey:(NSData *)key {
return [self hmacDataUsingAlg:kCCHmacAlgMD5 withKey:key];
}
- (NSString *)hmacSHA1StringWithKey:(NSString *)key {
return [self hmacStringUsingAlg:kCCHmacAlgSHA1 withKey:key];
}
- (NSData *)hmacSHA1DataWithKey:(NSData *)key {
return [self hmacDataUsingAlg:kCCHmacAlgSHA1 withKey:key];
}
- (NSString *)hmacSHA224StringWithKey:(NSString *)key {
return [self hmacStringUsingAlg:kCCHmacAlgSHA224 withKey:key];
}
- (NSData *)hmacSHA224DataWithKey:(NSData *)key {
return [self hmacDataUsingAlg:kCCHmacAlgSHA224 withKey:key];
}
- (NSString *)hmacSHA256StringWithKey:(NSString *)key {
return [self hmacStringUsingAlg:kCCHmacAlgSHA256 withKey:key];
}
- (NSData *)hmacSHA256DataWithKey:(NSData *)key {
return [self hmacDataUsingAlg:kCCHmacAlgSHA256 withKey:key];
}
- (NSString *)hmacSHA384StringWithKey:(NSString *)key {
return [self hmacStringUsingAlg:kCCHmacAlgSHA384 withKey:key];
}
- (NSData *)hmacSHA384DataWithKey:(NSData *)key {
return [self hmacDataUsingAlg:kCCHmacAlgSHA384 withKey:key];
}
- (NSString *)hmacSHA512StringWithKey:(NSString *)key {
return [self hmacStringUsingAlg:kCCHmacAlgSHA512 withKey:key];
}
- (NSData *)hmacSHA512DataWithKey:(NSData *)key {
return [self hmacDataUsingAlg:kCCHmacAlgSHA512 withKey:key];
}
- (NSString *)crc32String {
uLong result = crc32(0, self.bytes, (uInt)self.length);
return [NSString stringWithFormat:@"%08x", (uint32_t)result];
}
- (uint32_t)crc32 {
uLong result = crc32(0, self.bytes, (uInt)self.length);
return (uint32_t)result;
}
- (NSData *)aes256EncryptWithKey:(NSData *)key iv:(NSData *)iv {
if (key.length != 16 && key.length != 24 && key.length != 32) {
return nil;
}
if (iv.length != 16 && iv.length != 0) {
return nil;
}
NSData *result = nil;
size_t bufferSize = self.length + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
if (!buffer) return nil;
size_t encryptedSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
iv.bytes,
self.bytes,
self.length,
buffer,
bufferSize,
&encryptedSize);
if (cryptStatus == kCCSuccess) {
result = [[NSData alloc]initWithBytes:buffer length:encryptedSize];
free(buffer);
return result;
} else {
free(buffer);
return nil;
}
}
- (NSData *)aes256DecryptWithkey:(NSData *)key iv:(NSData *)iv {
if (key.length != 16 && key.length != 24 && key.length != 32) {
return nil;
}
if (iv.length != 16 && iv.length != 0) {
return nil;
}
NSData *result = nil;
size_t bufferSize = self.length + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
if (!buffer) return nil;
size_t encryptedSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
iv.bytes,
self.bytes,
self.length,
buffer,
bufferSize,
&encryptedSize);
if (cryptStatus == kCCSuccess) {
result = [[NSData alloc]initWithBytes:buffer length:encryptedSize];
free(buffer);
return result;
} else {
free(buffer);
return nil;
}
}
- (NSString *)utf8String {
if (self.length > 0) {
return [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];
}
return @"";
}
- (NSString *)hexString {
NSUInteger length = self.length;
NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];
const unsigned char *byte = self.bytes;
for (int i = 0; i < length; i++, byte++) {
[result appendFormat:@"%02X", *byte];
}
return result;
}
+ (NSData *)dataWithHexString:(NSString *)hexStr {
hexStr = [hexStr stringByReplacingOccurrencesOfString:@" " withString:@""];
hexStr = [hexStr lowercaseString];
NSUInteger len = hexStr.length;
if (!len) return nil;
unichar *buf = malloc(sizeof(unichar) * len);
if (!buf) return nil;
[hexStr getCharacters:buf range:NSMakeRange(0, len)];
NSMutableData *result = [NSMutableData data];
unsigned char bytes;
char str[3] = { '\0', '\0', '\0' };
int i;
for (i = 0; i < len / 2; i++) {
str[0] = buf[i * 2];
str[1] = buf[i * 2 + 1];
bytes = strtol(str, NULL, 16);
[result appendBytes:&bytes length:1];
}
free(buf);
return result;
}
static const char base64EncodingTable[64]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const short base64DecodingTable[256] = {
-2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
-2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
-2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
};
- (NSString *)base64EncodedString {
NSUInteger length = self.length;
if (length == 0)
return @"";
NSUInteger out_length = ((length + 2) / 3) * 4;
uint8_t *output = malloc(((out_length + 2) / 3) * 4);
if (output == NULL)
return nil;
const char *input = self.bytes;
NSInteger i, value;
for (i = 0; i < length; i += 3) {
value = 0;
for (NSInteger j = i; j < i + 3; j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger index = (i / 3) * 4;
output[index + 0] = base64EncodingTable[(value >> 18) & 0x3F];
output[index + 1] = base64EncodingTable[(value >> 12) & 0x3F];
output[index + 2] = ((i + 1) < length)
? base64EncodingTable[(value >> 6) & 0x3F]
: '=';
output[index + 3] = ((i + 2) < length)
? base64EncodingTable[(value >> 0) & 0x3F]
: '=';
}
NSString *base64 = [[NSString alloc] initWithBytes:output
length:out_length
encoding:NSASCIIStringEncoding];
free(output);
return base64;
}
+ (NSData *)dataWithBase64EncodedString:(NSString *)base64EncodedString {
NSInteger length = base64EncodedString.length;
const char *string = [base64EncodedString cStringUsingEncoding:NSASCIIStringEncoding];
if (string == NULL)
return nil;
while (length > 0 && string[length - 1] == '=')
length--;
NSInteger outputLength = length * 3 / 4;
NSMutableData *data = [NSMutableData dataWithLength:outputLength];
if (data == nil)
return nil;
if (length == 0)
return data;
uint8_t *output = data.mutableBytes;
NSInteger inputPoint = 0;
NSInteger outputPoint = 0;
while (inputPoint < length) {
char i0 = string[inputPoint++];
char i1 = string[inputPoint++];
char i2 = inputPoint < length ? string[inputPoint++] : 'A';
char i3 = inputPoint < length ? string[inputPoint++] : 'A';
output[outputPoint++] = (base64DecodingTable[i0] << 2)
| (base64DecodingTable[i1] >> 4);
if (outputPoint < outputLength) {
output[outputPoint++] = ((base64DecodingTable[i1] & 0xf) << 4)
| (base64DecodingTable[i2] >> 2);
}
if (outputPoint < outputLength) {
output[outputPoint++] = ((base64DecodingTable[i2] & 0x3) << 6)
| base64DecodingTable[i3];
}
}
return data;
}
- (id)jsonValueDecoded {
NSError *error = nil;
id value = [NSJSONSerialization JSONObjectWithData:self options:kNilOptions error:&error];
if (error) {
NSLog(@"jsonValueDecoded error:%@", error);
}
return value;
}
- (NSData *)gzipInflate {
if ([self length] == 0) return self;
unsigned full_length = (unsigned)[self length];
unsigned half_length = (unsigned)[self length] / 2;
NSMutableData *decompressed = [NSMutableData
dataWithLength:full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef *)[self bytes];
strm.avail_in = (unsigned)[self length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit2(&strm, (15 + 32)) != Z_OK) return nil;
while (!done) {
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length])
[decompressed increaseLengthBy:half_length];
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = (uInt)([decompressed length] - strm.total_out);
// Inflate another chunk.
status = inflate(&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END) done = YES;
else if (status != Z_OK) break;
}
if (inflateEnd(&strm) != Z_OK) return nil;
// Set real length.
if (done) {
[decompressed setLength:strm.total_out];
return [NSData dataWithData:decompressed];
} else return nil;
}
- (NSData *)gzipDeflate {
if ([self length] == 0) return self;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in = (Bytef *)[self bytes];
strm.avail_in = (uInt)[self length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15 + 16),
8, Z_DEFAULT_STRATEGY) != Z_OK)
return nil;
// 16K chunks for expansion
NSMutableData *compressed = [NSMutableData dataWithLength:16384];
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy:16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = (uInt)([compressed length] - strm.total_out);
deflate(&strm, Z_FINISH);
}
while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength:strm.total_out];
return [NSData dataWithData:compressed];
}
- (NSData *)zlibInflate {
if ([self length] == 0) return self;
NSUInteger full_length = [self length];
NSUInteger half_length = [self length] / 2;
NSMutableData *decompressed = [NSMutableData
dataWithLength:full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef *)[self bytes];
strm.avail_in = (uInt)full_length;
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit(&strm) != Z_OK) return nil;
while (!done) {
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length])
[decompressed increaseLengthBy:half_length];
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = (uInt)([decompressed length] - strm.total_out);
// Inflate another chunk.
status = inflate(&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END) done = YES;
else if (status != Z_OK) break;
}
if (inflateEnd(&strm) != Z_OK) return nil;
// Set real length.
if (done) {
[decompressed setLength:strm.total_out];
return [NSData dataWithData:decompressed];
} else return nil;
}
- (NSData *)zlibDeflate {
if ([self length] == 0) return self;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in = (Bytef *)[self bytes];
strm.avail_in = (uInt)[self length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) return nil;
// 16K chuncks for expansion
NSMutableData *compressed = [NSMutableData dataWithLength:16384];
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy:16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = (uInt)([compressed length] - strm.total_out);
deflate(&strm, Z_FINISH);
}
while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength:strm.total_out];
return [NSData dataWithData:compressed];
}
+ (NSData *)dataNamed:(NSString *)name {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@""];
if (!path) return nil;
NSData *data = [NSData dataWithContentsOfFile:path];
return data;
}
@end

View File

@@ -0,0 +1,188 @@
//
// NSDate+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `NSDate`.
*/
@interface NSDate (YYAdd)
#pragma mark - Component Properties
///=============================================================================
/// @name Component Properties
///=============================================================================
@property (nonatomic, readonly) NSInteger year; ///< Year component
@property (nonatomic, readonly) NSInteger month; ///< Month component (1~12)
@property (nonatomic, readonly) NSInteger day; ///< Day component (1~31)
@property (nonatomic, readonly) NSInteger hour; ///< Hour component (0~23)
@property (nonatomic, readonly) NSInteger minute; ///< Minute component (0~59)
@property (nonatomic, readonly) NSInteger second; ///< Second component (0~59)
@property (nonatomic, readonly) NSInteger nanosecond; ///< Nanosecond component
@property (nonatomic, readonly) NSInteger weekday; ///< Weekday component (1~7, first day is based on user setting)
@property (nonatomic, readonly) NSInteger weekdayOrdinal; ///< WeekdayOrdinal component
@property (nonatomic, readonly) NSInteger weekOfMonth; ///< WeekOfMonth component (1~5)
@property (nonatomic, readonly) NSInteger weekOfYear; ///< WeekOfYear component (1~53)
@property (nonatomic, readonly) NSInteger yearForWeekOfYear; ///< YearForWeekOfYear component
@property (nonatomic, readonly) NSInteger quarter; ///< Quarter component
@property (nonatomic, readonly) BOOL isLeapMonth; ///< Weather the month is leap month
@property (nonatomic, readonly) BOOL isLeapYear; ///< Weather the year is leap year
@property (nonatomic, readonly) BOOL isToday; ///< Weather date is today (based on current locale)
@property (nonatomic, readonly) BOOL isYesterday; ///< Weather date is yesterday (based on current locale)
#pragma mark - Date modify
///=============================================================================
/// @name Date modify
///=============================================================================
/**
Returns a date representing the receiver date shifted later by the provided number of years.
@param years Number of years to add.
@return Date modified by the number of desired years.
*/
- (nullable NSDate *)dateByAddingYears:(NSInteger)years;
/**
Returns a date representing the receiver date shifted later by the provided number of months.
@param months Number of months to add.
@return Date modified by the number of desired months.
*/
- (nullable NSDate *)dateByAddingMonths:(NSInteger)months;
/**
Returns a date representing the receiver date shifted later by the provided number of weeks.
@param weeks Number of weeks to add.
@return Date modified by the number of desired weeks.
*/
- (nullable NSDate *)dateByAddingWeeks:(NSInteger)weeks;
/**
Returns a date representing the receiver date shifted later by the provided number of days.
@param days Number of days to add.
@return Date modified by the number of desired days.
*/
- (nullable NSDate *)dateByAddingDays:(NSInteger)days;
/**
Returns a date representing the receiver date shifted later by the provided number of hours.
@param hours Number of hours to add.
@return Date modified by the number of desired hours.
*/
- (nullable NSDate *)dateByAddingHours:(NSInteger)hours;
/**
Returns a date representing the receiver date shifted later by the provided number of minutes.
@param minutes Number of minutes to add.
@return Date modified by the number of desired minutes.
*/
- (nullable NSDate *)dateByAddingMinutes:(NSInteger)minutes;
/**
Returns a date representing the receiver date shifted later by the provided number of seconds.
@param seconds Number of seconds to add.
@return Date modified by the number of desired seconds.
*/
- (nullable NSDate *)dateByAddingSeconds:(NSInteger)seconds;
#pragma mark - Date Format
///=============================================================================
/// @name Date Format
///=============================================================================
/**
Returns a formatted string representing this date.
see http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
for format description.
@param format String representing the desired date format.
e.g. @"yyyy-MM-dd HH:mm:ss"
@return NSString representing the formatted date string.
*/
- (nullable NSString *)stringWithFormat:(NSString *)format;
/**
Returns a formatted string representing this date.
see http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
for format description.
@param format String representing the desired date format.
e.g. @"yyyy-MM-dd HH:mm:ss"
@param timeZone Desired time zone.
@param locale Desired locale.
@return NSString representing the formatted date string.
*/
- (nullable NSString *)stringWithFormat:(NSString *)format
timeZone:(nullable NSTimeZone *)timeZone
locale:(nullable NSLocale *)locale;
/**
Returns a string representing this date in ISO8601 format.
e.g. "2010-07-09T16:13:30+12:00"
@return NSString representing the formatted date string in ISO8601.
*/
- (nullable NSString *)stringWithISOFormat;
/**
Returns a date parsed from given string interpreted using the format.
@param dateString The string to parse.
@param format The string's date format.
@return A date representation of string interpreted using the format.
If can not parse the string, returns nil.
*/
+ (nullable NSDate *)dateWithString:(NSString *)dateString format:(NSString *)format;
/**
Returns a date parsed from given string interpreted using the format.
@param dateString The string to parse.
@param format The string's date format.
@param timeZone The time zone, can be nil.
@param locale The locale, can be nil.
@return A date representation of string interpreted using the format.
If can not parse the string, returns nil.
*/
+ (nullable NSDate *)dateWithString:(NSString *)dateString
format:(NSString *)format
timeZone:(nullable NSTimeZone *)timeZone
locale:(nullable NSLocale *)locale;
/**
Returns a date parsed from given string interpreted using the ISO8601 format.
@param dateString The date string in ISO8601 format. e.g. "2010-07-09T16:13:30+12:00"
@return A date representation of string interpreted using the format.
If can not parse the string, returns nil.
*/
+ (nullable NSDate *)dateWithISOFormatString:(NSString *)dateString;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,188 @@
//
// NSDate+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSDate+YYAdd.h"
#import "YYCategoriesMacro.h"
#import <time.h>
YYSYNTH_DUMMY_CLASS(NSDate_YYAdd)
@implementation NSDate (YYAdd)
- (NSInteger)year {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:self] year];
}
- (NSInteger)month {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitMonth fromDate:self] month];
}
- (NSInteger)day {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitDay fromDate:self] day];
}
- (NSInteger)hour {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitHour fromDate:self] hour];
}
- (NSInteger)minute {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitMinute fromDate:self] minute];
}
- (NSInteger)second {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitSecond fromDate:self] second];
}
- (NSInteger)nanosecond {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitSecond fromDate:self] nanosecond];
}
- (NSInteger)weekday {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekday fromDate:self] weekday];
}
- (NSInteger)weekdayOrdinal {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekdayOrdinal fromDate:self] weekdayOrdinal];
}
- (NSInteger)weekOfMonth {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekOfMonth fromDate:self] weekOfMonth];
}
- (NSInteger)weekOfYear {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekOfYear fromDate:self] weekOfYear];
}
- (NSInteger)yearForWeekOfYear {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitYearForWeekOfYear fromDate:self] yearForWeekOfYear];
}
- (NSInteger)quarter {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitQuarter fromDate:self] quarter];
}
- (BOOL)isLeapMonth {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitQuarter fromDate:self] isLeapMonth];
}
- (BOOL)isLeapYear {
NSUInteger year = self.year;
return ((year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)));
}
- (BOOL)isToday {
if (fabs(self.timeIntervalSinceNow) >= 60 * 60 * 24) return NO;
return [NSDate new].day == self.day;
}
- (BOOL)isYesterday {
NSDate *added = [self dateByAddingDays:1];
return [added isToday];
}
- (NSDate *)dateByAddingYears:(NSInteger)years {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:years];
return [calendar dateByAddingComponents:components toDate:self options:0];
}
- (NSDate *)dateByAddingMonths:(NSInteger)months {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setMonth:months];
return [calendar dateByAddingComponents:components toDate:self options:0];
}
- (NSDate *)dateByAddingWeeks:(NSInteger)weeks {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setWeekOfYear:weeks];
return [calendar dateByAddingComponents:components toDate:self options:0];
}
- (NSDate *)dateByAddingDays:(NSInteger)days {
NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + 86400 * days;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
- (NSDate *)dateByAddingHours:(NSInteger)hours {
NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + 3600 * hours;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
- (NSDate *)dateByAddingMinutes:(NSInteger)minutes {
NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + 60 * minutes;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
- (NSDate *)dateByAddingSeconds:(NSInteger)seconds {
NSTimeInterval aTimeInterval = [self timeIntervalSinceReferenceDate] + seconds;
NSDate *newDate = [NSDate dateWithTimeIntervalSinceReferenceDate:aTimeInterval];
return newDate;
}
- (NSString *)stringWithFormat:(NSString *)format {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:format];
[formatter setLocale:[NSLocale currentLocale]];
return [formatter stringFromDate:self];
}
- (NSString *)stringWithFormat:(NSString *)format timeZone:(NSTimeZone *)timeZone locale:(NSLocale *)locale {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:format];
if (timeZone) [formatter setTimeZone:timeZone];
if (locale) [formatter setLocale:locale];
return [formatter stringFromDate:self];
}
- (NSString *)stringWithISOFormat {
static NSDateFormatter *formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
});
return [formatter stringFromDate:self];
}
+ (NSDate *)dateWithString:(NSString *)dateString format:(NSString *)format {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:format];
return [formatter dateFromString:dateString];
}
+ (NSDate *)dateWithString:(NSString *)dateString format:(NSString *)format timeZone:(NSTimeZone *)timeZone locale:(NSLocale *)locale {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:format];
if (timeZone) [formatter setTimeZone:timeZone];
if (locale) [formatter setLocale:locale];
return [formatter dateFromString:dateString];
}
+ (NSDate *)dateWithISOFormatString:(NSString *)dateString {
static NSDateFormatter *formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
});
return [formatter dateFromString:dateString];
}
@end

View File

@@ -0,0 +1,197 @@
//
// NSDictionary+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide some some common method for `NSDictionary`.
*/
@interface NSDictionary (YYAdd)
#pragma mark - Dictionary Convertor
///=============================================================================
/// @name Dictionary Convertor
///=============================================================================
/**
Creates and returns a dictionary from a specified property list data.
@param plist A property list data whose root object is a dictionary.
@return A new dictionary created from the plist data, or nil if an error occurs.
*/
+ (nullable NSDictionary *)dictionaryWithPlistData:(NSData *)plist;
/**
Creates and returns a dictionary from a specified property list xml string.
@param plist A property list xml string whose root object is a dictionary.
@return A new dictionary created from the plist string, or nil if an error occurs.
@discussion Apple has implemented this method, but did not make it public.
*/
+ (nullable NSDictionary *)dictionaryWithPlistString:(NSString *)plist;
/**
Serialize the dictionary to a binary property list data.
@return A bplist data, or nil if an error occurs.
@discussion Apple has implemented this method, but did not make it public.
*/
- (nullable NSData *)plistData;
/**
Serialize the dictionary to a xml property list string.
@return A plist xml string, or nil if an error occurs.
*/
- (nullable NSString *)plistString;
/**
Returns a new array containing the dictionary's keys sorted.
The keys should be NSString, and they will be sorted ascending.
@return A new array containing the dictionary's keys,
or an empty array if the dictionary has no entries.
*/
- (NSArray *)allKeysSorted;
/**
Returns a new array containing the dictionary's values sorted by keys.
The order of the values in the array is defined by keys.
The keys should be NSString, and they will be sorted ascending.
@return A new array containing the dictionary's values sorted by keys,
or an empty array if the dictionary has no entries.
*/
- (NSArray *)allValuesSortedByKeys;
/**
Returns a BOOL value tells if the dictionary has an object for key.
@param key The key.
*/
- (BOOL)containsObjectForKey:(id)key;
/**
Returns a new dictionary containing the entries for keys.
If the keys is empty or nil, it just returns an empty dictionary.
@param keys The keys.
@return The entries for the keys.
*/
- (NSDictionary *)entriesForKeys:(NSArray *)keys;
/**
Convert dictionary to json string. return nil if an error occurs.
*/
- (nullable NSString *)jsonStringEncoded;
/**
Convert dictionary to json string formatted. return nil if an error occurs.
*/
- (nullable NSString *)jsonPrettyStringEncoded;
/**
Try to parse an XML and wrap it into a dictionary.
If you just want to get some value from a small xml, try this.
example XML: "<config><a href="test.com">link</a></config>"
example Return: @{@"_name":@"config", @"a":{@"_text":@"link",@"href":@"test.com"}}
@param xmlDataOrString XML in NSData or NSString format.
@return Return a new dictionary, or nil if an error occurs.
*/
+ (nullable NSDictionary *)dictionaryWithXML:(id)xmlDataOrString;
#pragma mark - Dictionary Value Getter
///=============================================================================
/// @name Dictionary Value Getter
///=============================================================================
- (BOOL)boolValueForKey:(NSString *)key default:(BOOL)def;
- (char)charValueForKey:(NSString *)key default:(char)def;
- (unsigned char)unsignedCharValueForKey:(NSString *)key default:(unsigned char)def;
- (short)shortValueForKey:(NSString *)key default:(short)def;
- (unsigned short)unsignedShortValueForKey:(NSString *)key default:(unsigned short)def;
- (int)intValueForKey:(NSString *)key default:(int)def;
- (unsigned int)unsignedIntValueForKey:(NSString *)key default:(unsigned int)def;
- (long)longValueForKey:(NSString *)key default:(long)def;
- (unsigned long)unsignedLongValueForKey:(NSString *)key default:(unsigned long)def;
- (long long)longLongValueForKey:(NSString *)key default:(long long)def;
- (unsigned long long)unsignedLongLongValueForKey:(NSString *)key default:(unsigned long long)def;
- (float)floatValueForKey:(NSString *)key default:(float)def;
- (double)doubleValueForKey:(NSString *)key default:(double)def;
- (NSInteger)integerValueForKey:(NSString *)key default:(NSInteger)def;
- (NSUInteger)unsignedIntegerValueForKey:(NSString *)key default:(NSUInteger)def;
- (nullable NSNumber *)numberValueForKey:(NSString *)key default:(nullable NSNumber *)def;
- (nullable NSString *)stringValueForKey:(NSString *)key default:(nullable NSString *)def;
@end
/**
Provide some some common method for `NSMutableDictionary`.
*/
@interface NSMutableDictionary (YYAdd)
/**
Creates and returns a dictionary from a specified property list data.
@param plist A property list data whose root object is a dictionary.
@return A new dictionary created from the plist data, or nil if an error occurs.
@discussion Apple has implemented this method, but did not make it public.
*/
+ (nullable NSMutableDictionary *)dictionaryWithPlistData:(NSData *)plist;
/**
Creates and returns a dictionary from a specified property list xml string.
@param plist A property list xml string whose root object is a dictionary.
@return A new dictionary created from the plist string, or nil if an error occurs.
*/
+ (nullable NSMutableDictionary *)dictionaryWithPlistString:(NSString *)plist;
/**
Removes and returns the value associated with a given key.
@param aKey The key for which to return and remove the corresponding value.
@return The value associated with aKey, or nil if no value is associated with aKey.
*/
- (nullable id)popObjectForKey:(id)aKey;
/**
Returns a new dictionary containing the entries for keys, and remove these
entries from reciever. If the keys is empty or nil, it just returns an
empty dictionary.
@param keys The keys.
@return The entries for the keys.
*/
- (NSDictionary *)popEntriesForKeys:(NSArray *)keys;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,391 @@
//
// NSDictionary+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSDictionary+YYAdd.h"
#import "NSString+YYAdd.h"
#import "NSData+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(NSDictionary_YYAdd)
@interface _YYXMLDictionaryParser : NSObject <NSXMLParserDelegate>
@end
@implementation _YYXMLDictionaryParser {
NSMutableDictionary *_root;
NSMutableArray *_stack;
NSMutableString *_text;
}
- (instancetype)initWithData:(NSData *)data {
self = super.init;
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];
return self;
}
- (instancetype)initWithString:(NSString *)xml {
NSData *data = [xml dataUsingEncoding:NSUTF8StringEncoding];
return [self initWithData:data];
}
- (NSDictionary *)result {
return _root;
}
#pragma mark NSXMLParserDelegate
#define XMLText @"_text"
#define XMLName @"_name"
#define XMLPref @"_"
- (void)textEnd {
_text = _text.stringByTrim.mutableCopy;
if (_text.length) {
NSMutableDictionary *top = _stack.lastObject;
id existing = top[XMLText];
if ([existing isKindOfClass:[NSArray class]]) {
[existing addObject:_text];
} else if (existing) {
top[XMLText] = [@[existing, _text] mutableCopy];
} else {
top[XMLText] = _text;
}
}
_text = nil;
}
- (void)parser:(__unused NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName attributes:(NSDictionary *)attributeDict {
[self textEnd];
NSMutableDictionary *node = [NSMutableDictionary new];
if (!_root) node[XMLName] = elementName;
if (attributeDict.count) [node addEntriesFromDictionary:attributeDict];
if (_root) {
NSMutableDictionary *top = _stack.lastObject;
id existing = top[elementName];
if ([existing isKindOfClass:[NSArray class]]) {
[existing addObject:node];
} else if (existing) {
top[elementName] = [@[existing, node] mutableCopy];
} else {
top[elementName] = node;
}
[_stack addObject:node];
} else {
_root = node;
_stack = [NSMutableArray arrayWithObject:node];
}
}
- (void)parser:(__unused NSXMLParser *)parser didEndElement:(__unused NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName {
[self textEnd];
NSMutableDictionary *top = _stack.lastObject;
[_stack removeLastObject];
NSMutableDictionary *left = top.mutableCopy;
[left removeObjectsForKeys:@[XMLText, XMLName]];
for (NSString *key in left.allKeys) {
[left removeObjectForKey:key];
if ([key hasPrefix:XMLPref]) {
left[[key substringFromIndex:XMLPref.length]] = top[key];
}
}
if (left.count) return;
NSMutableDictionary *children = top.mutableCopy;
[children removeObjectsForKeys:@[XMLText, XMLName]];
for (NSString *key in children.allKeys) {
if ([key hasPrefix:XMLPref]) {
[children removeObjectForKey:key];
}
}
if (children.count) return;
NSMutableDictionary *topNew = _stack.lastObject;
NSString *nodeName = top[XMLName];
if (!nodeName) {
for (NSString *name in topNew) {
id object = topNew[name];
if (object == top) {
nodeName = name; break;
} else if ([object isKindOfClass:[NSArray class]] && [object containsObject:top]) {
nodeName = name; break;
}
}
}
if (!nodeName) return;
id inner = top[XMLText];
if ([inner isKindOfClass:[NSArray class]]) {
inner = [inner componentsJoinedByString:@"\n"];
}
if (!inner) return;
id parent = topNew[nodeName];
if ([parent isKindOfClass:[NSArray class]]) {
parent[[parent count] - 1] = inner;
} else {
topNew[nodeName] = inner;
}
}
- (void)parser:(__unused NSXMLParser *)parser foundCharacters:(NSString *)string {
if (_text) [_text appendString:string];
else _text = [NSMutableString stringWithString:string];
}
- (void)parser:(__unused NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock {
NSString *string = [[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding];
if (_text) [_text appendString:string];
else _text = [NSMutableString stringWithString:string];
}
#undef XMLText
#undef XMLName
#undef XMLPref
@end
@implementation NSDictionary (YYAdd)
+ (NSDictionary *)dictionaryWithPlistData:(NSData *)plist {
if (!plist) return nil;
NSDictionary *dictionary = [NSPropertyListSerialization propertyListWithData:plist options:NSPropertyListImmutable format:NULL error:NULL];
if ([dictionary isKindOfClass:[NSDictionary class]]) return dictionary;
return nil;
}
+ (NSDictionary *)dictionaryWithPlistString:(NSString *)plist {
if (!plist) return nil;
NSData* data = [plist dataUsingEncoding:NSUTF8StringEncoding];
return [self dictionaryWithPlistData:data];
}
- (NSData *)plistData {
return [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListBinaryFormat_v1_0 options:kNilOptions error:NULL];
}
- (NSString *)plistString {
NSData *xmlData = [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListXMLFormat_v1_0 options:kNilOptions error:NULL];
if (xmlData) return xmlData.utf8String;
return nil;
}
- (NSArray *)allKeysSorted {
return [[self allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
}
- (NSArray *)allValuesSortedByKeys {
NSArray *sortedKeys = [self allKeysSorted];
NSMutableArray *arr = [[NSMutableArray alloc] init];
for (id key in sortedKeys) {
[arr addObject:self[key]];
}
return arr;
}
- (BOOL)containsObjectForKey:(id)key {
if (!key) return NO;
return self[key] != nil;
}
- (NSDictionary *)entriesForKeys:(NSArray *)keys {
NSMutableDictionary *dic = [NSMutableDictionary new];
for (id key in keys) {
id value = self[key];
if (value) dic[key] = value;
}
return dic;
}
- (NSString *)jsonStringEncoded {
if ([NSJSONSerialization isValidJSONObject:self]) {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:0 error:&error];
NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return json;
}
return nil;
}
- (NSString *)jsonPrettyStringEncoded {
if ([NSJSONSerialization isValidJSONObject:self]) {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self options:NSJSONWritingPrettyPrinted error:&error];
NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return json;
}
return nil;
}
+ (NSDictionary *)dictionaryWithXML:(id)xml {
_YYXMLDictionaryParser *parser = nil;
if ([xml isKindOfClass:[NSString class]]) {
parser = [[_YYXMLDictionaryParser alloc] initWithString:xml];
} else if ([xml isKindOfClass:[NSData class]]) {
parser = [[_YYXMLDictionaryParser alloc] initWithData:xml];
}
return [parser result];
}
/// Get a number value from 'id'.
static NSNumber *NSNumberFromID(id value) {
static NSCharacterSet *dot;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)];
});
if (!value || value == [NSNull null]) return nil;
if ([value isKindOfClass:[NSNumber class]]) return value;
if ([value isKindOfClass:[NSString class]]) {
NSString *lower = ((NSString *)value).lowercaseString;
if ([lower isEqualToString:@"true"] || [lower isEqualToString:@"yes"]) return @(YES);
if ([lower isEqualToString:@"false"] || [lower isEqualToString:@"no"]) return @(NO);
if ([lower isEqualToString:@"nil"] || [lower isEqualToString:@"null"]) return nil;
if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) {
return @(((NSString *)value).doubleValue);
} else {
return @(((NSString *)value).longLongValue);
}
}
return nil;
}
#define RETURN_VALUE(_type_) \
if (!key) return def; \
id value = self[key]; \
if (!value || value == [NSNull null]) return def; \
if ([value isKindOfClass:[NSNumber class]]) return ((NSNumber *)value)._type_; \
if ([value isKindOfClass:[NSString class]]) return NSNumberFromID(value)._type_; \
return def;
- (BOOL)boolValueForKey:(NSString *)key default:(BOOL)def {
RETURN_VALUE(boolValue);
}
- (char)charValueForKey:(NSString *)key default:(char)def {
RETURN_VALUE(charValue);
}
- (unsigned char)unsignedCharValueForKey:(NSString *)key default:(unsigned char)def {
RETURN_VALUE(unsignedCharValue);
}
- (short)shortValueForKey:(NSString *)key default:(short)def {
RETURN_VALUE(shortValue);
}
- (unsigned short)unsignedShortValueForKey:(NSString *)key default:(unsigned short)def {
RETURN_VALUE(unsignedShortValue);
}
- (int)intValueForKey:(NSString *)key default:(int)def {
RETURN_VALUE(intValue);
}
- (unsigned int)unsignedIntValueForKey:(NSString *)key default:(unsigned int)def {
RETURN_VALUE(unsignedIntValue);
}
- (long)longValueForKey:(NSString *)key default:(long)def {
RETURN_VALUE(longValue);
}
- (unsigned long)unsignedLongValueForKey:(NSString *)key default:(unsigned long)def {
RETURN_VALUE(unsignedLongValue);
}
- (long long)longLongValueForKey:(NSString *)key default:(long long)def {
RETURN_VALUE(longLongValue);
}
- (unsigned long long)unsignedLongLongValueForKey:(NSString *)key default:(unsigned long long)def {
RETURN_VALUE(unsignedLongLongValue);
}
- (float)floatValueForKey:(NSString *)key default:(float)def {
RETURN_VALUE(floatValue);
}
- (double)doubleValueForKey:(NSString *)key default:(double)def {
RETURN_VALUE(doubleValue);
}
- (NSInteger)integerValueForKey:(NSString *)key default:(NSInteger)def {
RETURN_VALUE(integerValue);
}
- (NSUInteger)unsignedIntegerValueForKey:(NSString *)key default:(NSUInteger)def {
RETURN_VALUE(unsignedIntegerValue);
}
- (NSNumber *)numberValueForKey:(NSString *)key default:(NSNumber *)def {
if (!key) return def;
id value = self[key];
if (!value || value == [NSNull null]) return def;
if ([value isKindOfClass:[NSNumber class]]) return value;
if ([value isKindOfClass:[NSString class]]) return NSNumberFromID(value);
return def;
}
- (NSString *)stringValueForKey:(NSString *)key default:(NSString *)def {
if (!key) return def;
id value = self[key];
if (!value || value == [NSNull null]) return def;
if ([value isKindOfClass:[NSString class]]) return value;
if ([value isKindOfClass:[NSNumber class]]) return ((NSNumber *)value).description;
return def;
}
@end
@implementation NSMutableDictionary (YYAdd)
+ (NSMutableDictionary *)dictionaryWithPlistData:(NSData *)plist {
if (!plist) return nil;
NSMutableDictionary *dictionary = [NSPropertyListSerialization propertyListWithData:plist options:NSPropertyListMutableContainersAndLeaves format:NULL error:NULL];
if ([dictionary isKindOfClass:[NSMutableDictionary class]]) return dictionary;
return nil;
}
+ (NSMutableDictionary *)dictionaryWithPlistString:(NSString *)plist {
if (!plist) return nil;
NSData* data = [plist dataUsingEncoding:NSUTF8StringEncoding];
return [self dictionaryWithPlistData:data];
}
- (id)popObjectForKey:(id)aKey {
if (!aKey) return nil;
id value = self[aKey];
[self removeObjectForKey:aKey];
return value;
}
- (NSDictionary *)popEntriesForKeys:(NSArray *)keys {
NSMutableDictionary *dic = [NSMutableDictionary new];
for (id key in keys) {
id value = self[key];
if (value) {
[self removeObjectForKey:key];
dic[key] = value;
}
}
return dic;
}
@end

View File

@@ -0,0 +1,45 @@
//
// NSKeyedUnarchiver+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/8/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `NSKeyedUnarchiver`.
*/
@interface NSKeyedUnarchiver (YYAdd)
/**
Same as unarchiveObjectWithData:, except it returns the exception by reference.
@param data The data need unarchived.
@param exception Pointer which will, upon return, if an exception occurred and
said pointer is not NULL, point to said NSException.
*/
+ (nullable id)unarchiveObjectWithData:(NSData *)data
exception:(NSException *_Nullable *_Nullable)exception;
/**
Same as unarchiveObjectWithFile:, except it returns the exception by reference.
@param path The path of archived object file.
@param exception Pointer which will, upon return, if an exception occurred and
said pointer is not NULL, point to said NSException.
*/
+ (nullable id)unarchiveObjectWithFile:(NSString *)path
exception:(NSException *_Nullable *_Nullable)exception;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,51 @@
//
// NSKeyedUnarchiver+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/8/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSKeyedUnarchiver+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(NSKeyedUnarchiver_YYAdd)
@implementation NSKeyedUnarchiver (YYAdd)
+ (id)unarchiveObjectWithData:(NSData *)data exception:(__autoreleasing NSException **)exception {
id object = nil;
@try {
object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
@catch (NSException *e)
{
if (exception) *exception = e;
}
@finally
{
}
return object;
}
+ (id)unarchiveObjectWithFile:(NSString *)path exception:(__autoreleasing NSException **)exception {
id object = nil;
@try {
object = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
}
@catch (NSException *e)
{
if (exception) *exception = e;
}
@finally
{
}
return object;
}
@end

View File

@@ -0,0 +1,97 @@
//
// NSNotificationCenter+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/8/24.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide some method for `NSNotificationCenter`
to post notification in different thread.
*/
@interface NSNotificationCenter (YYAdd)
/**
Posts a given notification to the receiver on main thread.
If current thread is main thread, the notification is posted synchronized;
otherwise, is posted asynchronized.
@param notification The notification to post.
An exception is raised if notification is nil.
*/
- (void)postNotificationOnMainThread:(NSNotification *)notification;
/**
Posts a given notification to the receiver on main thread.
@param notification The notification to post.
An exception is raised if notification is nil.
@param wait A Boolean that specifies whether the current thread blocks
until after the specified notification is posted on the
receiver on the main thread. Specify YES to block this
thread; otherwise, specify NO to have this method return
immediately.
*/
- (void)postNotificationOnMainThread:(NSNotification *)notification
waitUntilDone:(BOOL)wait;
/**
Creates a notification with a given name and sender and posts it to the
receiver on main thread. If current thread is main thread, the notification
is posted synchronized; otherwise, is posted asynchronized.
@param name The name of the notification.
@param object The object posting the notification.
*/
- (void)postNotificationOnMainThreadWithName:(NSString *)name
object:(nullable id)object;
/**
Creates a notification with a given name and sender and posts it to the
receiver on main thread. If current thread is main thread, the notification
is posted synchronized; otherwise, is posted asynchronized.
@param name The name of the notification.
@param object The object posting the notification.
@param userInfo Information about the the notification. May be nil.
*/
- (void)postNotificationOnMainThreadWithName:(NSString *)name
object:(nullable id)object
userInfo:(nullable NSDictionary *)userInfo;
/**
Creates a notification with a given name and sender and posts it to the
receiver on main thread.
@param name The name of the notification.
@param object The object posting the notification.
@param userInfo Information about the the notification. May be nil.
@param wait A Boolean that specifies whether the current thread blocks
until after the specified notification is posted on the
receiver on the main thread. Specify YES to block this
thread; otherwise, specify NO to have this method return
immediately.
*/
- (void)postNotificationOnMainThreadWithName:(NSString *)name
object:(nullable id)object
userInfo:(nullable NSDictionary *)userInfo
waitUntilDone:(BOOL)wait;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,62 @@
//
// NSNotificationCenter+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/8/24.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSNotificationCenter+YYAdd.h"
#include <pthread.h>
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(NSNotificationCenter_YYAdd)
@implementation NSNotificationCenter (YYAdd)
- (void)postNotificationOnMainThread:(NSNotification *)notification {
if (pthread_main_np()) return [self postNotification:notification];
[self postNotificationOnMainThread:notification waitUntilDone:NO];
}
- (void)postNotificationOnMainThread:(NSNotification *)notification waitUntilDone:(BOOL)wait {
if (pthread_main_np()) return [self postNotification:notification];
[[self class] performSelectorOnMainThread:@selector(_yy_postNotification:) withObject:notification waitUntilDone:wait];
}
- (void)postNotificationOnMainThreadWithName:(NSString *)name object:(id)object {
if (pthread_main_np()) return [self postNotificationName:name object:object userInfo:nil];
[self postNotificationOnMainThreadWithName:name object:object userInfo:nil waitUntilDone:NO];
}
- (void)postNotificationOnMainThreadWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo {
if (pthread_main_np()) return [self postNotificationName:name object:object userInfo:userInfo];
[self postNotificationOnMainThreadWithName:name object:object userInfo:userInfo waitUntilDone:NO];
}
- (void)postNotificationOnMainThreadWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo waitUntilDone:(BOOL)wait {
if (pthread_main_np()) return [self postNotificationName:name object:object userInfo:userInfo];
NSMutableDictionary *info = [[NSMutableDictionary allocWithZone:nil] initWithCapacity:3];
if (name) [info setObject:name forKey:@"name"];
if (object) [info setObject:object forKey:@"object"];
if (userInfo) [info setObject:userInfo forKey:@"userInfo"];
[[self class] performSelectorOnMainThread:@selector(_yy_postNotificationName:) withObject:info waitUntilDone:wait];
}
+ (void)_yy_postNotification:(NSNotification *)notification {
[[self defaultCenter] postNotification:notification];
}
+ (void)_yy_postNotificationName:(NSDictionary *)info {
NSString *name = [info objectForKey:@"name"];
id object = [info objectForKey:@"object"];
NSDictionary *userInfo = [info objectForKey:@"userInfo"];
[[self defaultCenter] postNotificationName:name object:object userInfo:userInfo];
}
@end

View File

@@ -0,0 +1,33 @@
//
// NSNumber+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/8/24.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide a method to parse `NSString` for `NSNumber`.
*/
@interface NSNumber (YYAdd)
/**
Creates and returns an NSNumber object from a string.
Valid format: @"12", @"12.345", @" -0xFF", @" .23e99 "...
@param string The string described an number.
@return an NSNumber when parse succeed, or nil if an error occurs.
*/
+ (nullable NSNumber *)numberWithString:(NSString *)string;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,63 @@
//
// NSNumber+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/8/24.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSNumber+YYAdd.h"
#import "NSString+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(NSNumber_YYAdd)
@implementation NSNumber (YYAdd)
+ (NSNumber *)numberWithString:(NSString *)string {
NSString *str = [[string stringByTrim] lowercaseString];
if (!str || !str.length) {
return nil;
}
static NSDictionary *dic;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dic = @{@"true" : @(YES),
@"yes" : @(YES),
@"false" : @(NO),
@"no" : @(NO),
@"nil" : [NSNull null],
@"null" : [NSNull null],
@"<null>" : [NSNull null]};
});
NSNumber *num = dic[str];
if (num) {
if (num == (id)[NSNull null]) return nil;
return num;
}
// hex number
int sign = 0;
if ([str hasPrefix:@"0x"]) sign = 1;
else if ([str hasPrefix:@"-0x"]) sign = -1;
if (sign != 0) {
NSScanner *scan = [NSScanner scannerWithString:str];
unsigned num = -1;
BOOL suc = [scan scanHexInt:&num];
if (suc)
return [NSNumber numberWithLong:((long)num * sign)];
else
return nil;
}
// normal number
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
return [formatter numberFromString:string];
}
@end

View File

@@ -0,0 +1,302 @@
//
// NSObject+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/8.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Common tasks for NSObject.
*/
@interface NSObject (YYAdd)
#pragma mark - Sending messages with variable parameters
///=============================================================================
/// @name Sending messages with variable parameters
///=============================================================================
/**
Sends a specified message to the receiver and returns the result of the message.
@param sel A selector identifying the message to send. If the selector is
NULL or unrecognized, an NSInvalidArgumentException is raised.
@param ... Variable parameter list. Parameters type must correspond to the
selector's method declaration, or unexpected results may occur.
It doesn't support union or struct which is larger than 256 bytes.
@return An object that is the result of the message.
@discussion The selector's return value will be wrap as NSNumber or NSValue
if the selector's `return type` is not object. It always returns nil
if the selector's `return type` is void.
Sample Code:
// no variable args
[view performSelectorWithArgs:@selector(removeFromSuperView)];
// variable arg is not object
[view performSelectorWithArgs:@selector(setCenter:), CGPointMake(0, 0)];
// perform and return object
UIImage *image = [UIImage.class performSelectorWithArgs:@selector(imageWithData:scale:), data, 2.0];
// perform and return wrapped number
NSNumber *lengthValue = [@"hello" performSelectorWithArgs:@selector(length)];
NSUInteger length = lengthValue.unsignedIntegerValue;
// perform and return wrapped struct
NSValue *frameValue = [view performSelectorWithArgs:@selector(frame)];
CGRect frame = frameValue.CGRectValue;
*/
- (nullable id)performSelectorWithArgs:(SEL)sel, ...;
/**
Invokes a method of the receiver on the current thread using the default mode after a delay.
@warning It can't cancelled by previous request.
@param sel A selector identifying the message to send. If the selector is
NULL or unrecognized, an NSInvalidArgumentException is raised immediately.
@param delay The minimum time before which the message is sent. Specifying
a delay of 0 does not necessarily cause the selector to be
performed immediately. The selector is still queued on the
thread's run loop and performed as soon as possible.
@param ... Variable parameter list. Parameters type must correspond to the
selector's method declaration, or unexpected results may occur.
It doesn't support union or struct which is larger than 256 bytes.
Sample Code:
// no variable args
[view performSelectorWithArgs:@selector(removeFromSuperView) afterDelay:2.0];
// variable arg is not object
[view performSelectorWithArgs:@selector(setCenter:), afterDelay:0, CGPointMake(0, 0)];
*/
- (void)performSelectorWithArgs:(SEL)sel afterDelay:(NSTimeInterval)delay, ...;
/**
Invokes a method of the receiver on the main thread using the default mode.
@param sel A selector identifying the message to send. If the selector is
NULL or unrecognized, an NSInvalidArgumentException is raised.
@param wait A Boolean that specifies whether the current thread blocks until
after the specified selector is performed on the receiver on the
specified thread. Specify YES to block this thread; otherwise,
specify NO to have this method return immediately.
@param ... Variable parameter list. Parameters type must correspond to the
selector's method declaration, or unexpected results may occur.
It doesn't support union or struct which is larger than 256 bytes.
@return While @a wait is YES, it returns object that is the result of
the message. Otherwise return nil;
@discussion The selector's return value will be wrap as NSNumber or NSValue
if the selector's `return type` is not object. It always returns nil
if the selector's `return type` is void, or @a wait is YES.
Sample Code:
// no variable args
[view performSelectorWithArgsOnMainThread:@selector(removeFromSuperView), waitUntilDone:NO];
// variable arg is not object
[view performSelectorWithArgsOnMainThread:@selector(setCenter:), waitUntilDone:NO, CGPointMake(0, 0)];
*/
- (nullable id)performSelectorWithArgsOnMainThread:(SEL)sel waitUntilDone:(BOOL)wait, ...;
/**
Invokes a method of the receiver on the specified thread using the default mode.
@param sel A selector identifying the message to send. If the selector is
NULL or unrecognized, an NSInvalidArgumentException is raised.
@param thread The thread on which to execute aSelector.
@param wait A Boolean that specifies whether the current thread blocks until
after the specified selector is performed on the receiver on the
specified thread. Specify YES to block this thread; otherwise,
specify NO to have this method return immediately.
@param ... Variable parameter list. Parameters type must correspond to the
selector's method declaration, or unexpected results may occur.
It doesn't support union or struct which is larger than 256 bytes.
@return While @a wait is YES, it returns object that is the result of
the message. Otherwise return nil;
@discussion The selector's return value will be wrap as NSNumber or NSValue
if the selector's `return type` is not object. It always returns nil
if the selector's `return type` is void, or @a wait is YES.
Sample Code:
[view performSelectorWithArgs:@selector(removeFromSuperView) onThread:mainThread waitUntilDone:NO];
[array performSelectorWithArgs:@selector(sortUsingComparator:)
onThread:backgroundThread
waitUntilDone:NO, ^NSComparisonResult(NSNumber *num1, NSNumber *num2) {
return [num2 compare:num2];
}];
*/
- (nullable id)performSelectorWithArgs:(SEL)sel onThread:(NSThread *)thread waitUntilDone:(BOOL)wait, ...;
/**
Invokes a method of the receiver on a new background thread.
@param sel A selector identifying the message to send. If the selector is
NULL or unrecognized, an NSInvalidArgumentException is raised.
@param ... Variable parameter list. Parameters type must correspond to the
selector's method declaration, or unexpected results may occur.
It doesn't support union or struct which is larger than 256 bytes.
@discussion This method creates a new thread in your application, putting
your application into multithreaded mode if it was not already.
The method represented by sel must set up the thread environment
just as you would for any other new thread in your program.
Sample Code:
[array performSelectorWithArgsInBackground:@selector(sortUsingComparator:),
^NSComparisonResult(NSNumber *num1, NSNumber *num2) {
return [num2 compare:num2];
}];
*/
- (void)performSelectorWithArgsInBackground:(SEL)sel, ...;
/**
Invokes a method of the receiver on the current thread after a delay.
@warning arc-performSelector-leaks
@param sel A selector that identifies the method to invoke. The method should
not have a significant return value and should take no argument.
If the selector is NULL or unrecognized,
an NSInvalidArgumentException is raised after the delay.
@param delay The minimum time before which the message is sent. Specifying a
delay of 0 does not necessarily cause the selector to be performed
immediately. The selector is still queued on the thread's run loop
and performed as soon as possible.
@discussion This method sets up a timer to perform the aSelector message on
the current thread's run loop. The timer is configured to run in
the default mode (NSDefaultRunLoopMode). When the timer fires, the
thread attempts to dequeue the message from the run loop and
perform the selector. It succeeds if the run loop is running and
in the default mode; otherwise, the timer waits until the run loop
is in the default mode.
*/
- (void)performSelector:(SEL)sel afterDelay:(NSTimeInterval)delay;
#pragma mark - Swap method (Swizzling)
///=============================================================================
/// @name Swap method (Swizzling)
///=============================================================================
/**
Swap two instance method's implementation in one class. Dangerous, be careful.
@param originalSel Selector 1.
@param newSel Selector 2.
@return YES if swizzling succeed; otherwise, NO.
*/
+ (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel;
/**
Swap two class method's implementation in one class. Dangerous, be careful.
@param originalSel Selector 1.
@param newSel Selector 2.
@return YES if swizzling succeed; otherwise, NO.
*/
+ (BOOL)swizzleClassMethod:(SEL)originalSel with:(SEL)newSel;
#pragma mark - Associate value
///=============================================================================
/// @name Associate value
///=============================================================================
/**
Associate one object to `self`, as if it was a strong property (strong, nonatomic).
@param value The object to associate.
@param key The pointer to get value from `self`.
*/
- (void)setAssociateValue:(nullable id)value withKey:(void *)key;
/**
Associate one object to `self`, as if it was a weak property (week, nonatomic).
@param value The object to associate.
@param key The pointer to get value from `self`.
*/
- (void)setAssociateWeakValue:(nullable id)value withKey:(void *)key;
/**
Get the associated value from `self`.
@param key The pointer to get value from `self`.
*/
- (nullable id)getAssociatedValueForKey:(void *)key;
/**
Remove all associated values.
*/
- (void)removeAssociatedValues;
#pragma mark - Others
///=============================================================================
/// @name Others
///=============================================================================
/**
Returns the class name in NSString.
*/
+ (NSString *)className;
/**
Returns the class name in NSString.
@discussion Apple has implemented this method in NSObject(NSLayoutConstraintCallsThis),
but did not make it public.
*/
- (NSString *)className;
/**
Returns a copy of the instance with `NSKeyedArchiver` and ``NSKeyedUnarchiver``.
Returns nil if an error occurs.
*/
- (nullable id)deepCopy;
/**
Returns a copy of the instance use archiver and unarchiver.
Returns nil if an error occurs.
@param archiver NSKeyedArchiver class or any class inherited.
@param unarchiver NSKeyedUnarchiver clsas or any class inherited.
*/
- (nullable id)deepCopyWithArchiver:(Class)archiver unarchiver:(Class)unarchiver;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,396 @@
//
// NSObject+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/8.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSObject+YYAdd.h"
#import "YYCategoriesMacro.h"
#import <objc/objc.h>
#import <objc/runtime.h>
YYSYNTH_DUMMY_CLASS(NSObject_YYAdd)
@implementation NSObject (YYAdd)
/*
NSInvocation is much slower than objc_msgSend()...
Do not use it if you have performance issues.
*/
#define INIT_INV(_last_arg_, _return_) \
NSMethodSignature * sig = [self methodSignatureForSelector:sel]; \
if (!sig) { [self doesNotRecognizeSelector:sel]; return _return_; } \
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig]; \
if (!inv) { [self doesNotRecognizeSelector:sel]; return _return_; } \
[inv setTarget:self]; \
[inv setSelector:sel]; \
va_list args; \
va_start(args, _last_arg_); \
[NSObject setInv:inv withSig:sig andArgs:args]; \
va_end(args);
- (id)performSelectorWithArgs:(SEL)sel, ...{
INIT_INV(sel, nil);
[inv invoke];
return [NSObject getReturnFromInv:inv withSig:sig];
}
- (void)performSelectorWithArgs:(SEL)sel afterDelay:(NSTimeInterval)delay, ...{
INIT_INV(delay, );
[inv retainArguments];
[inv performSelector:@selector(invoke) withObject:nil afterDelay:delay];
}
- (id)performSelectorWithArgsOnMainThread:(SEL)sel waitUntilDone:(BOOL)wait, ...{
INIT_INV(wait, nil);
if (!wait) [inv retainArguments];
[inv performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:wait];
return wait ? [NSObject getReturnFromInv:inv withSig:sig] : nil;
}
- (id)performSelectorWithArgs:(SEL)sel onThread:(NSThread *)thr waitUntilDone:(BOOL)wait, ...{
INIT_INV(wait, nil);
if (!wait) [inv retainArguments];
[inv performSelector:@selector(invoke) onThread:thr withObject:nil waitUntilDone:wait];
return wait ? [NSObject getReturnFromInv:inv withSig:sig] : nil;
}
- (void)performSelectorWithArgsInBackground:(SEL)sel, ...{
INIT_INV(sel, );
[inv retainArguments];
[inv performSelectorInBackground:@selector(invoke) withObject:nil];
}
#undef INIT_INV
+ (id)getReturnFromInv:(NSInvocation *)inv withSig:(NSMethodSignature *)sig {
NSUInteger length = [sig methodReturnLength];
if (length == 0) return nil;
char *type = (char *)[sig methodReturnType];
while (*type == 'r' || // const
*type == 'n' || // in
*type == 'N' || // inout
*type == 'o' || // out
*type == 'O' || // bycopy
*type == 'R' || // byref
*type == 'V') { // oneway
type++; // cutoff useless prefix
}
#define return_with_number(_type_) \
do { \
_type_ ret; \
[inv getReturnValue:&ret]; \
return @(ret); \
} while (0)
switch (*type) {
case 'v': return nil; // void
case 'B': return_with_number(bool);
case 'c': return_with_number(char);
case 'C': return_with_number(unsigned char);
case 's': return_with_number(short);
case 'S': return_with_number(unsigned short);
case 'i': return_with_number(int);
case 'I': return_with_number(unsigned int);
case 'l': return_with_number(int);
case 'L': return_with_number(unsigned int);
case 'q': return_with_number(long long);
case 'Q': return_with_number(unsigned long long);
case 'f': return_with_number(float);
case 'd': return_with_number(double);
case 'D': { // long double
long double ret;
[inv getReturnValue:&ret];
return [NSNumber numberWithDouble:ret];
};
case '@': { // id
id ret = nil;
[inv getReturnValue:&ret];
return ret;
};
case '#': { // Class
Class ret = nil;
[inv getReturnValue:&ret];
return ret;
};
default: { // struct / union / SEL / void* / unknown
const char *objCType = [sig methodReturnType];
char *buf = calloc(1, length);
if (!buf) return nil;
[inv getReturnValue:buf];
NSValue *value = [NSValue valueWithBytes:buf objCType:objCType];
free(buf);
return value;
};
}
#undef return_with_number
}
+ (void)setInv:(NSInvocation *)inv withSig:(NSMethodSignature *)sig andArgs:(va_list)args {
NSUInteger count = [sig numberOfArguments];
for (int index = 2; index < count; index++) {
char *type = (char *)[sig getArgumentTypeAtIndex:index];
while (*type == 'r' || // const
*type == 'n' || // in
*type == 'N' || // inout
*type == 'o' || // out
*type == 'O' || // bycopy
*type == 'R' || // byref
*type == 'V') { // oneway
type++; // cutoff useless prefix
}
BOOL unsupportedType = NO;
switch (*type) {
case 'v': // 1: void
case 'B': // 1: bool
case 'c': // 1: char / BOOL
case 'C': // 1: unsigned char
case 's': // 2: short
case 'S': // 2: unsigned short
case 'i': // 4: int / NSInteger(32bit)
case 'I': // 4: unsigned int / NSUInteger(32bit)
case 'l': // 4: long(32bit)
case 'L': // 4: unsigned long(32bit)
{ // 'char' and 'short' will be promoted to 'int'.
int arg = va_arg(args, int);
[inv setArgument:&arg atIndex:index];
} break;
case 'q': // 8: long long / long(64bit) / NSInteger(64bit)
case 'Q': // 8: unsigned long long / unsigned long(64bit) / NSUInteger(64bit)
{
long long arg = va_arg(args, long long);
[inv setArgument:&arg atIndex:index];
} break;
case 'f': // 4: float / CGFloat(32bit)
{ // 'float' will be promoted to 'double'.
double arg = va_arg(args, double);
float argf = arg;
[inv setArgument:&argf atIndex:index];
} break;
case 'd': // 8: double / CGFloat(64bit)
{
double arg = va_arg(args, double);
[inv setArgument:&arg atIndex:index];
} break;
case 'D': // 16: long double
{
long double arg = va_arg(args, long double);
[inv setArgument:&arg atIndex:index];
} break;
case '*': // char *
case '^': // pointer
{
void *arg = va_arg(args, void *);
[inv setArgument:&arg atIndex:index];
} break;
case ':': // SEL
{
SEL arg = va_arg(args, SEL);
[inv setArgument:&arg atIndex:index];
} break;
case '#': // Class
{
Class arg = va_arg(args, Class);
[inv setArgument:&arg atIndex:index];
} break;
case '@': // id
{
id arg = va_arg(args, id);
[inv setArgument:&arg atIndex:index];
} break;
case '{': // struct
{
if (strcmp(type, @encode(CGPoint)) == 0) {
CGPoint arg = va_arg(args, CGPoint);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(CGSize)) == 0) {
CGSize arg = va_arg(args, CGSize);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(CGRect)) == 0) {
CGRect arg = va_arg(args, CGRect);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(CGVector)) == 0) {
CGVector arg = va_arg(args, CGVector);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(CGAffineTransform)) == 0) {
CGAffineTransform arg = va_arg(args, CGAffineTransform);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(CATransform3D)) == 0) {
CATransform3D arg = va_arg(args, CATransform3D);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(NSRange)) == 0) {
NSRange arg = va_arg(args, NSRange);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(UIOffset)) == 0) {
UIOffset arg = va_arg(args, UIOffset);
[inv setArgument:&arg atIndex:index];
} else if (strcmp(type, @encode(UIEdgeInsets)) == 0) {
UIEdgeInsets arg = va_arg(args, UIEdgeInsets);
[inv setArgument:&arg atIndex:index];
} else {
unsupportedType = YES;
}
} break;
case '(': // union
{
unsupportedType = YES;
} break;
case '[': // array
{
unsupportedType = YES;
} break;
default: // what?!
{
unsupportedType = YES;
} break;
}
if (unsupportedType) {
// Try with some dummy type...
NSUInteger size = 0;
NSGetSizeAndAlignment(type, &size, NULL);
#define case_size(_size_) \
else if (size <= 4 * _size_ ) { \
struct dummy { char tmp[4 * _size_]; }; \
struct dummy arg = va_arg(args, struct dummy); \
[inv setArgument:&arg atIndex:index]; \
}
if (size == 0) { }
case_size( 1) case_size( 2) case_size( 3) case_size( 4)
case_size( 5) case_size( 6) case_size( 7) case_size( 8)
case_size( 9) case_size(10) case_size(11) case_size(12)
case_size(13) case_size(14) case_size(15) case_size(16)
case_size(17) case_size(18) case_size(19) case_size(20)
case_size(21) case_size(22) case_size(23) case_size(24)
case_size(25) case_size(26) case_size(27) case_size(28)
case_size(29) case_size(30) case_size(31) case_size(32)
case_size(33) case_size(34) case_size(35) case_size(36)
case_size(37) case_size(38) case_size(39) case_size(40)
case_size(41) case_size(42) case_size(43) case_size(44)
case_size(45) case_size(46) case_size(47) case_size(48)
case_size(49) case_size(50) case_size(51) case_size(52)
case_size(53) case_size(54) case_size(55) case_size(56)
case_size(57) case_size(58) case_size(59) case_size(60)
case_size(61) case_size(62) case_size(63) case_size(64)
else {
/*
Larger than 256 byte?! I don't want to deal with this stuff up...
Ignore this argument.
*/
struct dummy {char tmp;};
for (int i = 0; i < size; i++) va_arg(args, struct dummy);
NSLog(@"YYCategories performSelectorWithArgs unsupported type:%s (%lu bytes)",
[sig getArgumentTypeAtIndex:index],(unsigned long)size);
}
#undef case_size
}
}
}
- (void)performSelector:(SEL)selector afterDelay:(NSTimeInterval)delay {
[self performSelector:selector withObject:nil afterDelay:delay];
}
+ (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel {
Method originalMethod = class_getInstanceMethod(self, originalSel);
Method newMethod = class_getInstanceMethod(self, newSel);
if (!originalMethod || !newMethod) return NO;
class_addMethod(self,
originalSel,
class_getMethodImplementation(self, originalSel),
method_getTypeEncoding(originalMethod));
class_addMethod(self,
newSel,
class_getMethodImplementation(self, newSel),
method_getTypeEncoding(newMethod));
method_exchangeImplementations(class_getInstanceMethod(self, originalSel),
class_getInstanceMethod(self, newSel));
return YES;
}
+ (BOOL)swizzleClassMethod:(SEL)originalSel with:(SEL)newSel {
Class class = object_getClass(self);
Method originalMethod = class_getInstanceMethod(class, originalSel);
Method newMethod = class_getInstanceMethod(class, newSel);
if (!originalMethod || !newMethod) return NO;
method_exchangeImplementations(originalMethod, newMethod);
return YES;
}
- (void)setAssociateValue:(id)value withKey:(void *)key {
objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)setAssociateWeakValue:(id)value withKey:(void *)key {
objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_ASSIGN);
}
- (void)removeAssociatedValues {
objc_removeAssociatedObjects(self);
}
- (id)getAssociatedValueForKey:(void *)key {
return objc_getAssociatedObject(self, key);
}
+ (NSString *)className {
return NSStringFromClass(self);
}
- (NSString *)className {
return [NSString stringWithUTF8String:class_getName([self class])];
}
- (id)deepCopy {
id obj = nil;
@try {
obj = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:self]];
}
@catch (NSException *exception) {
NSLog(@"%@", exception);
}
return obj;
}
- (id)deepCopyWithArchiver:(Class)archiver unarchiver:(Class)unarchiver {
id obj = nil;
@try {
obj = [unarchiver unarchiveObjectWithData:[archiver archivedDataWithRootObject:self]];
}
@catch (NSException *exception) {
NSLog(@"%@", exception);
}
return obj;
}
@end

View File

@@ -0,0 +1,31 @@
//
// NSObject+YYAddForARC.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/12/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
/**
Debug method for NSObject when using ARC.
*/
@interface NSObject (YYAddForARC)
/// Same as `retain`
- (instancetype)arcDebugRetain;
/// Same as `release`
- (oneway void)arcDebugRelease;
/// Same as `autorelease`
- (instancetype)arcDebugAutorelease;
/// Same as `retainCount`
- (NSUInteger)arcDebugRetainCount;
@end

View File

@@ -0,0 +1,40 @@
//
// NSObject+YYAddForARC.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/12/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSObject+YYAddForARC.h"
@interface NSObject_YYAddForARC : NSObject @end
@implementation NSObject_YYAddForARC @end
#if __has_feature(objc_arc)
#error This file must be compiled without ARC. Specify the -fno-objc-arc flag to this file.
#endif
@implementation NSObject (YYAddForARC)
- (instancetype)arcDebugRetain {
return [self retain];
}
- (oneway void)arcDebugRelease {
[self release];
}
- (instancetype)arcDebugAutorelease {
return [self autorelease];
}
- (NSUInteger)arcDebugRetainCount {
return [self retainCount];
}
@end

View File

@@ -0,0 +1,54 @@
//
// NSObject+YYAddForKVO.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Observer with block (KVO).
*/
@interface NSObject (YYAddForKVO)
/**
Registers a block to receive KVO notifications for the specified key-path
relative to the receiver.
@discussion The block and block captured objects are retained. Call
`removeObserverBlocksForKeyPath:` or `removeObserverBlocks` to release.
@param keyPath The key path, relative to the receiver, of the property to
observe. This value must not be nil.
@param block The block to register for KVO notifications.
*/
- (void)addObserverBlockForKeyPath:(NSString*)keyPath
block:(void (^)(id _Nonnull obj, id _Nonnull oldVal, id _Nonnull newVal))block;
/**
Stops all blocks (associated by `addObserverBlockForKeyPath:block:`) from
receiving change notifications for the property specified by a given key-path
relative to the receiver, and release these blocks.
@param keyPath A key-path, relative to the receiver, for which blocks is
registered to receive KVO change notifications.
*/
- (void)removeObserverBlocksForKeyPath:(NSString*)keyPath;
/**
Stops all blocks (associated by `addObserverBlockForKeyPath:block:`) from
receiving change notifications, and release these blocks.
*/
- (void)removeObserverBlocks;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,110 @@
//
// NSObject+YYAddForKVO.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSObject+YYAddForKVO.h"
#import "YYCategoriesMacro.h"
#import <objc/objc.h>
#import <objc/runtime.h>
YYSYNTH_DUMMY_CLASS(NSObject_YYAddForKVO)
static const int block_key;
@interface _YYNSObjectKVOBlockTarget : NSObject
@property (nonatomic, copy) void (^block)(__weak id obj, id oldVal, id newVal);
- (id)initWithBlock:(void (^)(__weak id obj, id oldVal, id newVal))block;
@end
@implementation _YYNSObjectKVOBlockTarget
- (id)initWithBlock:(void (^)(__weak id obj, id oldVal, id newVal))block {
self = [super init];
if (self) {
self.block = block;
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (!self.block) return;
BOOL isPrior = [[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
if (isPrior) return;
NSKeyValueChange changeKind = [[change objectForKey:NSKeyValueChangeKindKey] integerValue];
if (changeKind != NSKeyValueChangeSetting) return;
id oldVal = [change objectForKey:NSKeyValueChangeOldKey];
if (oldVal == [NSNull null]) oldVal = nil;
id newVal = [change objectForKey:NSKeyValueChangeNewKey];
if (newVal == [NSNull null]) newVal = nil;
self.block(object, oldVal, newVal);
}
@end
@implementation NSObject (YYAddForKVO)
- (void)addObserverBlockForKeyPath:(NSString *)keyPath block:(void (^)(__weak id obj, id oldVal, id newVal))block {
if (!keyPath || !block) return;
_YYNSObjectKVOBlockTarget *target = [[_YYNSObjectKVOBlockTarget alloc] initWithBlock:block];
NSMutableDictionary *dic = [self _yy_allNSObjectObserverBlocks];
NSMutableArray *arr = dic[keyPath];
if (!arr) {
arr = [NSMutableArray new];
dic[keyPath] = arr;
}
[arr addObject:target];
[self addObserver:target forKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
}
- (void)removeObserverBlocksForKeyPath:(NSString *)keyPath {
if (!keyPath) return;
NSMutableDictionary *dic = [self _yy_allNSObjectObserverBlocks];
NSMutableArray *arr = dic[keyPath];
[arr enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
[self removeObserver:obj forKeyPath:keyPath];
}];
[dic removeObjectForKey:keyPath];
}
- (void)removeObserverBlocks {
NSMutableDictionary *dic = [self _yy_allNSObjectObserverBlocks];
[dic enumerateKeysAndObjectsUsingBlock: ^(NSString *key, NSArray *arr, BOOL *stop) {
[arr enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
[self removeObserver:obj forKeyPath:key];
}];
}];
[dic removeAllObjects];
}
- (NSMutableDictionary *)_yy_allNSObjectObserverBlocks {
NSMutableDictionary *targets = objc_getAssociatedObject(self, &block_key);
if (!targets) {
targets = [NSMutableDictionary new];
objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return targets;
}
@end

View File

@@ -0,0 +1,409 @@
//
// NSString+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide hash, encrypt, encode and some common method for 'NSString'.
*/
@interface NSString (YYAdd)
#pragma mark - Hash
///=============================================================================
/// @name Hash
///=============================================================================
/**
Returns a lowercase NSString for md2 hash.
*/
- (nullable NSString *)md2String;
/**
Returns a lowercase NSString for md4 hash.
*/
- (nullable NSString *)md4String;
/**
Returns a lowercase NSString for md5 hash.
*/
- (nullable NSString *)md5String;
/**
Returns a lowercase NSString for sha1 hash.
*/
- (nullable NSString *)sha1String;
/**
Returns a lowercase NSString for sha224 hash.
*/
- (nullable NSString *)sha224String;
/**
Returns a lowercase NSString for sha256 hash.
*/
- (nullable NSString *)sha256String;
/**
Returns a lowercase NSString for sha384 hash.
*/
- (nullable NSString *)sha384String;
/**
Returns a lowercase NSString for sha512 hash.
*/
- (nullable NSString *)sha512String;
/**
Returns a lowercase NSString for hmac using algorithm md5 with key.
@param key The hmac key.
*/
- (nullable NSString *)hmacMD5StringWithKey:(NSString *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha1 with key.
@param key The hmac key.
*/
- (nullable NSString *)hmacSHA1StringWithKey:(NSString *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha224 with key.
@param key The hmac key.
*/
- (nullable NSString *)hmacSHA224StringWithKey:(NSString *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha256 with key.
@param key The hmac key.
*/
- (nullable NSString *)hmacSHA256StringWithKey:(NSString *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha384 with key.
@param key The hmac key.
*/
- (nullable NSString *)hmacSHA384StringWithKey:(NSString *)key;
/**
Returns a lowercase NSString for hmac using algorithm sha512 with key.
@param key The hmac key.
*/
- (nullable NSString *)hmacSHA512StringWithKey:(NSString *)key;
/**
Returns a lowercase NSString for crc32 hash.
*/
- (nullable NSString *)crc32String;
#pragma mark - Encode and decode
///=============================================================================
/// @name Encode and decode
///=============================================================================
/**
Returns an NSString for base64 encoded.
*/
- (nullable NSString *)base64EncodedString;
/**
Returns an NSString from base64 encoded string.
@param base64Encoding The encoded string.
*/
+ (nullable NSString *)stringWithBase64EncodedString:(NSString *)base64EncodedString;
/**
URL encode a string in utf-8.
@return the encoded string.
*/
- (NSString *)stringByURLEncode;
/**
URL decode a string in utf-8.
@return the decoded string.
*/
- (NSString *)stringByURLDecode;
/**
Escape commmon HTML to Entity.
Example: "a<b" will be escape to "a&lt;b".
*/
- (NSString *)stringByEscapingHTML;
#pragma mark - Drawing
///=============================================================================
/// @name Drawing
///=============================================================================
/**
Returns the size of the string if it were rendered with the specified constraints.
@param font The font to use for computing the string size.
@param size The maximum acceptable size for the string. This value is
used to calculate where line breaks and wrapping would occur.
@param lineBreakMode The line break options for computing the size of the string.
For a list of possible values, see NSLineBreakMode.
@return The width and height of the resulting string's bounding box.
These values may be rounded up to the nearest whole number.
*/
- (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode;
/**
Returns the width of the string if it were to be rendered with the specified
font on a single line.
@param font The font to use for computing the string width.
@return The width of the resulting string's bounding box. These values may be
rounded up to the nearest whole number.
*/
- (CGFloat)widthForFont:(UIFont *)font;
/**
Returns the height of the string if it were rendered with the specified constraints.
@param font The font to use for computing the string size.
@param width The maximum acceptable width for the string. This value is used
to calculate where line breaks and wrapping would occur.
@return The height of the resulting string's bounding box. These values
may be rounded up to the nearest whole number.
*/
- (CGFloat)heightForFont:(UIFont *)font width:(CGFloat)width;
#pragma mark - Regular Expression
///=============================================================================
/// @name Regular Expression
///=============================================================================
/**
Whether it can match the regular expression
@param regex The regular expression
@param options The matching options to report.
@return YES if can match the regex; otherwise, NO.
*/
- (BOOL)matchesRegex:(NSString *)regex options:(NSRegularExpressionOptions)options;
/**
Match the regular expression, and executes a given block using each object in the matches.
@param regex The regular expression
@param options The matching options to report.
@param block The block to apply to elements in the array of matches.
The block takes four arguments:
match: The match substring.
matchRange: The matching options.
stop: A reference to a Boolean value. The block can set the value
to YES to stop further processing of the array. The stop
argument is an out-only argument. You should only ever set
this Boolean to YES within the Block.
*/
- (void)enumerateRegexMatches:(NSString *)regex
options:(NSRegularExpressionOptions)options
usingBlock:(void (^)(NSString *match, NSRange matchRange, BOOL *stop))block;
/**
Returns a new string containing matching regular expressions replaced with the template string.
@param regex The regular expression
@param options The matching options to report.
@param replacement The substitution template used when replacing matching instances.
@return A string with matching regular expressions replaced by the template string.
*/
- (NSString *)stringByReplacingRegex:(NSString *)regex
options:(NSRegularExpressionOptions)options
withString:(NSString *)replacement;
#pragma mark - NSNumber Compatible
///=============================================================================
/// @name NSNumber Compatible
///=============================================================================
// Now you can use NSString as a NSNumber.
@property (readonly) char charValue;
@property (readonly) unsigned char unsignedCharValue;
@property (readonly) short shortValue;
@property (readonly) unsigned short unsignedShortValue;
@property (readonly) unsigned int unsignedIntValue;
@property (readonly) long longValue;
@property (readonly) unsigned long unsignedLongValue;
@property (readonly) unsigned long long unsignedLongLongValue;
@property (readonly) NSUInteger unsignedIntegerValue;
#pragma mark - Utilities
///=============================================================================
/// @name Utilities
///=============================================================================
/**
Returns a new UUID NSString
e.g. "D1178E50-2A4D-4F1F-9BD3-F6AAB00E06B1"
*/
+ (NSString *)stringWithUUID;
/**
Returns a string containing the characters in a given UTF32Char.
@param char32 A UTF-32 character.
@return A new string, or nil if the character is invalid.
*/
+ (NSString *)stringWithUTF32Char:(UTF32Char)char32;
/**
Returns a string containing the characters in a given UTF32Char array.
@param char32 An array of UTF-32 character.
@param length The character count in array.
@return A new string, or nil if an error occurs.
*/
+ (NSString *)stringWithUTF32Chars:(const UTF32Char *)char32 length:(NSUInteger)length;
/**
Enumerates the unicode characters (UTF-32) in the specified range of the string.
@param range The range within the string to enumerate substrings.
@param block The block executed for the enumeration. The block takes four arguments:
char32: The unicode character.
range: The range in receiver. If the range.length is 1, the character is in BMP;
otherwise (range.length is 2) the character is in none-BMP Plane and stored
by a surrogate pair in the receiver.
stop: A reference to a Boolean value that the block can use to stop the enumeration
by setting *stop = YES; it should not touch *stop otherwise.
*/
- (void)enumerateUTF32CharInRange:(NSRange)range usingBlock:(void (^)(UTF32Char char32, NSRange range, BOOL *stop))block;
/**
Trim blank characters (space and newline) in head and tail.
@return the trimmed string.
*/
- (NSString *)stringByTrim;
/**
Add scale modifier to the file name (without path extension),
From @"name" to @"name@2x".
e.g.
<table>
<tr><th>Before </th><th>After(scale:2)</th></tr>
<tr><td>"icon" </td><td>"icon@2x" </td></tr>
<tr><td>"icon " </td><td>"icon @2x" </td></tr>
<tr><td>"icon.top" </td><td>"icon.top@2x" </td></tr>
<tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr>
<tr><td>"/path/" </td><td>"/path/" </td></tr>
</table>
@param scale Resource scale.
@return String by add scale modifier, or just return if it's not end with file name.
*/
- (NSString *)stringByAppendingNameScale:(CGFloat)scale;
/**
Add scale modifier to the file path (with path extension),
From @"name.png" to @"name@2x.png".
e.g.
<table>
<tr><th>Before </th><th>After(scale:2)</th></tr>
<tr><td>"icon.png" </td><td>"icon@2x.png" </td></tr>
<tr><td>"icon..png"</td><td>"icon.@2x.png"</td></tr>
<tr><td>"icon" </td><td>"icon@2x" </td></tr>
<tr><td>"icon " </td><td>"icon @2x" </td></tr>
<tr><td>"icon." </td><td>"icon.@2x" </td></tr>
<tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr>
<tr><td>"/path/" </td><td>"/path/" </td></tr>
</table>
@param scale Resource scale.
@return String by add scale modifier, or just return if it's not end with file name.
*/
- (NSString *)stringByAppendingPathScale:(CGFloat)scale;
/**
Return the path scale.
e.g.
<table>
<tr><th>Path </th><th>Scale </th></tr>
<tr><td>"icon.png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png" </td><td>2 </td></tr>
<tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr>
<tr><td>"icon@2x" </td><td>1 </td></tr>
<tr><td>"icon@2x..png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png/" </td><td>1 </td></tr>
</table>
*/
- (CGFloat)pathScale;
/**
nil, @"", @" ", @"\n" will Returns NO; otherwise Returns YES.
*/
- (BOOL)isNotBlank;
/**
Returns YES if the target string is contained within the receiver.
@param string A string to test the the receiver.
@discussion Apple has implemented this method in iOS8.
*/
- (BOOL)containsString:(NSString *)string;
/**
Returns YES if the target CharacterSet is contained within the receiver.
@param set A character set to test the the receiver.
*/
- (BOOL)containsCharacterSet:(NSCharacterSet *)set;
/**
Try to parse this string and returns an `NSNumber`.
@return Returns an `NSNumber` if parse succeed, or nil if an error occurs.
*/
- (NSNumber *)numberValue;
/**
Returns an NSData using UTF-8 encoding.
*/
- (NSData *)dataValue;
/**
Returns NSMakeRange(0, self.length).
*/
- (NSRange)rangeOfAll;
/**
Returns an NSDictionary/NSArray which is decoded from receiver.
Returns nil if an error occurs.
e.g. NSString: @"{"name":"a","count":2}" => NSDictionary: @[@"name":@"a",@"count":@2]
*/
- (id)jsonValueDecoded;
/**
Create a string from the file in main bundle (similar to [UIImage imageNamed:]).
@param name The file name (in main bundle).
@return A new string create from the file in UTF-8 character encoding.
*/
+ (NSString *)stringNamed:(NSString *)name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,414 @@
//
// NSString+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSString+YYAdd.h"
#import "NSData+YYAdd.h"
#import "NSNumber+YYAdd.h"
#import "UIDevice+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(NSString_YYAdd)
@implementation NSString (YYAdd)
- (NSString *)md2String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] md2String];
}
- (NSString *)md4String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] md4String];
}
- (NSString *)md5String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] md5String];
}
- (NSString *)sha1String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] sha1String];
}
- (NSString *)sha224String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] sha224String];
}
- (NSString *)sha256String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] sha256String];
}
- (NSString *)sha384String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] sha384String];
}
- (NSString *)sha512String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] sha512String];
}
- (NSString *)crc32String {
return [[self dataUsingEncoding:NSUTF8StringEncoding] crc32String];
}
- (NSString *)hmacMD5StringWithKey:(NSString *)key {
return [[self dataUsingEncoding:NSUTF8StringEncoding]
hmacMD5StringWithKey:key];
}
- (NSString *)hmacSHA1StringWithKey:(NSString *)key {
return [[self dataUsingEncoding:NSUTF8StringEncoding]
hmacSHA1StringWithKey:key];
}
- (NSString *)hmacSHA224StringWithKey:(NSString *)key {
return [[self dataUsingEncoding:NSUTF8StringEncoding]
hmacSHA224StringWithKey:key];
}
- (NSString *)hmacSHA256StringWithKey:(NSString *)key {
return [[self dataUsingEncoding:NSUTF8StringEncoding]
hmacSHA256StringWithKey:key];
}
- (NSString *)hmacSHA384StringWithKey:(NSString *)key {
return [[self dataUsingEncoding:NSUTF8StringEncoding]
hmacSHA384StringWithKey:key];
}
- (NSString *)hmacSHA512StringWithKey:(NSString *)key {
return [[self dataUsingEncoding:NSUTF8StringEncoding]
hmacSHA512StringWithKey:key];
}
- (NSString *)base64EncodedString {
return [[self dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString];
}
+ (NSString *)stringWithBase64EncodedString:(NSString *)base64EncodedString {
NSData *data = [NSData dataWithBase64EncodedString:base64EncodedString];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
- (NSString *)stringByURLEncode {
if ([self respondsToSelector:@selector(stringByAddingPercentEncodingWithAllowedCharacters:)]) {
/**
AFNetworking/AFURLRequestSerialization.m
Returns a percent-escaped string following RFC 3986 for a query string key or value.
RFC 3986 states that the following characters are "reserved" characters.
- General Delimiters: ":", "#", "[", "]", "@", "?", "/"
- Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
should be percent-escaped in the query string.
- parameter string: The string to be percent-escaped.
- returns: The percent-escaped string.
*/
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < self.length) {
NSUInteger length = MIN(self.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as 👴🏻👮🏽
range = [self rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [self substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding);
NSString *encoded = (__bridge_transfer NSString *)
CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(__bridge CFStringRef)self,
NULL,
CFSTR("!#$&'()*+,/:;=?@[]"),
cfEncoding);
return encoded;
#pragma clang diagnostic pop
}
}
- (NSString *)stringByURLDecode {
if ([self respondsToSelector:@selector(stringByRemovingPercentEncoding)]) {
return [self stringByRemovingPercentEncoding];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CFStringEncoding en = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding);
NSString *decoded = [self stringByReplacingOccurrencesOfString:@"+"
withString:@" "];
decoded = (__bridge_transfer NSString *)
CFURLCreateStringByReplacingPercentEscapesUsingEncoding(
NULL,
(__bridge CFStringRef)decoded,
CFSTR(""),
en);
return decoded;
#pragma clang diagnostic pop
}
}
- (NSString *)stringByEscapingHTML {
NSUInteger len = self.length;
if (!len) return self;
unichar *buf = malloc(sizeof(unichar) * len);
if (!buf) return self;
[self getCharacters:buf range:NSMakeRange(0, len)];
NSMutableString *result = [NSMutableString string];
for (int i = 0; i < len; i++) {
unichar c = buf[i];
NSString *esc = nil;
switch (c) {
case 34: esc = @"&quot;"; break;
case 38: esc = @"&amp;"; break;
case 39: esc = @"&apos;"; break;
case 60: esc = @"&lt;"; break;
case 62: esc = @"&gt;"; break;
default: break;
}
if (esc) {
[result appendString:esc];
} else {
CFStringAppendCharacters((CFMutableStringRef)result, &c, 1);
}
}
free(buf);
return result;
}
- (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode {
CGSize result;
if (!font) font = [UIFont systemFontOfSize:12];
if ([self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) {
NSMutableDictionary *attr = [NSMutableDictionary new];
attr[NSFontAttributeName] = font;
if (lineBreakMode != NSLineBreakByWordWrapping) {
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineBreakMode = lineBreakMode;
attr[NSParagraphStyleAttributeName] = paragraphStyle;
}
CGRect rect = [self boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:attr context:nil];
result = rect.size;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
result = [self sizeWithFont:font constrainedToSize:size lineBreakMode:lineBreakMode];
#pragma clang diagnostic pop
}
return result;
}
- (CGFloat)widthForFont:(UIFont *)font {
CGSize size = [self sizeForFont:font size:CGSizeMake(HUGE, HUGE) mode:NSLineBreakByWordWrapping];
return size.width;
}
- (CGFloat)heightForFont:(UIFont *)font width:(CGFloat)width {
CGSize size = [self sizeForFont:font size:CGSizeMake(width, HUGE) mode:NSLineBreakByWordWrapping];
return size.height;
}
- (BOOL)matchesRegex:(NSString *)regex options:(NSRegularExpressionOptions)options {
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:NULL];
if (!pattern) return NO;
return ([pattern numberOfMatchesInString:self options:0 range:NSMakeRange(0, self.length)] > 0);
}
- (void)enumerateRegexMatches:(NSString *)regex
options:(NSRegularExpressionOptions)options
usingBlock:(void (^)(NSString *match, NSRange matchRange, BOOL *stop))block {
if (regex.length == 0 || !block) return;
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:nil];
if (!regex) return;
[pattern enumerateMatchesInString:self options:kNilOptions range:NSMakeRange(0, self.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
block([self substringWithRange:result.range], result.range, stop);
}];
}
- (NSString *)stringByReplacingRegex:(NSString *)regex
options:(NSRegularExpressionOptions)options
withString:(NSString *)replacement; {
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:nil];
if (!pattern) return self;
return [pattern stringByReplacingMatchesInString:self options:0 range:NSMakeRange(0, [self length]) withTemplate:replacement];
}
- (char)charValue {
return self.numberValue.charValue;
}
- (unsigned char) unsignedCharValue {
return self.numberValue.unsignedCharValue;
}
- (short) shortValue {
return self.numberValue.shortValue;
}
- (unsigned short) unsignedShortValue {
return self.numberValue.unsignedShortValue;
}
- (unsigned int) unsignedIntValue {
return self.numberValue.unsignedIntValue;
}
- (long) longValue {
return self.numberValue.longValue;
}
- (unsigned long) unsignedLongValue {
return self.numberValue.unsignedLongValue;
}
- (unsigned long long) unsignedLongLongValue {
return self.numberValue.unsignedLongLongValue;
}
- (NSUInteger) unsignedIntegerValue {
return self.numberValue.unsignedIntegerValue;
}
+ (NSString *)stringWithUUID {
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
return (__bridge_transfer NSString *)string;
}
+ (NSString *)stringWithUTF32Char:(UTF32Char)char32 {
char32 = NSSwapHostIntToLittle(char32);
return [[NSString alloc] initWithBytes:&char32 length:4 encoding:NSUTF32LittleEndianStringEncoding];
}
+ (NSString *)stringWithUTF32Chars:(const UTF32Char *)char32 length:(NSUInteger)length {
return [[NSString alloc] initWithBytes:(const void *)char32
length:length * 4
encoding:NSUTF32LittleEndianStringEncoding];
}
- (void)enumerateUTF32CharInRange:(NSRange)range usingBlock:(void (^)(UTF32Char char32, NSRange range, BOOL *stop))block {
NSString *str = self;
if (range.location != 0 || range.length != self.length) {
str = [self substringWithRange:range];
}
NSUInteger len = [str lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
UTF32Char *char32 = (UTF32Char *)[str cStringUsingEncoding:NSUTF32LittleEndianStringEncoding];
if (len == 0 || char32 == NULL) return;
NSUInteger location = 0;
BOOL stop = NO;
NSRange subRange;
UTF32Char oneChar;
for (NSUInteger i = 0; i < len; i++) {
oneChar = char32[i];
subRange = NSMakeRange(location, oneChar > 0xFFFF ? 2 : 1);
block(oneChar, subRange, &stop);
if (stop) return;
location += subRange.length;
}
}
- (NSString *)stringByTrim {
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
return [self stringByTrimmingCharactersInSet:set];
}
- (NSString *)stringByAppendingNameScale:(CGFloat)scale {
if (fabs(scale - 1) <= __FLT_EPSILON__ || self.length == 0 || [self hasSuffix:@"/"]) return self.copy;
return [self stringByAppendingFormat:@"@%@x", @(scale)];
}
- (NSString *)stringByAppendingPathScale:(CGFloat)scale {
if (fabs(scale - 1) <= __FLT_EPSILON__ || self.length == 0 || [self hasSuffix:@"/"]) return self.copy;
NSString *ext = self.pathExtension;
NSRange extRange = NSMakeRange(self.length - ext.length, 0);
if (ext.length > 0) extRange.location -= 1;
NSString *scaleStr = [NSString stringWithFormat:@"@%@x", @(scale)];
return [self stringByReplacingCharactersInRange:extRange withString:scaleStr];
}
- (CGFloat)pathScale {
if (self.length == 0 || [self hasSuffix:@"/"]) return 1;
NSString *name = self.stringByDeletingPathExtension;
__block CGFloat scale = 1;
[name enumerateRegexMatches:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines usingBlock: ^(NSString *match, NSRange matchRange, BOOL *stop) {
scale = [match substringWithRange:NSMakeRange(1, match.length - 2)].doubleValue;
}];
return scale;
}
- (BOOL)isNotBlank {
NSCharacterSet *blank = [NSCharacterSet whitespaceAndNewlineCharacterSet];
for (NSInteger i = 0; i < self.length; ++i) {
unichar c = [self characterAtIndex:i];
if (![blank characterIsMember:c]) {
return YES;
}
}
return NO;
}
- (BOOL)containsString:(NSString *)string {
if (string == nil) return NO;
return [self rangeOfString:string].location != NSNotFound;
}
- (BOOL)containsCharacterSet:(NSCharacterSet *)set {
if (set == nil) return NO;
return [self rangeOfCharacterFromSet:set].location != NSNotFound;
}
- (NSNumber *)numberValue {
return [NSNumber numberWithString:self];
}
- (NSData *)dataValue {
return [self dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSRange)rangeOfAll {
return NSMakeRange(0, self.length);
}
- (id)jsonValueDecoded {
return [[self dataValue] jsonValueDecoded];
}
+ (NSString *)stringNamed:(NSString *)name {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@""];
NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
if (!str) {
path = [[NSBundle mainBundle] pathForResource:name ofType:@"txt"];
str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
}
return str;
}
@end

View File

@@ -0,0 +1,25 @@
//
// NSThread+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 15/7/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
@interface NSThread (YYAdd)
/**
Add an autorelease pool to current runloop for current thread.
@discussion If you create your own thread (NSThread/pthread), and you use
runloop to manage your task, you may use this method to add an autorelease pool
to the runloop. Its behavior is the same as the main thread's autorelease pool.
*/
+ (void)addAutoreleasePoolToCurrentRunloop;
@end

View File

@@ -0,0 +1,107 @@
//
// NSThread+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 15/7/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSThread+YYAdd.h"
#import <CoreFoundation/CoreFoundation.h>
@interface NSThread_YYAdd : NSObject @end
@implementation NSThread_YYAdd @end
#if __has_feature(objc_arc)
#error This file must be compiled without ARC. Specify the -fno-objc-arc flag to this file.
#endif
static NSString *const YYNSThreadAutoleasePoolKey = @"YYNSThreadAutoleasePoolKey";
static NSString *const YYNSThreadAutoleasePoolStackKey = @"YYNSThreadAutoleasePoolStackKey";
static const void *PoolStackRetainCallBack(CFAllocatorRef allocator, const void *value) {
return value;
}
static void PoolStackReleaseCallBack(CFAllocatorRef allocator, const void *value) {
CFRelease((CFTypeRef)value);
}
static inline void YYAutoreleasePoolPush() {
NSMutableDictionary *dic = [NSThread currentThread].threadDictionary;
NSMutableArray *poolStack = dic[YYNSThreadAutoleasePoolStackKey];
if (!poolStack) {
/*
do not retain pool on push,
but release on pop to avoid memory analyze warning
*/
CFArrayCallBacks callbacks = {0};
callbacks.retain = PoolStackRetainCallBack;
callbacks.release = PoolStackReleaseCallBack;
poolStack = (id)CFArrayCreateMutable(CFAllocatorGetDefault(), 0, &callbacks);
dic[YYNSThreadAutoleasePoolStackKey] = poolStack;
CFRelease(poolStack);
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //< create
[poolStack addObject:pool]; // push
}
static inline void YYAutoreleasePoolPop() {
NSMutableDictionary *dic = [NSThread currentThread].threadDictionary;
NSMutableArray *poolStack = dic[YYNSThreadAutoleasePoolStackKey];
[poolStack removeLastObject]; // pop
}
static void YYRunLoopAutoreleasePoolObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
case kCFRunLoopEntry: {
YYAutoreleasePoolPush();
} break;
case kCFRunLoopBeforeWaiting: {
YYAutoreleasePoolPop();
YYAutoreleasePoolPush();
} break;
case kCFRunLoopExit: {
YYAutoreleasePoolPop();
} break;
default: break;
}
}
static void YYRunloopAutoreleasePoolSetup() {
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFRunLoopObserverRef pushObserver;
pushObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopEntry,
true, // repeat
-0x7FFFFFFF, // before other observers
YYRunLoopAutoreleasePoolObserverCallBack, NULL);
CFRunLoopAddObserver(runloop, pushObserver, kCFRunLoopCommonModes);
CFRelease(pushObserver);
CFRunLoopObserverRef popObserver;
popObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit,
true, // repeat
0x7FFFFFFF, // after other observers
YYRunLoopAutoreleasePoolObserverCallBack, NULL);
CFRunLoopAddObserver(runloop, popObserver, kCFRunLoopCommonModes);
CFRelease(popObserver);
}
@implementation NSThread (YYAdd)
+ (void)addAutoreleasePoolToCurrentRunloop {
if ([NSThread isMainThread]) return; // The main thread already has autorelease pool.
NSThread *thread = [self currentThread];
if (!thread) return;
if (thread.threadDictionary[YYNSThreadAutoleasePoolKey]) return; // already added
YYRunloopAutoreleasePoolSetup();
thread.threadDictionary[YYNSThreadAutoleasePoolKey] = YYNSThreadAutoleasePoolKey; // mark the state
}
@end

View File

@@ -0,0 +1,66 @@
//
// NSTimer+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/15/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `NSTimer`.
*/
@interface NSTimer (YYAdd)
/**
Creates and returns a new NSTimer object and schedules it on the current run
loop in the default mode.
@discussion After seconds seconds have elapsed, the timer fires,
sending the message aSelector to target.
@param seconds The number of seconds between firings of the timer. If seconds
is less than or equal to 0.0, this method chooses the
nonnegative value of 0.1 milliseconds instead.
@param block The block to invoke when the timer fires. The timer maintains
a strong reference to the block until it (the timer) is invalidated.
@param repeats If YES, the timer will repeatedly reschedule itself until
invalidated. If NO, the timer will be invalidated after it fires.
@return A new NSTimer object, configured according to the specified parameters.
*/
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;
/**
Creates and returns a new NSTimer object initialized with the specified block.
@discussion You must add the new timer to a run loop, using addTimer:forMode:.
Then, after seconds have elapsed, the timer fires, invoking
block. (If the timer is configured to repeat, there is no need
to subsequently re-add the timer to the run loop.)
@param seconds The number of seconds between firings of the timer. If seconds
is less than or equal to 0.0, this method chooses the
nonnegative value of 0.1 milliseconds instead.
@param block The block to invoke when the timer fires. The timer instructs
the block to maintain a strong reference to its arguments.
@param repeats If YES, the timer will repeatedly reschedule itself until
invalidated. If NO, the timer will be invalidated after it fires.
@return A new NSTimer object, configured according to the specified parameters.
*/
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,35 @@
//
// NSTimer+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/15/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSTimer+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(NSTimer_YYAdd)
@implementation NSTimer (YYAdd)
+ (void)_yy_ExecBlock:(NSTimer *)timer {
if ([timer userInfo]) {
void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];
block(timer);
}
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
return [NSTimer timerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}
@end

View File

@@ -0,0 +1,97 @@
//
// CALayer+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/10.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `CALayer`.
*/
@interface CALayer (YYAdd)
/**
Take snapshot without transform, image's size equals to bounds.
*/
- (nullable UIImage *)snapshotImage;
/**
Take snapshot without transform, PDF's page size equals to bounds.
*/
- (nullable NSData *)snapshotPDF;
/**
Shortcut to set the layer's shadow
@param color Shadow Color
@param offset Shadow offset
@param radius Shadow radius
*/
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius;
/**
Remove all sublayers.
*/
- (void)removeAllSublayers;
@property (nonatomic) CGFloat left; ///< Shortcut for frame.origin.x.
@property (nonatomic) CGFloat top; ///< Shortcut for frame.origin.y
@property (nonatomic) CGFloat right; ///< Shortcut for frame.origin.x + frame.size.width
@property (nonatomic) CGFloat bottom; ///< Shortcut for frame.origin.y + frame.size.height
@property (nonatomic) CGFloat width; ///< Shortcut for frame.size.width.
@property (nonatomic) CGFloat height; ///< Shortcut for frame.size.height.
@property (nonatomic) CGPoint center; ///< Shortcut for center.
@property (nonatomic) CGFloat centerX; ///< Shortcut for center.x
@property (nonatomic) CGFloat centerY; ///< Shortcut for center.y
@property (nonatomic) CGPoint origin; ///< Shortcut for frame.origin.
@property (nonatomic, getter=frameSize, setter=setFrameSize:) CGSize size; ///< Shortcut for frame.size.
@property (nonatomic) CGFloat transformRotation; ///< key path "tranform.rotation"
@property (nonatomic) CGFloat transformRotationX; ///< key path "tranform.rotation.x"
@property (nonatomic) CGFloat transformRotationY; ///< key path "tranform.rotation.y"
@property (nonatomic) CGFloat transformRotationZ; ///< key path "tranform.rotation.z"
@property (nonatomic) CGFloat transformScale; ///< key path "tranform.scale"
@property (nonatomic) CGFloat transformScaleX; ///< key path "tranform.scale.x"
@property (nonatomic) CGFloat transformScaleY; ///< key path "tranform.scale.y"
@property (nonatomic) CGFloat transformScaleZ; ///< key path "tranform.scale.z"
@property (nonatomic) CGFloat transformTranslationX; ///< key path "tranform.translation.x"
@property (nonatomic) CGFloat transformTranslationY; ///< key path "tranform.translation.y"
@property (nonatomic) CGFloat transformTranslationZ; ///< key path "tranform.translation.z"
/**
Shortcut for transform.m34, -1/1000 is a good value.
It should be set before other transform shortcut.
*/
@property (nonatomic) CGFloat transformDepth;
/**
Wrapper for `contentsGravity` property.
*/
@property (nonatomic) UIViewContentMode contentMode;
/**
Add a fade animation to layer's contents when the contents is changed.
@param duration Animation duration
@param curve Animation curve.
*/
- (void)addFadeAnimationWithDuration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve;
/**
Cancel fade animation which is added with "-addFadeAnimationWithDuration:curve:".
*/
- (void)removePreviousFadeAnimation;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,324 @@
//
// CALayer+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/10.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "CALayer+YYAdd.h"
#import "YYCategoriesMacro.h"
#import "YYCGUtilities.h"
YYSYNTH_DUMMY_CLASS(CALayer_YYAdd)
@implementation CALayer (YYAdd)
- (UIImage *)snapshotImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
[self renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (NSData *)snapshotPDF {
CGRect bounds = self.bounds;
NSMutableData* data = [NSMutableData data];
CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData((__bridge CFMutableDataRef)data);
CGContextRef context = CGPDFContextCreate(consumer, &bounds, NULL);
CGDataConsumerRelease(consumer);
if (!context) return nil;
CGPDFContextBeginPage(context, NULL);
CGContextTranslateCTM(context, 0, bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
[self renderInContext:context];
CGPDFContextEndPage(context);
CGPDFContextClose(context);
CGContextRelease(context);
return data;
}
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius {
self.shadowColor = color.CGColor;
self.shadowOffset = offset;
self.shadowRadius = radius;
self.shadowOpacity = 1;
self.shouldRasterize = YES;
self.rasterizationScale = [UIScreen mainScreen].scale;
}
- (void)removeAllSublayers {
while (self.sublayers.count) {
[self.sublayers.lastObject removeFromSuperlayer];
}
}
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)top {
return self.frame.origin.y;
}
- (void)setTop:(CGFloat)y {
CGRect frame = self.frame;
frame.origin.y = y;
self.frame = frame;
}
- (CGFloat)right {
return self.frame.origin.x + self.frame.size.width;
}
- (void)setRight:(CGFloat)right {
CGRect frame = self.frame;
frame.origin.x = right - frame.size.width;
self.frame = frame;
}
- (CGFloat)bottom {
return self.frame.origin.y + self.frame.size.height;
}
- (void)setBottom:(CGFloat)bottom {
CGRect frame = self.frame;
frame.origin.y = bottom - frame.size.height;
self.frame = frame;
}
- (CGFloat)width {
return self.frame.size.width;
}
- (void)setWidth:(CGFloat)width {
CGRect frame = self.frame;
frame.size.width = width;
self.frame = frame;
}
- (CGFloat)height {
return self.frame.size.height;
}
- (void)setHeight:(CGFloat)height {
CGRect frame = self.frame;
frame.size.height = height;
self.frame = frame;
}
- (CGPoint)center {
return CGPointMake(self.frame.origin.x + self.frame.size.width * 0.5,
self.frame.origin.y + self.frame.size.height * 0.5);
}
- (void)setCenter:(CGPoint)center {
CGRect frame = self.frame;
frame.origin.x = center.x - frame.size.width * 0.5;
frame.origin.y = center.y - frame.size.height * 0.5;
self.frame = frame;
}
- (CGFloat)centerX {
return self.frame.origin.x + self.frame.size.width * 0.5;
}
- (void)setCenterX:(CGFloat)centerX {
CGRect frame = self.frame;
frame.origin.x = centerX - frame.size.width * 0.5;
self.frame = frame;
}
- (CGFloat)centerY {
return self.frame.origin.y + self.frame.size.height * 0.5;
}
- (void)setCenterY:(CGFloat)centerY {
CGRect frame = self.frame;
frame.origin.y = centerY - frame.size.height * 0.5;
self.frame = frame;
}
- (CGPoint)origin {
return self.frame.origin;
}
- (void)setOrigin:(CGPoint)origin {
CGRect frame = self.frame;
frame.origin = origin;
self.frame = frame;
}
- (CGSize)frameSize {
return self.frame.size;
}
- (void)setFrameSize:(CGSize)size {
CGRect frame = self.frame;
frame.size = size;
self.frame = frame;
}
- (CGFloat)transformRotation {
NSNumber *v = [self valueForKeyPath:@"transform.rotation"];
return v.doubleValue;
}
- (void)setTransformRotation:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation"];
}
- (CGFloat)transformRotationX {
NSNumber *v = [self valueForKeyPath:@"transform.rotation.x"];
return v.doubleValue;
}
- (void)setTransformRotationX:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation.x"];
}
- (CGFloat)transformRotationY {
NSNumber *v = [self valueForKeyPath:@"transform.rotation.y"];
return v.doubleValue;
}
- (void)setTransformRotationY:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation.y"];
}
- (CGFloat)transformRotationZ {
NSNumber *v = [self valueForKeyPath:@"transform.rotation.z"];
return v.doubleValue;
}
- (void)setTransformRotationZ:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation.z"];
}
- (CGFloat)transformScaleX {
NSNumber *v = [self valueForKeyPath:@"transform.scale.x"];
return v.doubleValue;
}
- (void)setTransformScaleX:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale.x"];
}
- (CGFloat)transformScaleY {
NSNumber *v = [self valueForKeyPath:@"transform.scale.y"];
return v.doubleValue;
}
- (void)setTransformScaleY:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale.y"];
}
- (CGFloat)transformScaleZ {
NSNumber *v = [self valueForKeyPath:@"transform.scale.z"];
return v.doubleValue;
}
- (void)setTransformScaleZ:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale.z"];
}
- (CGFloat)transformScale {
NSNumber *v = [self valueForKeyPath:@"transform.scale"];
return v.doubleValue;
}
- (void)setTransformScale:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale"];
}
- (CGFloat)transformTranslationX {
NSNumber *v = [self valueForKeyPath:@"transform.translation.x"];
return v.doubleValue;
}
- (void)setTransformTranslationX:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.translation.x"];
}
- (CGFloat)transformTranslationY {
NSNumber *v = [self valueForKeyPath:@"transform.translation.y"];
return v.doubleValue;
}
- (void)setTransformTranslationY:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.translation.y"];
}
- (CGFloat)transformTranslationZ {
NSNumber *v = [self valueForKeyPath:@"transform.translation.z"];
return v.doubleValue;
}
- (void)setTransformTranslationZ:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.translation.z"];
}
- (CGFloat)transformDepth {
return self.transform.m34;
}
- (void)setTransformDepth:(CGFloat)v {
CATransform3D d = self.transform;
d.m34 = v;
self.transform = d;
}
- (UIViewContentMode)contentMode {
return YYCAGravityToUIViewContentMode(self.contentsGravity);
}
- (void)setContentMode:(UIViewContentMode)contentMode {
self.contentsGravity = YYUIViewContentModeToCAGravity(contentMode);
}
- (void)addFadeAnimationWithDuration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
if (duration <= 0) return;
NSString *mediaFunction;
switch (curve) {
case UIViewAnimationCurveEaseInOut: {
mediaFunction = kCAMediaTimingFunctionEaseInEaseOut;
} break;
case UIViewAnimationCurveEaseIn: {
mediaFunction = kCAMediaTimingFunctionEaseIn;
} break;
case UIViewAnimationCurveEaseOut: {
mediaFunction = kCAMediaTimingFunctionEaseOut;
} break;
case UIViewAnimationCurveLinear: {
mediaFunction = kCAMediaTimingFunctionLinear;
} break;
default: {
mediaFunction = kCAMediaTimingFunctionLinear;
} break;
}
CATransition *transition = [CATransition animation];
transition.duration = duration;
transition.timingFunction = [CAMediaTimingFunction functionWithName:mediaFunction];
transition.type = kCATransitionFade;
[self addAnimation:transition forKey:@"yykit.fade"];
}
- (void)removePreviousFadeAnimation {
[self removeAnimationForKey:@"yykit.fade"];
}
@end

View File

@@ -0,0 +1,330 @@
//
// YYCGUtilities.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 15/2/28.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
NS_ASSUME_NONNULL_BEGIN
/// Create an `ARGB` Bitmap context. Returns NULL if an error occurs.
///
/// @discussion The function is same as UIGraphicsBeginImageContextWithOptions(),
/// but it doesn't push the context to UIGraphic, so you can retain the context for reuse.
CGContextRef _Nullable YYCGContextCreateARGBBitmapContext(CGSize size, BOOL opaque, CGFloat scale);
/// Create a `DeviceGray` Bitmap context. Returns NULL if an error occurs.
CGContextRef _Nullable YYCGContextCreateGrayBitmapContext(CGSize size, CGFloat scale);
/// Get main screen's scale.
CGFloat YYScreenScale();
/// Get main screen's size. Height is always larger than width.
CGSize YYScreenSize();
/// Convert degrees to radians.
static inline CGFloat DegreesToRadians(CGFloat degrees) {
return degrees * M_PI / 180;
}
/// Convert radians to degrees.
static inline CGFloat RadiansToDegrees(CGFloat radians) {
return radians * 180 / M_PI;
}
/// Get the transform rotation.
/// @return the rotation in radians [-PI,PI] ([-180°,180°])
static inline CGFloat CGAffineTransformGetRotation(CGAffineTransform transform) {
return atan2(transform.b, transform.a);
}
/// Get the transform's scale.x
static inline CGFloat CGAffineTransformGetScaleX(CGAffineTransform transform) {
return sqrt(transform.a * transform.a + transform.c * transform.c);
}
/// Get the transform's scale.y
static inline CGFloat CGAffineTransformGetScaleY(CGAffineTransform transform) {
return sqrt(transform.b * transform.b + transform.d * transform.d);
}
/// Get the transform's translate.x
static inline CGFloat CGAffineTransformGetTranslateX(CGAffineTransform transform) {
return transform.tx;
}
/// Get the transform's translate.y
static inline CGFloat CGAffineTransformGetTranslateY(CGAffineTransform transform) {
return transform.ty;
}
/**
If you have 3 pair of points transformed by a same CGAffineTransform:
p1 (transform->) q1
p2 (transform->) q2
p3 (transform->) q3
This method returns the original transform matrix from these 3 pair of points.
@see http://stackoverflow.com/questions/13291796/calculate-values-for-a-cgaffinetransform-from-three-points-in-each-of-two-uiview
*/
CGAffineTransform YYCGAffineTransformGetFromPoints(CGPoint before[3], CGPoint after[3]);
/// Get the transform which can converts a point from the coordinate system of a given view to another.
CGAffineTransform YYCGAffineTransformGetFromViews(UIView *from, UIView *to);
/// Create a skew transform.
static inline CGAffineTransform CGAffineTransformMakeSkew(CGFloat x, CGFloat y){
CGAffineTransform transform = CGAffineTransformIdentity;
transform.c = -x;
transform.b = y;
return transform;
}
/// Negates/inverts a UIEdgeInsets.
static inline UIEdgeInsets UIEdgeInsetsInvert(UIEdgeInsets insets) {
return UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right);
}
/// Convert CALayer's gravity string to UIViewContentMode.
UIViewContentMode YYCAGravityToUIViewContentMode(NSString *gravity);
/// Convert UIViewContentMode to CALayer's gravity string.
NSString *YYUIViewContentModeToCAGravity(UIViewContentMode contentMode);
/**
Returns a rectangle to fit the @param rect with specified content mode.
@param rect The constrant rect
@param size The content size
@param mode The content mode
@return A rectangle for the given content mode.
@discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill.
*/
CGRect YYCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode);
/// Returns the center for the rectangle.
static inline CGPoint CGRectGetCenter(CGRect rect) {
return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
}
/// Returns the area of the rectangle.
static inline CGFloat CGRectGetArea(CGRect rect) {
if (CGRectIsNull(rect)) return 0;
rect = CGRectStandardize(rect);
return rect.size.width * rect.size.height;
}
/// Returns the distance between two points.
static inline CGFloat CGPointGetDistanceToPoint(CGPoint p1, CGPoint p2) {
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
/// Returns the minmium distance between a point to a rectangle.
static inline CGFloat CGPointGetDistanceToRect(CGPoint p, CGRect r) {
r = CGRectStandardize(r);
if (CGRectContainsPoint(r, p)) return 0;
CGFloat distV, distH;
if (CGRectGetMinY(r) <= p.y && p.y <= CGRectGetMaxY(r)) {
distV = 0;
} else {
distV = p.y < CGRectGetMinY(r) ? CGRectGetMinY(r) - p.y : p.y - CGRectGetMaxY(r);
}
if (CGRectGetMinX(r) <= p.x && p.x <= CGRectGetMaxX(r)) {
distH = 0;
} else {
distH = p.x < CGRectGetMinX(r) ? CGRectGetMinX(r) - p.x : p.x - CGRectGetMaxX(r);
}
return MAX(distV, distH);
}
/// Convert point to pixel.
static inline CGFloat CGFloatToPixel(CGFloat value) {
return value * YYScreenScale();
}
/// Convert pixel to point.
static inline CGFloat CGFloatFromPixel(CGFloat value) {
return value / YYScreenScale();
}
/// floor point value for pixel-aligned
static inline CGFloat CGFloatPixelFloor(CGFloat value) {
CGFloat scale = YYScreenScale();
return floor(value * scale) / scale;
}
/// round point value for pixel-aligned
static inline CGFloat CGFloatPixelRound(CGFloat value) {
CGFloat scale = YYScreenScale();
return round(value * scale) / scale;
}
/// ceil point value for pixel-aligned
static inline CGFloat CGFloatPixelCeil(CGFloat value) {
CGFloat scale = YYScreenScale();
return ceil(value * scale) / scale;
}
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
static inline CGFloat CGFloatPixelHalf(CGFloat value) {
CGFloat scale = YYScreenScale();
return (floor(value * scale) + 0.5) / scale;
}
/// floor point value for pixel-aligned
static inline CGPoint CGPointPixelFloor(CGPoint point) {
CGFloat scale = YYScreenScale();
return CGPointMake(floor(point.x * scale) / scale,
floor(point.y * scale) / scale);
}
/// round point value for pixel-aligned
static inline CGPoint CGPointPixelRound(CGPoint point) {
CGFloat scale = YYScreenScale();
return CGPointMake(round(point.x * scale) / scale,
round(point.y * scale) / scale);
}
/// ceil point value for pixel-aligned
static inline CGPoint CGPointPixelCeil(CGPoint point) {
CGFloat scale = YYScreenScale();
return CGPointMake(ceil(point.x * scale) / scale,
ceil(point.y * scale) / scale);
}
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
static inline CGPoint CGPointPixelHalf(CGPoint point) {
CGFloat scale = YYScreenScale();
return CGPointMake((floor(point.x * scale) + 0.5) / scale,
(floor(point.y * scale) + 0.5) / scale);
}
/// floor point value for pixel-aligned
static inline CGSize CGSizePixelFloor(CGSize size) {
CGFloat scale = YYScreenScale();
return CGSizeMake(floor(size.width * scale) / scale,
floor(size.height * scale) / scale);
}
/// round point value for pixel-aligned
static inline CGSize CGSizePixelRound(CGSize size) {
CGFloat scale = YYScreenScale();
return CGSizeMake(round(size.width * scale) / scale,
round(size.height * scale) / scale);
}
/// ceil point value for pixel-aligned
static inline CGSize CGSizePixelCeil(CGSize size) {
CGFloat scale = YYScreenScale();
return CGSizeMake(ceil(size.width * scale) / scale,
ceil(size.height * scale) / scale);
}
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
static inline CGSize CGSizePixelHalf(CGSize size) {
CGFloat scale = YYScreenScale();
return CGSizeMake((floor(size.width * scale) + 0.5) / scale,
(floor(size.height * scale) + 0.5) / scale);
}
/// floor point value for pixel-aligned
static inline CGRect CGRectPixelFloor(CGRect rect) {
CGPoint origin = CGPointPixelCeil(rect.origin);
CGPoint corner = CGPointPixelFloor(CGPointMake(rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height));
CGRect ret = CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
if (ret.size.width < 0) ret.size.width = 0;
if (ret.size.height < 0) ret.size.height = 0;
return ret;
}
/// round point value for pixel-aligned
static inline CGRect CGRectPixelRound(CGRect rect) {
CGPoint origin = CGPointPixelRound(rect.origin);
CGPoint corner = CGPointPixelRound(CGPointMake(rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height));
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
}
/// ceil point value for pixel-aligned
static inline CGRect CGRectPixelCeil(CGRect rect) {
CGPoint origin = CGPointPixelFloor(rect.origin);
CGPoint corner = CGPointPixelCeil(CGPointMake(rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height));
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
}
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
static inline CGRect CGRectPixelHalf(CGRect rect) {
CGPoint origin = CGPointPixelHalf(rect.origin);
CGPoint corner = CGPointPixelHalf(CGPointMake(rect.origin.x + rect.size.width,
rect.origin.y + rect.size.height));
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
}
/// floor UIEdgeInset for pixel-aligned
static inline UIEdgeInsets UIEdgeInsetPixelFloor(UIEdgeInsets insets) {
insets.top = CGFloatPixelFloor(insets.top);
insets.left = CGFloatPixelFloor(insets.left);
insets.bottom = CGFloatPixelFloor(insets.bottom);
insets.right = CGFloatPixelFloor(insets.right);
return insets;
}
/// ceil UIEdgeInset for pixel-aligned
static inline UIEdgeInsets UIEdgeInsetPixelCeil(UIEdgeInsets insets) {
insets.top = CGFloatPixelCeil(insets.top);
insets.left = CGFloatPixelCeil(insets.left);
insets.bottom = CGFloatPixelCeil(insets.bottom);
insets.right = CGFloatPixelCeil(insets.right);
return insets;
}
// main screen's scale
#ifndef kScreenScale
#define kScreenScale YYScreenScale()
#endif
// main screen's size (portrait)
#ifndef kScreenSize
#define kScreenSize YYScreenSize()
#endif
// main screen's width (portrait)
#ifndef kScreenWidth
#define kScreenWidth YYScreenSize().width
#endif
// main screen's height (portrait)
#ifndef kScreenHeight
#define kScreenHeight YYScreenSize().height
#endif
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,271 @@
//
// YYCGUtilities.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 15/2/28.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYCGUtilities.h"
#import <Accelerate/Accelerate.h>
#import "UIView+YYAdd.h"
CGContextRef YYCGContextCreateARGBBitmapContext(CGSize size, BOOL opaque, CGFloat scale) {
size_t width = ceil(size.width * scale);
size_t height = ceil(size.height * scale);
if (width < 1 || height < 1) return NULL;
//pre-multiplied ARGB, 8-bits per component
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGImageAlphaInfo alphaInfo = (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo);
CGColorSpaceRelease(space);
if (context) {
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, scale, -scale);
}
return context;
}
CGContextRef YYCGContextCreateGrayBitmapContext(CGSize size, CGFloat scale) {
size_t width = ceil(size.width * scale);
size_t height = ceil(size.height * scale);
if (width < 1 || height < 1) return NULL;
//DeviceGray, 8-bits per component
CGColorSpaceRef space = CGColorSpaceCreateDeviceGray();
CGImageAlphaInfo alphaInfo = kCGImageAlphaNone;
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo);
CGColorSpaceRelease(space);
if (context) {
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, scale, -scale);
}
return context;
}
CGFloat YYScreenScale() {
static CGFloat scale;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
scale = [UIScreen mainScreen].scale;
});
return scale;
}
CGSize YYScreenSize() {
static CGSize size;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
size = [UIScreen mainScreen].bounds.size;
if (size.height < size.width) {
CGFloat tmp = size.height;
size.height = size.width;
size.width = tmp;
}
});
return size;
}
// return 0 when succeed
static int matrix_invert(__CLPK_integer N, double *matrix) {
__CLPK_integer error = 0;
__CLPK_integer pivot_tmp[6 * 6];
__CLPK_integer *pivot = pivot_tmp;
double workspace_tmp[6 * 6];
double *workspace = workspace_tmp;
bool need_free = false;
if (N > 6) {
need_free = true;
pivot = malloc(N * N * sizeof(__CLPK_integer));
if (!pivot) return -1;
workspace = malloc(N * sizeof(double));
if (!workspace) {
free(pivot);
return -1;
}
}
dgetrf_(&N, &N, matrix, &N, pivot, &error);
if (error == 0) {
dgetri_(&N, matrix, &N, pivot, workspace, &N, &error);
}
if (need_free) {
free(pivot);
free(workspace);
}
return error;
}
CGAffineTransform YYCGAffineTransformGetFromPoints(CGPoint before[3], CGPoint after[3]) {
if (before == NULL || after == NULL) return CGAffineTransformIdentity;
CGPoint p1, p2, p3, q1, q2, q3;
p1 = before[0]; p2 = before[1]; p3 = before[2];
q1 = after[0]; q2 = after[1]; q3 = after[2];
double A[36];
A[ 0] = p1.x; A[ 1] = p1.y; A[ 2] = 0; A[ 3] = 0; A[ 4] = 1; A[ 5] = 0;
A[ 6] = 0; A[ 7] = 0; A[ 8] = p1.x; A[ 9] = p1.y; A[10] = 0; A[11] = 1;
A[12] = p2.x; A[13] = p2.y; A[14] = 0; A[15] = 0; A[16] = 1; A[17] = 0;
A[18] = 0; A[19] = 0; A[20] = p2.x; A[21] = p2.y; A[22] = 0; A[23] = 1;
A[24] = p3.x; A[25] = p3.y; A[26] = 0; A[27] = 0; A[28] = 1; A[29] = 0;
A[30] = 0; A[31] = 0; A[32] = p3.x; A[33] = p3.y; A[34] = 0; A[35] = 1;
int error = matrix_invert(6, A);
if (error) return CGAffineTransformIdentity;
double B[6];
B[0] = q1.x; B[1] = q1.y; B[2] = q2.x; B[3] = q2.y; B[4] = q3.x; B[5] = q3.y;
double M[6];
M[0] = A[ 0] * B[0] + A[ 1] * B[1] + A[ 2] * B[2] + A[ 3] * B[3] + A[ 4] * B[4] + A[ 5] * B[5];
M[1] = A[ 6] * B[0] + A[ 7] * B[1] + A[ 8] * B[2] + A[ 9] * B[3] + A[10] * B[4] + A[11] * B[5];
M[2] = A[12] * B[0] + A[13] * B[1] + A[14] * B[2] + A[15] * B[3] + A[16] * B[4] + A[17] * B[5];
M[3] = A[18] * B[0] + A[19] * B[1] + A[20] * B[2] + A[21] * B[3] + A[22] * B[4] + A[23] * B[5];
M[4] = A[24] * B[0] + A[25] * B[1] + A[26] * B[2] + A[27] * B[3] + A[28] * B[4] + A[29] * B[5];
M[5] = A[30] * B[0] + A[31] * B[1] + A[32] * B[2] + A[33] * B[3] + A[34] * B[4] + A[35] * B[5];
CGAffineTransform transform = CGAffineTransformMake(M[0], M[2], M[1], M[3], M[4], M[5]);
return transform;
}
CGAffineTransform YYCGAffineTransformGetFromViews(UIView *from, UIView *to) {
if (!from || !to) return CGAffineTransformIdentity;
CGPoint before[3], after[3];
before[0] = CGPointMake(0, 0);
before[1] = CGPointMake(0, 1);
before[2] = CGPointMake(1, 0);
after[0] = [from convertPoint:before[0] toViewOrWindow:to];
after[1] = [from convertPoint:before[1] toViewOrWindow:to];
after[2] = [from convertPoint:before[2] toViewOrWindow:to];
return YYCGAffineTransformGetFromPoints(before, after);
}
UIViewContentMode YYCAGravityToUIViewContentMode(NSString *gravity) {
static NSDictionary *dic;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dic = @{ kCAGravityCenter:@(UIViewContentModeCenter),
kCAGravityTop:@(UIViewContentModeTop),
kCAGravityBottom:@(UIViewContentModeBottom),
kCAGravityLeft:@(UIViewContentModeLeft),
kCAGravityRight:@(UIViewContentModeRight),
kCAGravityTopLeft:@(UIViewContentModeTopLeft),
kCAGravityTopRight:@(UIViewContentModeTopRight),
kCAGravityBottomLeft:@(UIViewContentModeBottomLeft),
kCAGravityBottomRight:@(UIViewContentModeBottomRight),
kCAGravityResize:@(UIViewContentModeScaleToFill),
kCAGravityResizeAspect:@(UIViewContentModeScaleAspectFit),
kCAGravityResizeAspectFill:@(UIViewContentModeScaleAspectFill) };
});
if (!gravity) return UIViewContentModeScaleToFill;
return (UIViewContentMode)((NSNumber *)dic[gravity]).integerValue;
}
NSString *YYUIViewContentModeToCAGravity(UIViewContentMode contentMode) {
switch (contentMode) {
case UIViewContentModeScaleToFill: return kCAGravityResize;
case UIViewContentModeScaleAspectFit: return kCAGravityResizeAspect;
case UIViewContentModeScaleAspectFill: return kCAGravityResizeAspectFill;
case UIViewContentModeRedraw: return kCAGravityResize;
case UIViewContentModeCenter: return kCAGravityCenter;
case UIViewContentModeTop: return kCAGravityTop;
case UIViewContentModeBottom: return kCAGravityBottom;
case UIViewContentModeLeft: return kCAGravityLeft;
case UIViewContentModeRight: return kCAGravityRight;
case UIViewContentModeTopLeft: return kCAGravityTopLeft;
case UIViewContentModeTopRight: return kCAGravityTopRight;
case UIViewContentModeBottomLeft: return kCAGravityBottomLeft;
case UIViewContentModeBottomRight: return kCAGravityBottomRight;
default: return kCAGravityResize;
}
}
CGRect YYCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) {
rect = CGRectStandardize(rect);
size.width = size.width < 0 ? -size.width : size.width;
size.height = size.height < 0 ? -size.height : size.height;
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
switch (mode) {
case UIViewContentModeScaleAspectFit:
case UIViewContentModeScaleAspectFill: {
if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
size.width < 0.01 || size.height < 0.01) {
rect.origin = center;
rect.size = CGSizeZero;
} else {
CGFloat scale;
if (mode == UIViewContentModeScaleAspectFit) {
if (size.width / size.height < rect.size.width / rect.size.height) {
scale = rect.size.height / size.height;
} else {
scale = rect.size.width / size.width;
}
} else {
if (size.width / size.height < rect.size.width / rect.size.height) {
scale = rect.size.width / size.width;
} else {
scale = rect.size.height / size.height;
}
}
size.width *= scale;
size.height *= scale;
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
}
} break;
case UIViewContentModeCenter: {
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
} break;
case UIViewContentModeTop: {
rect.origin.x = center.x - size.width * 0.5;
rect.size = size;
} break;
case UIViewContentModeBottom: {
rect.origin.x = center.x - size.width * 0.5;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeLeft: {
rect.origin.y = center.y - size.height * 0.5;
rect.size = size;
} break;
case UIViewContentModeRight: {
rect.origin.y = center.y - size.height * 0.5;
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeTopLeft: {
rect.size = size;
} break;
case UIViewContentModeTopRight: {
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeBottomLeft: {
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeBottomRight: {
rect.origin.x += rect.size.width - size.width;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeScaleToFill:
case UIViewContentModeRedraw:
default: {
rect = rect;
}
}
return rect;
}

View File

@@ -0,0 +1,85 @@
//
// UIApplication+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIApplication`.
*/
@interface UIApplication (YYAdd)
/// "Documents" folder in this app's sandbox.
@property (nonatomic, readonly) NSURL *documentsURL;
@property (nonatomic, readonly) NSString *documentsPath;
/// "Caches" folder in this app's sandbox.
@property (nonatomic, readonly) NSURL *cachesURL;
@property (nonatomic, readonly) NSString *cachesPath;
/// "Library" folder in this app's sandbox.
@property (nonatomic, readonly) NSURL *libraryURL;
@property (nonatomic, readonly) NSString *libraryPath;
/// Application's Bundle Name (show in SpringBoard).
@property (nullable, nonatomic, readonly) NSString *appBundleName;
/// Application's Bundle ID. e.g. "com.ibireme.MyApp"
@property (nullable, nonatomic, readonly) NSString *appBundleID;
/// Application's Version. e.g. "1.2.0"
@property (nullable, nonatomic, readonly) NSString *appVersion;
/// Application's Build number. e.g. "123"
@property (nullable, nonatomic, readonly) NSString *appBuildVersion;
/// Whether this app is priated (not install from appstore).
@property (nonatomic, readonly) BOOL isPirated;
/// Whether this app is being debugged (debugger attached).
@property (nonatomic, readonly) BOOL isBeingDebugged;
/// Current thread real memory used in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryUsage;
/// Current thread CPU usage, 1.0 means 100%. (-1 when error occurs)
@property (nonatomic, readonly) float cpuUsage;
/**
Increments the number of active network requests.
If this number was zero before incrementing, this will start animating the
status bar network activity indicator.
This method is thread safe.
*/
- (void)incrementNetworkActivityCount;
/**
Decrements the number of active network requests.
If this number becomes zero after decrementing, this will stop animating the
status bar network activity indicator.
This method is thread safe.
*/
- (void)decrementNetworkActivityCount;
/// Returns YES in App Extension.
+ (BOOL)isAppExtension;
/// Same as sharedApplication, but returns nil in App Extension.
+ (nullable UIApplication *)sharedExtensionApplication;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,239 @@
//
// UIApplication+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIApplication+YYAdd.h"
#import "NSArray+YYAdd.h"
#import "NSObject+YYAdd.h"
#import "YYCategoriesMacro.h"
#import "UIDevice+YYAdd.h"
#import <sys/sysctl.h>
#import <mach/mach.h>
#import <objc/runtime.h>
YYSYNTH_DUMMY_CLASS(UIApplication_YYAdd)
#define kNetworkIndicatorDelay (1/30.0)
@interface _YYUIApplicationNetworkIndicatorInfo : NSObject
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation _YYUIApplicationNetworkIndicatorInfo
@end
@implementation UIApplication (YYAdd)
- (NSURL *)documentsURL {
return [[[NSFileManager defaultManager]
URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
}
- (NSString *)documentsPath {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
}
- (NSURL *)cachesURL {
return [[[NSFileManager defaultManager]
URLsForDirectory:NSCachesDirectory
inDomains:NSUserDomainMask] lastObject];
}
- (NSString *)cachesPath {
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
}
- (NSURL *)libraryURL {
return [[[NSFileManager defaultManager]
URLsForDirectory:NSLibraryDirectory
inDomains:NSUserDomainMask] lastObject];
}
- (NSString *)libraryPath {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
}
- (BOOL)isPirated {
if ([[UIDevice currentDevice] isSimulator]) return YES; // Simulator is not from appstore
if (getgid() <= 10) return YES; // process ID shouldn't be root
if ([[[NSBundle mainBundle] infoDictionary] objectForKey:@"SignerIdentity"]) {
return YES;
}
if (![self _yy_fileExistInMainBundle:@"_CodeSignature"]) {
return YES;
}
if (![self _yy_fileExistInMainBundle:@"SC_Info"]) {
return YES;
}
//if someone really want to crack your app, this method is useless..
//you may change this method's name, encrypt the code and do more check..
return NO;
}
- (BOOL)_yy_fileExistInMainBundle:(NSString *)name {
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *path = [NSString stringWithFormat:@"%@/%@", bundlePath, name];
return [[NSFileManager defaultManager] fileExistsAtPath:path];
}
- (NSString *)appBundleName {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
}
- (NSString *)appBundleID {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
}
- (NSString *)appVersion {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}
- (NSString *)appBuildVersion {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
}
- (BOOL)isBeingDebugged {
size_t size = sizeof(struct kinfo_proc);
struct kinfo_proc info;
int ret = 0, name[4];
memset(&info, 0, sizeof(struct kinfo_proc));
name[0] = CTL_KERN;
name[1] = KERN_PROC;
name[2] = KERN_PROC_PID; name[3] = getpid();
if (ret == (sysctl(name, 4, &info, &size, NULL, 0))) {
return ret != 0;
}
return (info.kp_proc.p_flag & P_TRACED) ? YES : NO;
}
- (int64_t)memoryUsage {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kern = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if (kern != KERN_SUCCESS) return -1;
return info.resident_size;
}
- (float)cpuUsage {
kern_return_t kr;
task_info_data_t tinfo;
mach_msg_type_number_t task_info_count;
task_info_count = TASK_INFO_MAX;
kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
if (kr != KERN_SUCCESS) {
return -1;
}
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
thread_info_data_t thinfo;
mach_msg_type_number_t thread_info_count;
thread_basic_info_t basic_info_th;
kr = task_threads(mach_task_self(), &thread_list, &thread_count);
if (kr != KERN_SUCCESS) {
return -1;
}
long tot_sec = 0;
long tot_usec = 0;
float tot_cpu = 0;
int j;
for (j = 0; j < thread_count; j++) {
thread_info_count = THREAD_INFO_MAX;
kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
(thread_info_t)thinfo, &thread_info_count);
if (kr != KERN_SUCCESS) {
return -1;
}
basic_info_th = (thread_basic_info_t)thinfo;
if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds;
tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE;
}
}
kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
assert(kr == KERN_SUCCESS);
return tot_cpu;
}
YYSYNTH_DYNAMIC_PROPERTY_OBJECT(networkActivityInfo, setNetworkActivityInfo, RETAIN_NONATOMIC, _YYUIApplicationNetworkIndicatorInfo *);
- (void)_delaySetActivity:(NSTimer *)timer {
NSNumber *visiable = timer.userInfo;
if (self.networkActivityIndicatorVisible != visiable.boolValue) {
[self setNetworkActivityIndicatorVisible:visiable.boolValue];
}
[timer invalidate];
}
- (void)_changeNetworkActivityCount:(NSInteger)delta {
@synchronized(self){
dispatch_async_on_main_queue(^{
_YYUIApplicationNetworkIndicatorInfo *info = [self networkActivityInfo];
if (!info) {
info = [_YYUIApplicationNetworkIndicatorInfo new];
[self setNetworkActivityInfo:info];
}
NSInteger count = info.count;
count += delta;
info.count = count;
[info.timer invalidate];
info.timer = [NSTimer timerWithTimeInterval:kNetworkIndicatorDelay target:self selector:@selector(_delaySetActivity:) userInfo:@(info.count > 0) repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:info.timer forMode:NSRunLoopCommonModes];
});
}
}
- (void)incrementNetworkActivityCount {
[self _changeNetworkActivityCount:1];
}
- (void)decrementNetworkActivityCount {
[self _changeNetworkActivityCount:-1];
}
+ (BOOL)isAppExtension {
static BOOL isAppExtension = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"UIApplication");
if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES;
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES;
});
return isAppExtension;
}
+ (UIApplication *)sharedExtensionApplication {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
return [self isAppExtension] ? nil : [UIApplication performSelector:@selector(sharedApplication)];
#pragma clang diagnostic pop
}
@end

View File

@@ -0,0 +1,32 @@
//
// UIBarButtonItem+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/10/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIBarButtonItem`.
*/
@interface UIBarButtonItem (YYAdd)
/**
The block that invoked when the item is selected. The objects captured by block
will retained by the ButtonItem.
@discussion This param is conflict with `target` and `action` property.
Set this will set `target` and `action` property to some internal objects.
*/
@property (nullable, nonatomic, copy) void (^actionBlock)(id);
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,62 @@
//
// UIBarButtonItem+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/10/15.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIBarButtonItem+YYAdd.h"
#import "YYCategoriesMacro.h"
#import <objc/runtime.h>
YYSYNTH_DUMMY_CLASS(UIBarButtonItem_YYAdd)
static const int block_key;
@interface _YYUIBarButtonItemBlockTarget : NSObject
@property (nonatomic, copy) void (^block)(id sender);
- (id)initWithBlock:(void (^)(id sender))block;
- (void)invoke:(id)sender;
@end
@implementation _YYUIBarButtonItemBlockTarget
- (id)initWithBlock:(void (^)(id sender))block{
self = [super init];
if (self) {
_block = [block copy];
}
return self;
}
- (void)invoke:(id)sender {
if (self.block) self.block(sender);
}
@end
@implementation UIBarButtonItem (YYAdd)
- (void)setActionBlock:(void (^)(id sender))block {
_YYUIBarButtonItemBlockTarget *target = [[_YYUIBarButtonItemBlockTarget alloc] initWithBlock:block];
objc_setAssociatedObject(self, &block_key, target, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self setTarget:target];
[self setAction:@selector(invoke:)];
}
- (void (^)(id)) actionBlock {
_YYUIBarButtonItemBlockTarget *target = objc_getAssociatedObject(self, &block_key);
return target.block;
}
@end

View File

@@ -0,0 +1,37 @@
//
// UIBezierPath+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/30.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIBezierPath`.
*/
@interface UIBezierPath (YYAdd)
/**
Creates and returns a new UIBezierPath object initialized with the text glyphs
generated from the specified font.
@discussion It doesnot support apple emoji. If you want get emoji image, try
[UIImage imageWithEmoji:size:] in `UIImage(YYAdd)`.
@param text The text to generate glyph path.
@param font The font to generate glyph path.
@return A new path object with the text and font, or nil if an error occurs.
*/
+ (nullable UIBezierPath *)bezierPathWithText:(NSString *)text font:(UIFont *)font;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,64 @@
//
// UIBezierPath+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/30.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIBezierPath+YYAdd.h"
#import "UIFont+YYAdd.h"
#import <CoreText/CoreText.h>
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(UIBezierPath_YYAdd)
@implementation UIBezierPath (YYAdd)
+ (UIBezierPath *)bezierPathWithText:(NSString *)text font:(UIFont *)font {
CTFontRef ctFont = font.CTFontRef;
if (!ctFont) return nil;
NSDictionary *attrs = @{ (__bridge id)kCTFontAttributeName:(__bridge id)ctFont };
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:text attributes:attrs];
CFRelease(ctFont);
CTLineRef line = CTLineCreateWithAttributedString((__bridge CFTypeRef)attrString);
if (!line) return nil;
CGMutablePathRef cgPath = CGPathCreateMutable();
CFArrayRef runs = CTLineGetGlyphRuns(line);
for (CFIndex iRun = 0, iRunMax = CFArrayGetCount(runs); iRun < iRunMax; iRun++) {
CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, iRun);
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
for (CFIndex iGlyph = 0, iGlyphMax = CTRunGetGlyphCount(run); iGlyph < iGlyphMax; iGlyph++) {
CFRange glyphRange = CFRangeMake(iGlyph, 1);
CGGlyph glyph;
CGPoint position;
CTRunGetGlyphs(run, glyphRange, &glyph);
CTRunGetPositions(run, glyphRange, &position);
CGPathRef glyphPath = CTFontCreatePathForGlyph(runFont, glyph, NULL);
if (glyphPath) {
CGAffineTransform transform = CGAffineTransformMakeTranslation(position.x, position.y);
CGPathAddPath(cgPath, &transform, glyphPath);
CGPathRelease(glyphPath);
}
}
}
UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:cgPath];
CGRect boundingBox = CGPathGetPathBoundingBox(cgPath);
CFRelease(cgPath);
CFRelease(line);
[path applyTransform:CGAffineTransformMakeScale(1.0, -1.0)];
[path applyTransform:CGAffineTransformMakeTranslation(0.0, boundingBox.size.height)];
return path;
}
@end

View File

@@ -0,0 +1,358 @@
//
// UIColor+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
extern void YY_RGB2HSL(CGFloat r, CGFloat g, CGFloat b,
CGFloat *h, CGFloat *s, CGFloat *l);
extern void YY_HSL2RGB(CGFloat h, CGFloat s, CGFloat l,
CGFloat *r, CGFloat *g, CGFloat *b);
extern void YY_RGB2HSB(CGFloat r, CGFloat g, CGFloat b,
CGFloat *h, CGFloat *s, CGFloat *v);
extern void YY_HSB2RGB(CGFloat h, CGFloat s, CGFloat v,
CGFloat *r, CGFloat *g, CGFloat *b);
extern void YY_RGB2CMYK(CGFloat r, CGFloat g, CGFloat b,
CGFloat *c, CGFloat *m, CGFloat *y, CGFloat *k);
extern void YY_CMYK2RGB(CGFloat c, CGFloat m, CGFloat y, CGFloat k,
CGFloat *r, CGFloat *g, CGFloat *b);
extern void YY_HSB2HSL(CGFloat h, CGFloat s, CGFloat b,
CGFloat *hh, CGFloat *ss, CGFloat *ll);
extern void YY_HSL2HSB(CGFloat h, CGFloat s, CGFloat l,
CGFloat *hh, CGFloat *ss, CGFloat *bb);
/*
Create UIColor with a hex string.
Example: UIColorHex(0xF0F), UIColorHex(66ccff), UIColorHex(#66CCFF88)
Valid format: #RGB #RGBA #RRGGBB #RRGGBBAA 0xRGB ...
The `#` or "0x" sign is not required.
*/
#ifndef UIColorHex
#define UIColorHex(_hex_) [UIColor colorWithHexString:((__bridge NSString *)CFSTR(#_hex_))]
#endif
/**
Provide some method for `UIColor` to convert color between
RGB,HSB,HSL,CMYK and Hex.
| Color space | Meaning |
|-------------|----------------------------------------|
| RGB * | Red, Green, Blue |
| HSB(HSV) * | Hue, Saturation, Brightness (Value) |
| HSL | Hue, Saturation, Lightness |
| CMYK | Cyan, Magenta, Yellow, Black |
Apple use RGB & HSB default.
All the value in this category is float number in the range `0.0` to `1.0`.
Values below `0.0` are interpreted as `0.0`,
and values above `1.0` are interpreted as `1.0`.
If you want convert color between more color space (CIEXYZ,Lab,YUV...),
see https://github.com/ibireme/yy_color_convertor
*/
@interface UIColor (YYAdd)
#pragma mark - Create a UIColor Object
///=============================================================================
/// @name Creating a UIColor Object
///=============================================================================
/**
Creates and returns a color object using the specified opacity
and HSL color space component values.
@param hue The hue component of the color object in the HSL color space,
specified as a value from 0.0 to 1.0.
@param saturation The saturation component of the color object in the HSL color space,
specified as a value from 0.0 to 1.0.
@param lightness The lightness component of the color object in the HSL color space,
specified as a value from 0.0 to 1.0.
@param alpha The opacity value of the color object,
specified as a value from 0.0 to 1.0.
@return The color object. The color information represented by this
object is in the device RGB colorspace.
*/
+ (UIColor *)colorWithHue:(CGFloat)hue
saturation:(CGFloat)saturation
lightness:(CGFloat)lightness
alpha:(CGFloat)alpha;
/**
Creates and returns a color object using the specified opacity
and CMYK color space component values.
@param cyan The cyan component of the color object in the CMYK color space,
specified as a value from 0.0 to 1.0.
@param magenta The magenta component of the color object in the CMYK color space,
specified as a value from 0.0 to 1.0.
@param yellow The yellow component of the color object in the CMYK color space,
specified as a value from 0.0 to 1.0.
@param black The black component of the color object in the CMYK color space,
specified as a value from 0.0 to 1.0.
@param alpha The opacity value of the color object,
specified as a value from 0.0 to 1.0.
@return The color object. The color information represented by this
object is in the device RGB colorspace.
*/
+ (UIColor *)colorWithCyan:(CGFloat)cyan
magenta:(CGFloat)magenta
yellow:(CGFloat)yellow
black:(CGFloat)black
alpha:(CGFloat)alpha;
/**
Creates and returns a color object using the hex RGB color values.
@param rgbValue The rgb value such as 0x66ccff.
@return The color object. The color information represented by this
object is in the device RGB colorspace.
*/
+ (UIColor *)colorWithRGB:(uint32_t)rgbValue;
/**
Creates and returns a color object using the hex RGBA color values.
@param rgbaValue The rgb value such as 0x66ccffff.
@return The color object. The color information represented by this
object is in the device RGB colorspace.
*/
+ (UIColor *)colorWithRGBA:(uint32_t)rgbaValue;
/**
Creates and returns a color object using the specified opacity and RGB hex value.
@param rgbValue The rgb value such as 0x66CCFF.
@param alpha The opacity value of the color object,
specified as a value from 0.0 to 1.0.
@return The color object. The color information represented by this
object is in the device RGB colorspace.
*/
+ (UIColor *)colorWithRGB:(uint32_t)rgbValue alpha:(CGFloat)alpha;
/**
Creates and returns a color object from hex string.
@discussion:
Valid format: #RGB #RGBA #RRGGBB #RRGGBBAA 0xRGB ...
The `#` or "0x" sign is not required.
The alpha will be set to 1.0 if there is no alpha component.
It will return nil when an error occurs in parsing.
Example: @"0xF0F", @"66ccff", @"#66CCFF88"
@param hexStr The hex string value for the new color.
@return An UIColor object from string, or nil if an error occurs.
*/
+ (nullable UIColor *)colorWithHexString:(NSString *)hexStr;
/**
Creates and returns a color object by add new color.
@param add the color added
@param blendMode add color blend mode
*/
- (UIColor *)colorByAddColor:(UIColor *)add blendMode:(CGBlendMode)blendMode;
/**
Creates and returns a color object by change components.
@param hueDelta the hue change delta specified as a value
from -1.0 to 1.0. 0 means no change.
@param saturationDelta the saturation change delta specified as a value
from -1.0 to 1.0. 0 means no change.
@param brightnessDelta the brightness change delta specified as a value
from -1.0 to 1.0. 0 means no change.
@param alphaDelta the alpha change delta specified as a value
from -1.0 to 1.0. 0 means no change.
*/
- (UIColor *)colorByChangeHue:(CGFloat)hueDelta
saturation:(CGFloat)saturationDelta
brightness:(CGFloat)brightnessDelta
alpha:(CGFloat)alphaDelta;
#pragma mark - Get color's description
///=============================================================================
/// @name Get color's description
///=============================================================================
/**
Returns the rgb value in hex.
@return hex value of RGB,such as 0x66ccff.
*/
- (uint32_t)rgbValue;
/**
Returns the rgba value in hex.
@return hex value of RGBA,such as 0x66ccffff.
*/
- (uint32_t)rgbaValue;
/**
Returns the color's RGB value as a hex string (lowercase).
Such as @"0066cc".
It will return nil when the color space is not RGB
@return The color's value as a hex string.
*/
- (nullable NSString *)hexString;
/**
Returns the color's RGBA value as a hex string (lowercase).
Such as @"0066ccff".
It will return nil when the color space is not RGBA
@return The color's value as a hex string.
*/
- (nullable NSString *)hexStringWithAlpha;
#pragma mark - Retrieving Color Information
///=============================================================================
/// @name Retrieving Color Information
///=============================================================================
/**
Returns the components that make up the color in the HSL color space.
@param hue On return, the hue component of the color object,
specified as a value between 0.0 and 1.0.
@param saturation On return, the saturation component of the color object,
specified as a value between 0.0 and 1.0.
@param lightness On return, the lightness component of the color object,
specified as a value between 0.0 and 1.0.
@param alpha On return, the alpha component of the color object,
specified as a value between 0.0 and 1.0.
@return YES if the color could be converted, NO otherwise.
*/
- (BOOL)getHue:(CGFloat *)hue
saturation:(CGFloat *)saturation
lightness:(CGFloat *)lightness
alpha:(CGFloat *)alpha;
/**
Returns the components that make up the color in the CMYK color space.
@param cyan On return, the cyan component of the color object,
specified as a value between 0.0 and 1.0.
@param magenta On return, the magenta component of the color object,
specified as a value between 0.0 and 1.0.
@param yellow On return, the yellow component of the color object,
specified as a value between 0.0 and 1.0.
@param black On return, the black component of the color object,
specified as a value between 0.0 and 1.0.
@param alpha On return, the alpha component of the color object,
specified as a value between 0.0 and 1.0.
@return YES if the color could be converted, NO otherwise.
*/
- (BOOL)getCyan:(CGFloat *)cyan
magenta:(CGFloat *)magenta
yellow:(CGFloat *)yellow
black:(CGFloat *)black
alpha:(CGFloat *)alpha;
/**
The color's red component value in RGB color space.
The value of this property is a float in the range `0.0` to `1.0`.
*/
@property (nonatomic, readonly) CGFloat red;
/**
The color's green component value in RGB color space.
The value of this property is a float in the range `0.0` to `1.0`.
*/
@property (nonatomic, readonly) CGFloat green;
/**
The color's blue component value in RGB color space.
The value of this property is a float in the range `0.0` to `1.0`.
*/
@property (nonatomic, readonly) CGFloat blue;
/**
The color's hue component value in HSB color space.
The value of this property is a float in the range `0.0` to `1.0`.
*/
@property (nonatomic, readonly) CGFloat hue;
/**
The color's saturation component value in HSB color space.
The value of this property is a float in the range `0.0` to `1.0`.
*/
@property (nonatomic, readonly) CGFloat saturation;
/**
The color's brightness component value in HSB color space.
The value of this property is a float in the range `0.0` to `1.0`.
*/
@property (nonatomic, readonly) CGFloat brightness;
/**
The color's alpha component value.
The value of this property is a float in the range `0.0` to `1.0`.
*/
@property (nonatomic, readonly) CGFloat alpha;
/**
The color's colorspace model.
*/
@property (nonatomic, readonly) CGColorSpaceModel colorSpaceModel;
/**
Readable colorspace string.
*/
@property (nullable, nonatomic, readonly) NSString *colorSpaceString;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,472 @@
//
// UIColor+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIColor+YYAdd.h"
#import "NSString+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(UIColor_YYAdd)
#define CLAMP_COLOR_VALUE(v) (v) = (v) < 0 ? 0 : (v) > 1 ? 1 : (v)
void YY_RGB2HSL(CGFloat r, CGFloat g, CGFloat b,
CGFloat *h, CGFloat *s, CGFloat *l) {
CLAMP_COLOR_VALUE(r);
CLAMP_COLOR_VALUE(g);
CLAMP_COLOR_VALUE(b);
CGFloat max, min, delta, sum;
max = fmaxf(r, fmaxf(g, b));
min = fminf(r, fminf(g, b));
delta = max - min;
sum = max + min;
*l = sum / 2; // Lightness
if (delta == 0) { // No Saturation, so Hue is undefined (achromatic)
*h = *s = 0;
return;
}
*s = delta / (sum < 1 ? sum : 2 - sum); // Saturation
if (r == max) *h = (g - b) / delta / 6; // color between y & m
else if (g == max) *h = (2 + (b - r) / delta) / 6; // color between c & y
else *h = (4 + (r - g) / delta) / 6; // color between m & y
if (*h < 0) *h += 1;
}
void YY_HSL2RGB(CGFloat h, CGFloat s, CGFloat l,
CGFloat *r, CGFloat *g, CGFloat *b) {
CLAMP_COLOR_VALUE(h);
CLAMP_COLOR_VALUE(s);
CLAMP_COLOR_VALUE(l);
if (s == 0) { // No Saturation, Hue is undefined (achromatic)
*r = *g = *b = l;
return;
}
CGFloat q;
q = (l <= 0.5) ? (l * (1 + s)) : (l + s - (l * s));
if (q <= 0) {
*r = *g = *b = 0.0;
} else {
*r = *g = *b = 0;
int sextant;
CGFloat m, sv, fract, vsf, mid1, mid2;
m = l + l - q;
sv = (q - m) / q;
if (h == 1) h = 0;
h *= 6.0;
sextant = h;
fract = h - sextant;
vsf = q * sv * fract;
mid1 = m + vsf;
mid2 = q - vsf;
switch (sextant) {
case 0: *r = q; *g = mid1; *b = m; break;
case 1: *r = mid2; *g = q; *b = m; break;
case 2: *r = m; *g = q; *b = mid1; break;
case 3: *r = m; *g = mid2; *b = q; break;
case 4: *r = mid1; *g = m; *b = q; break;
case 5: *r = q; *g = m; *b = mid2; break;
}
}
}
void YY_RGB2HSB(CGFloat r, CGFloat g, CGFloat b,
CGFloat *h, CGFloat *s, CGFloat *v) {
CLAMP_COLOR_VALUE(r);
CLAMP_COLOR_VALUE(g);
CLAMP_COLOR_VALUE(b);
CGFloat max, min, delta;
max = fmax(r, fmax(g, b));
min = fmin(r, fmin(g, b));
delta = max - min;
*v = max; // Brightness
if (delta == 0) { // No Saturation, so Hue is undefined (achromatic)
*h = *s = 0;
return;
}
*s = delta / max; // Saturation
if (r == max) *h = (g - b) / delta / 6; // color between y & m
else if (g == max) *h = (2 + (b - r) / delta) / 6; // color between c & y
else *h = (4 + (r - g) / delta) / 6; // color between m & c
if (*h < 0) *h += 1;
}
void YY_HSB2RGB(CGFloat h, CGFloat s, CGFloat v,
CGFloat *r, CGFloat *g, CGFloat *b) {
CLAMP_COLOR_VALUE(h);
CLAMP_COLOR_VALUE(s);
CLAMP_COLOR_VALUE(v);
if (s == 0) {
*r = *g = *b = v; // No Saturation, so Hue is undefined (Achromatic)
} else {
int sextant;
CGFloat f, p, q, t;
if (h == 1) h = 0;
h *= 6;
sextant = floor(h);
f = h - sextant;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch (sextant) {
case 0: *r = v; *g = t; *b = p; break;
case 1: *r = q; *g = v; *b = p; break;
case 2: *r = p; *g = v; *b = t; break;
case 3: *r = p; *g = q; *b = v; break;
case 4: *r = t; *g = p; *b = v; break;
case 5: *r = v; *g = p; *b = q; break;
}
}
}
void YY_RGB2CMYK(CGFloat r, CGFloat g, CGFloat b,
CGFloat *c, CGFloat *m, CGFloat *y, CGFloat *k) {
CLAMP_COLOR_VALUE(r);
CLAMP_COLOR_VALUE(g);
CLAMP_COLOR_VALUE(b);
*c = 1 - r;
*m = 1 - g;
*y = 1 - b;
*k = fmin(*c, fmin(*m, *y));
if (*k == 1) {
*c = *m = *y = 0; // Pure black
} else {
*c = (*c - *k) / (1 - *k);
*m = (*m - *k) / (1 - *k);
*y = (*y - *k) / (1 - *k);
}
}
void YY_CMYK2RGB(CGFloat c, CGFloat m, CGFloat y, CGFloat k,
CGFloat *r, CGFloat *g, CGFloat *b) {
CLAMP_COLOR_VALUE(c);
CLAMP_COLOR_VALUE(m);
CLAMP_COLOR_VALUE(y);
CLAMP_COLOR_VALUE(k);
*r = (1 - c) * (1 - k);
*g = (1 - m) * (1 - k);
*b = (1 - y) * (1 - k);
}
void YY_HSB2HSL(CGFloat h, CGFloat s, CGFloat b,
CGFloat *hh, CGFloat *ss, CGFloat *ll) {
CLAMP_COLOR_VALUE(h);
CLAMP_COLOR_VALUE(s);
CLAMP_COLOR_VALUE(b);
*hh = h;
*ll = (2 - s) * b / 2;
if (*ll <= 0.5) {
*ss = (s) / ((2 - s));
} else {
*ss = (s * b) / (2 - (2 - s) * b);
}
}
void YY_HSL2HSB(CGFloat h, CGFloat s, CGFloat l,
CGFloat *hh, CGFloat *ss, CGFloat *bb) {
CLAMP_COLOR_VALUE(h);
CLAMP_COLOR_VALUE(s);
CLAMP_COLOR_VALUE(l);
*hh = h;
if (l <= 0.5) {
*bb = (s + 1) * l;
*ss = (2 * s) / (s + 1);
} else {
*bb = l + s * (1 - l);
*ss = (2 * s * (1 - l)) / *bb;
}
}
@implementation UIColor (YYAdd)
+ (UIColor *)colorWithHue:(CGFloat)hue
saturation:(CGFloat)saturation
lightness:(CGFloat)lightness
alpha:(CGFloat)alpha {
CGFloat r, g, b;
YY_HSL2RGB(hue, saturation, lightness, &r, &g, &b);
return [UIColor colorWithRed:r green:g blue:b alpha:alpha];
}
+ (UIColor *)colorWithCyan:(CGFloat)cyan
magenta:(CGFloat)magenta
yellow:(CGFloat)yellow
black:(CGFloat)black
alpha:(CGFloat)alpha {
CGFloat r, g, b;
YY_CMYK2RGB(cyan, magenta, yellow, black, &r, &g, &b);
return [UIColor colorWithRed:r green:g blue:b alpha:alpha];
}
+ (UIColor *)colorWithRGB:(uint32_t)rgbValue {
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0f
green:((rgbValue & 0xFF00) >> 8) / 255.0f
blue:(rgbValue & 0xFF) / 255.0f
alpha:1];
}
+ (UIColor *)colorWithRGBA:(uint32_t)rgbaValue {
return [UIColor colorWithRed:((rgbaValue & 0xFF000000) >> 24) / 255.0f
green:((rgbaValue & 0xFF0000) >> 16) / 255.0f
blue:((rgbaValue & 0xFF00) >> 8) / 255.0f
alpha:(rgbaValue & 0xFF) / 255.0f];
}
+ (UIColor *)colorWithRGB:(uint32_t)rgbValue alpha:(CGFloat)alpha {
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0f
green:((rgbValue & 0xFF00) >> 8) / 255.0f
blue:(rgbValue & 0xFF) / 255.0f
alpha:alpha];
}
- (uint32_t)rgbValue {
CGFloat r = 0, g = 0, b = 0, a = 0;
[self getRed:&r green:&g blue:&b alpha:&a];
int8_t red = r * 255;
uint8_t green = g * 255;
uint8_t blue = b * 255;
return (red << 16) + (green << 8) + blue;
}
- (uint32_t)rgbaValue {
CGFloat r = 0, g = 0, b = 0, a = 0;
[self getRed:&r green:&g blue:&b alpha:&a];
int8_t red = r * 255;
uint8_t green = g * 255;
uint8_t blue = b * 255;
uint8_t alpha = a * 255;
return (red << 24) + (green << 16) + (blue << 8) + alpha;
}
static inline NSUInteger hexStrToInt(NSString *str) {
uint32_t result = 0;
sscanf([str UTF8String], "%X", &result);
return result;
}
static BOOL hexStrToRGBA(NSString *str,
CGFloat *r, CGFloat *g, CGFloat *b, CGFloat *a) {
str = [[str stringByTrim] uppercaseString];
if ([str hasPrefix:@"#"]) {
str = [str substringFromIndex:1];
} else if ([str hasPrefix:@"0X"]) {
str = [str substringFromIndex:2];
}
NSUInteger length = [str length];
// RGB RGBA RRGGBB RRGGBBAA
if (length != 3 && length != 4 && length != 6 && length != 8) {
return NO;
}
//RGB,RGBA,RRGGBB,RRGGBBAA
if (length < 5) {
*r = hexStrToInt([str substringWithRange:NSMakeRange(0, 1)]) / 255.0f;
*g = hexStrToInt([str substringWithRange:NSMakeRange(1, 1)]) / 255.0f;
*b = hexStrToInt([str substringWithRange:NSMakeRange(2, 1)]) / 255.0f;
if (length == 4) *a = hexStrToInt([str substringWithRange:NSMakeRange(3, 1)]) / 255.0f;
else *a = 1;
} else {
*r = hexStrToInt([str substringWithRange:NSMakeRange(0, 2)]) / 255.0f;
*g = hexStrToInt([str substringWithRange:NSMakeRange(2, 2)]) / 255.0f;
*b = hexStrToInt([str substringWithRange:NSMakeRange(4, 2)]) / 255.0f;
if (length == 8) *a = hexStrToInt([str substringWithRange:NSMakeRange(6, 2)]) / 255.0f;
else *a = 1;
}
return YES;
}
+ (instancetype)colorWithHexString:(NSString *)hexStr {
CGFloat r, g, b, a;
if (hexStrToRGBA(hexStr, &r, &g, &b, &a)) {
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
return nil;
}
- (NSString *)hexString {
return [self hexStringWithAlpha:NO];
}
- (NSString *)hexStringWithAlpha {
return [self hexStringWithAlpha:YES];
}
- (NSString *)hexStringWithAlpha:(BOOL)withAlpha {
CGColorRef color = self.CGColor;
size_t count = CGColorGetNumberOfComponents(color);
const CGFloat *components = CGColorGetComponents(color);
static NSString *stringFormat = @"%02x%02x%02x";
NSString *hex = nil;
if (count == 2) {
NSUInteger white = (NSUInteger)(components[0] * 255.0f);
hex = [NSString stringWithFormat:stringFormat, white, white, white];
} else if (count == 4) {
hex = [NSString stringWithFormat:stringFormat,
(NSUInteger)(components[0] * 255.0f),
(NSUInteger)(components[1] * 255.0f),
(NSUInteger)(components[2] * 255.0f)];
}
if (hex && withAlpha) {
hex = [hex stringByAppendingFormat:@"%02lx",
(unsigned long)(self.alpha * 255.0 + 0.5)];
}
return hex;
}
- (UIColor *)colorByAddColor:(UIColor *)add blendMode:(CGBlendMode)blendMode {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
uint8_t pixel[4] = { 0 };
CGContextRef context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, colorSpace, bitmapInfo);
CGContextSetFillColorWithColor(context, self.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGContextSetBlendMode(context, blendMode);
CGContextSetFillColorWithColor(context, add.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return [UIColor colorWithRed:pixel[0] / 255.0f green:pixel[1] / 255.0f blue:pixel[2] / 255.0f alpha:pixel[3] / 255.0f];
}
- (UIColor *)colorByChangeHue:(CGFloat)h saturation:(CGFloat)s brightness:(CGFloat)b alpha:(CGFloat)a {
CGFloat hh, ss, bb, aa;
if (![self getHue:&hh saturation:&ss brightness:&bb alpha:&aa]) {
return self;
}
hh += h;
ss += s;
bb += b;
aa += a;
hh -= (int)hh;
hh = hh < 0 ? hh + 1 : hh;
ss = ss < 0 ? 0 : ss > 1 ? 1 : ss;
bb = bb < 0 ? 0 : bb > 1 ? 1 : bb;
aa = aa < 0 ? 0 : aa > 1 ? 1 : aa;
return [UIColor colorWithHue:hh saturation:ss brightness:bb alpha:aa];
}
- (BOOL)getHue:(CGFloat *)hue
saturation:(CGFloat *)saturation
lightness:(CGFloat *)lightness
alpha:(CGFloat *)alpha {
CGFloat r, g, b, a;
if (![self getRed:&r green:&g blue:&b alpha:&a]) {
return NO;
}
YY_RGB2HSL(r, g, b, hue, saturation, lightness);
*alpha = a;
return YES;
}
- (BOOL)getCyan:(CGFloat *)cyan
magenta:(CGFloat *)magenta
yellow:(CGFloat *)yellow
black:(CGFloat *)black
alpha:(CGFloat *)alpha {
CGFloat r, g, b, a;
if (![self getRed:&r green:&g blue:&b alpha:&a]) {
return NO;
}
YY_RGB2CMYK(r, g, b, cyan, magenta, yellow, black);
*alpha = a;
return YES;
}
- (CGFloat)red {
CGFloat r = 0, g, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return r;
}
- (CGFloat)green {
CGFloat r, g = 0, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return g;
}
- (CGFloat)blue {
CGFloat r, g, b = 0, a;
[self getRed:&r green:&g blue:&b alpha:&a];
return b;
}
- (CGFloat)alpha {
return CGColorGetAlpha(self.CGColor);
}
- (CGFloat)hue {
CGFloat h = 0, s, b, a;
[self getHue:&h saturation:&s brightness:&b alpha:&a];
return h;
}
- (CGFloat)saturation {
CGFloat h, s = 0, b, a;
[self getHue:&h saturation:&s brightness:&b alpha:&a];
return s;
}
- (CGFloat)brightness {
CGFloat h, s, b = 0, a;
[self getHue:&h saturation:&s brightness:&b alpha:&a];
return b;
}
- (CGColorSpaceModel)colorSpaceModel {
return CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));
}
- (NSString *)colorSpaceString {
CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor));
switch (model) {
case kCGColorSpaceModelUnknown:
return @"kCGColorSpaceModelUnknown";
case kCGColorSpaceModelMonochrome:
return @"kCGColorSpaceModelMonochrome";
case kCGColorSpaceModelRGB:
return @"kCGColorSpaceModelRGB";
case kCGColorSpaceModelCMYK:
return @"kCGColorSpaceModelCMYK";
case kCGColorSpaceModelLab:
return @"kCGColorSpaceModelLab";
case kCGColorSpaceModelDeviceN:
return @"kCGColorSpaceModelDeviceN";
case kCGColorSpaceModelIndexed:
return @"kCGColorSpaceModelIndexed";
case kCGColorSpaceModelPattern:
return @"kCGColorSpaceModelPattern";
default:
return @"ColorSpaceInvalid";
}
}
@end

View File

@@ -0,0 +1,78 @@
//
// UIControl+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIControl`.
*/
@interface UIControl (YYAdd)
/**
Removes all targets and actions for a particular event (or events)
from an internal dispatch table.
*/
- (void)removeAllTargets;
/**
Adds or replaces a target and action for a particular event (or events)
to an internal dispatch table.
@param target The target object—that is, the object to which the
action message is sent. If this is nil, the responder
chain is searched for an object willing to respond to the
action message.
@param action A selector identifying an action message. It cannot be NULL.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
/**
Adds a block for a particular event (or events) to an internal dispatch table.
It will cause a strong reference to @a block.
@param block The block which is invoked then the action message is
sent (cannot be nil). The block is retained.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)addBlockForControlEvents:(UIControlEvents)controlEvents block:(void (^)(id sender))block;
/**
Adds or replaces a block for a particular event (or events) to an internal
dispatch table. It will cause a strong reference to @a block.
@param block The block which is invoked then the action message is
sent (cannot be nil). The block is retained.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)setBlockForControlEvents:(UIControlEvents)controlEvents block:(void (^)(id sender))block;
/**
Removes all blocks for a particular event (or events) from an internal
dispatch table.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)removeAllBlocksForControlEvents:(UIControlEvents)controlEvents;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,118 @@
//
// UIControl+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIControl+YYAdd.h"
#import "YYCategoriesMacro.h"
#import <objc/runtime.h>
YYSYNTH_DUMMY_CLASS(UIControl_YYAdd)
static const int block_key;
@interface _YYUIControlBlockTarget : NSObject
@property (nonatomic, copy) void (^block)(id sender);
@property (nonatomic, assign) UIControlEvents events;
- (id)initWithBlock:(void (^)(id sender))block events:(UIControlEvents)events;
- (void)invoke:(id)sender;
@end
@implementation _YYUIControlBlockTarget
- (id)initWithBlock:(void (^)(id sender))block events:(UIControlEvents)events {
self = [super init];
if (self) {
_block = [block copy];
_events = events;
}
return self;
}
- (void)invoke:(id)sender {
if (_block) _block(sender);
}
@end
@implementation UIControl (YYAdd)
- (void)removeAllTargets {
[[self allTargets] enumerateObjectsUsingBlock: ^(id object, BOOL *stop) {
[self removeTarget:object action:NULL forControlEvents:UIControlEventAllEvents];
}];
[[self _yy_allUIControlBlockTargets] removeAllObjects];
}
- (void)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents {
if (!target || !action || !controlEvents) return;
NSSet *targets = [self allTargets];
for (id currentTarget in targets) {
NSArray *actions = [self actionsForTarget:currentTarget forControlEvent:controlEvents];
for (NSString *currentAction in actions) {
[self removeTarget:currentTarget action:NSSelectorFromString(currentAction)
forControlEvents:controlEvents];
}
}
[self addTarget:target action:action forControlEvents:controlEvents];
}
- (void)addBlockForControlEvents:(UIControlEvents)controlEvents
block:(void (^)(id sender))block {
if (!controlEvents) return;
_YYUIControlBlockTarget *target = [[_YYUIControlBlockTarget alloc]
initWithBlock:block events:controlEvents];
[self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents];
NSMutableArray *targets = [self _yy_allUIControlBlockTargets];
[targets addObject:target];
}
- (void)setBlockForControlEvents:(UIControlEvents)controlEvents
block:(void (^)(id sender))block {
[self removeAllBlocksForControlEvents:UIControlEventAllEvents];
[self addBlockForControlEvents:controlEvents block:block];
}
- (void)removeAllBlocksForControlEvents:(UIControlEvents)controlEvents {
if (!controlEvents) return;
NSMutableArray *targets = [self _yy_allUIControlBlockTargets];
NSMutableArray *removes = [NSMutableArray array];
for (_YYUIControlBlockTarget *target in targets) {
if (target.events & controlEvents) {
UIControlEvents newEvent = target.events & (~controlEvents);
if (newEvent) {
[self removeTarget:target action:@selector(invoke:) forControlEvents:target.events];
target.events = newEvent;
[self addTarget:target action:@selector(invoke:) forControlEvents:target.events];
} else {
[self removeTarget:target action:@selector(invoke:) forControlEvents:target.events];
[removes addObject:target];
}
}
}
[targets removeObjectsInArray:removes];
}
- (NSMutableArray *)_yy_allUIControlBlockTargets {
NSMutableArray *targets = objc_getAssociatedObject(self, &block_key);
if (!targets) {
targets = [NSMutableArray array];
objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return targets;
}
@end

View File

@@ -0,0 +1,191 @@
//
// UIDevice+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIDevice`.
*/
@interface UIDevice (YYAdd)
#pragma mark - Device Information
///=============================================================================
/// @name Device Information
///=============================================================================
/// Device system version (e.g. 8.1)
+ (double)systemVersion;
/// Whether the device is iPad/iPad mini.
@property (nonatomic, readonly) BOOL isPad;
/// Whether the device is a simulator.
@property (nonatomic, readonly) BOOL isSimulator;
/// Whether the device is jailbroken.
@property (nonatomic, readonly) BOOL isJailbroken;
/// Wherher the device can make phone calls.
@property (nonatomic, readonly) BOOL canMakePhoneCalls NS_EXTENSION_UNAVAILABLE_IOS("");
/// The device's machine model. e.g. "iPhone6,1" "iPad4,6"
/// @see http://theiphonewiki.com/wiki/Models
@property (nullable, nonatomic, readonly) NSString *machineModel;
/// The device's machine model name. e.g. "iPhone 5s" "iPad mini 2"
/// @see http://theiphonewiki.com/wiki/Models
@property (nullable, nonatomic, readonly) NSString *machineModelName;
/// The System's startup time.
@property (nonatomic, readonly) NSDate *systemUptime;
#pragma mark - Network Information
///=============================================================================
/// @name Network Information
///=============================================================================
/// WIFI IP address of this device (can be nil). e.g. @"192.168.1.111"
@property (nullable, nonatomic, readonly) NSString *ipAddressWIFI;
/// Cell IP address of this device (can be nil). e.g. @"10.2.2.222"
@property (nullable, nonatomic, readonly) NSString *ipAddressCell;
/**
Network traffic type:
WWAN: Wireless Wide Area Network.
For example: 3G/4G.
WIFI: Wi-Fi.
AWDL: Apple Wireless Direct Link (peer-to-peer connection).
For exmaple: AirDrop, AirPlay, GameKit.
*/
typedef NS_OPTIONS(NSUInteger, YYNetworkTrafficType) {
YYNetworkTrafficTypeWWANSent = 1 << 0,
YYNetworkTrafficTypeWWANReceived = 1 << 1,
YYNetworkTrafficTypeWIFISent = 1 << 2,
YYNetworkTrafficTypeWIFIReceived = 1 << 3,
YYNetworkTrafficTypeAWDLSent = 1 << 4,
YYNetworkTrafficTypeAWDLReceived = 1 << 5,
YYNetworkTrafficTypeWWAN = YYNetworkTrafficTypeWWANSent | YYNetworkTrafficTypeWWANReceived,
YYNetworkTrafficTypeWIFI = YYNetworkTrafficTypeWIFISent | YYNetworkTrafficTypeWIFIReceived,
YYNetworkTrafficTypeAWDL = YYNetworkTrafficTypeAWDLSent | YYNetworkTrafficTypeAWDLReceived,
YYNetworkTrafficTypeALL = YYNetworkTrafficTypeWWAN |
YYNetworkTrafficTypeWIFI |
YYNetworkTrafficTypeAWDL,
};
/**
Get device network traffic bytes.
@discussion This is a counter since the device's last boot time.
Usage:
uint64_t bytes = [[UIDevice currentDevice] getNetworkTrafficBytes:YYNetworkTrafficTypeALL];
NSTimeInterval time = CACurrentMediaTime();
uint64_t bytesPerSecond = (bytes - _lastBytes) / (time - _lastTime);
_lastBytes = bytes;
_lastTime = time;
@param type traffic types
@return bytes counter.
*/
- (uint64_t)getNetworkTrafficBytes:(YYNetworkTrafficType)types;
#pragma mark - Disk Space
///=============================================================================
/// @name Disk Space
///=============================================================================
/// Total disk space in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t diskSpace;
/// Free disk space in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t diskSpaceFree;
/// Used disk space in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t diskSpaceUsed;
#pragma mark - Memory Information
///=============================================================================
/// @name Memory Information
///=============================================================================
/// Total physical memory in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryTotal;
/// Used (active + inactive + wired) memory in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryUsed;
/// Free memory in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryFree;
/// Acvite memory in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryActive;
/// Inactive memory in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryInactive;
/// Wired memory in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryWired;
/// Purgable memory in byte. (-1 when error occurs)
@property (nonatomic, readonly) int64_t memoryPurgable;
#pragma mark - CPU Information
///=============================================================================
/// @name CPU Information
///=============================================================================
/// Avaliable CPU processor count.
@property (nonatomic, readonly) NSUInteger cpuCount;
/// Current CPU usage, 1.0 means 100%. (-1 when error occurs)
@property (nonatomic, readonly) float cpuUsage;
/// Current CPU usage per processor (array of NSNumber), 1.0 means 100%. (nil when error occurs)
@property (nullable, nonatomic, readonly) NSArray<NSNumber *> *cpuUsagePerProcessor;
@end
NS_ASSUME_NONNULL_END
#ifndef kSystemVersion
#define kSystemVersion [UIDevice systemVersion]
#endif
#ifndef kiOS6Later
#define kiOS6Later (kSystemVersion >= 6)
#endif
#ifndef kiOS7Later
#define kiOS7Later (kSystemVersion >= 7)
#endif
#ifndef kiOS8Later
#define kiOS8Later (kSystemVersion >= 8)
#endif
#ifndef kiOS9Later
#define kiOS9Later (kSystemVersion >= 9)
#endif

View File

@@ -0,0 +1,516 @@
//
// UIDevice+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIDevice+YYAdd.h"
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <mach/mach.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#import "YYCategoriesMacro.h"
#import "NSString+YYAdd.h"
YYSYNTH_DUMMY_CLASS(UIDevice_YYAdd)
@implementation UIDevice (YYAdd)
+ (double)systemVersion {
static double version;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
version = [UIDevice currentDevice].systemVersion.doubleValue;
});
return version;
}
- (BOOL)isPad {
static dispatch_once_t one;
static BOOL pad;
dispatch_once(&one, ^{
pad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
});
return pad;
}
- (BOOL)isSimulator {
static dispatch_once_t one;
static BOOL simu;
dispatch_once(&one, ^{
simu = NSNotFound != [[self model] rangeOfString:@"Simulator"].location;
});
return simu;
}
- (BOOL)isJailbroken {
if ([self isSimulator]) return NO; // Dont't check simulator
// iOS9 URL Scheme query changed ...
// NSURL *cydiaURL = [NSURL URLWithString:@"cydia://package"];
// if ([[UIApplication sharedApplication] canOpenURL:cydiaURL]) return YES;
NSArray *paths = @[@"/Applications/Cydia.app",
@"/private/var/lib/apt/",
@"/private/var/lib/cydia",
@"/private/var/stash"];
for (NSString *path in paths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) return YES;
}
FILE *bash = fopen("/bin/bash", "r");
if (bash != NULL) {
fclose(bash);
return YES;
}
NSString *path = [NSString stringWithFormat:@"/private/%@", [NSString stringWithUUID]];
if ([@"test" writeToFile : path atomically : YES encoding : NSUTF8StringEncoding error : NULL]) {
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
return YES;
}
return NO;
}
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
- (BOOL)canMakePhoneCalls {
__block BOOL can;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
can = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel://"]];
});
return can;
}
#endif
- (NSString *)ipAddressWithIfaName:(NSString *)name {
if (name.length == 0) return nil;
NSString *address = nil;
struct ifaddrs *addrs = NULL;
if (getifaddrs(&addrs) == 0) {
struct ifaddrs *addr = addrs;
while (addr) {
if ([[NSString stringWithUTF8String:addr->ifa_name] isEqualToString:name]) {
sa_family_t family = addr->ifa_addr->sa_family;
switch (family) {
case AF_INET: { // IPv4
char str[INET_ADDRSTRLEN] = {0};
inet_ntop(family, &(((struct sockaddr_in *)addr->ifa_addr)->sin_addr), str, sizeof(str));
if (strlen(str) > 0) {
address = [NSString stringWithUTF8String:str];
}
} break;
case AF_INET6: { // IPv6
char str[INET6_ADDRSTRLEN] = {0};
inet_ntop(family, &(((struct sockaddr_in6 *)addr->ifa_addr)->sin6_addr), str, sizeof(str));
if (strlen(str) > 0) {
address = [NSString stringWithUTF8String:str];
}
}
default: break;
}
if (address) break;
}
addr = addr->ifa_next;
}
}
freeifaddrs(addrs);
return address;
}
- (NSString *)ipAddressWIFI {
return [self ipAddressWithIfaName:@"en0"];
}
- (NSString *)ipAddressCell {
return [self ipAddressWithIfaName:@"pdp_ip0"];
}
typedef struct {
uint64_t en_in;
uint64_t en_out;
uint64_t pdp_ip_in;
uint64_t pdp_ip_out;
uint64_t awdl_in;
uint64_t awdl_out;
} yy_net_interface_counter;
static uint64_t yy_net_counter_add(uint64_t counter, uint64_t bytes) {
if (bytes < (counter % 0xFFFFFFFF)) {
counter += 0xFFFFFFFF - (counter % 0xFFFFFFFF);
counter += bytes;
} else {
counter = bytes;
}
return counter;
}
static uint64_t yy_net_counter_get_by_type(yy_net_interface_counter *counter, YYNetworkTrafficType type) {
uint64_t bytes = 0;
if (type & YYNetworkTrafficTypeWWANSent) bytes += counter->pdp_ip_out;
if (type & YYNetworkTrafficTypeWWANReceived) bytes += counter->pdp_ip_in;
if (type & YYNetworkTrafficTypeWIFISent) bytes += counter->en_out;
if (type & YYNetworkTrafficTypeWIFIReceived) bytes += counter->en_in;
if (type & YYNetworkTrafficTypeAWDLSent) bytes += counter->awdl_out;
if (type & YYNetworkTrafficTypeAWDLReceived) bytes += counter->awdl_in;
return bytes;
}
static yy_net_interface_counter yy_get_net_interface_counter() {
static dispatch_semaphore_t lock;
static NSMutableDictionary *sharedInCounters;
static NSMutableDictionary *sharedOutCounters;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInCounters = [NSMutableDictionary new];
sharedOutCounters = [NSMutableDictionary new];
lock = dispatch_semaphore_create(1);
});
yy_net_interface_counter counter = {0};
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if (getifaddrs(&addrs) == 0) {
cursor = addrs;
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
while (cursor) {
if (cursor->ifa_addr->sa_family == AF_LINK) {
const struct if_data *data = cursor->ifa_data;
NSString *name = cursor->ifa_name ? [NSString stringWithUTF8String:cursor->ifa_name] : nil;
if (name) {
uint64_t counter_in = ((NSNumber *)sharedInCounters[name]).unsignedLongLongValue;
counter_in = yy_net_counter_add(counter_in, data->ifi_ibytes);
sharedInCounters[name] = @(counter_in);
uint64_t counter_out = ((NSNumber *)sharedOutCounters[name]).unsignedLongLongValue;
counter_out = yy_net_counter_add(counter_out, data->ifi_obytes);
sharedOutCounters[name] = @(counter_out);
if ([name hasPrefix:@"en"]) {
counter.en_in += counter_in;
counter.en_out += counter_out;
} else if ([name hasPrefix:@"awdl"]) {
counter.awdl_in += counter_in;
counter.awdl_out += counter_out;
} else if ([name hasPrefix:@"pdp_ip"]) {
counter.pdp_ip_in += counter_in;
counter.pdp_ip_out += counter_out;
}
}
}
cursor = cursor->ifa_next;
}
dispatch_semaphore_signal(lock);
freeifaddrs(addrs);
}
return counter;
}
- (uint64_t)getNetworkTrafficBytes:(YYNetworkTrafficType)types {
yy_net_interface_counter counter = yy_get_net_interface_counter();
return yy_net_counter_get_by_type(&counter, types);
}
- (NSString *)machineModel {
static dispatch_once_t one;
static NSString *model;
dispatch_once(&one, ^{
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *machine = malloc(size);
sysctlbyname("hw.machine", machine, &size, NULL, 0);
model = [NSString stringWithUTF8String:machine];
free(machine);
});
return model;
}
- (NSString *)machineModelName {
static dispatch_once_t one;
static NSString *name;
dispatch_once(&one, ^{
NSString *model = [self machineModel];
if (!model) return;
NSDictionary *dic = @{
@"Watch1,1" : @"Apple Watch 38mm",
@"Watch1,2" : @"Apple Watch 42mm",
@"Watch2,3" : @"Apple Watch Series 2 38mm",
@"Watch2,4" : @"Apple Watch Series 2 42mm",
@"Watch2,6" : @"Apple Watch Series 1 38mm",
@"Watch1,7" : @"Apple Watch Series 1 42mm",
@"iPod1,1" : @"iPod touch 1",
@"iPod2,1" : @"iPod touch 2",
@"iPod3,1" : @"iPod touch 3",
@"iPod4,1" : @"iPod touch 4",
@"iPod5,1" : @"iPod touch 5",
@"iPod7,1" : @"iPod touch 6",
@"iPhone1,1" : @"iPhone 1G",
@"iPhone1,2" : @"iPhone 3G",
@"iPhone2,1" : @"iPhone 3GS",
@"iPhone3,1" : @"iPhone 4 (GSM)",
@"iPhone3,2" : @"iPhone 4",
@"iPhone3,3" : @"iPhone 4 (CDMA)",
@"iPhone4,1" : @"iPhone 4S",
@"iPhone5,1" : @"iPhone 5",
@"iPhone5,2" : @"iPhone 5",
@"iPhone5,3" : @"iPhone 5c",
@"iPhone5,4" : @"iPhone 5c",
@"iPhone6,1" : @"iPhone 5s",
@"iPhone6,2" : @"iPhone 5s",
@"iPhone7,1" : @"iPhone 6 Plus",
@"iPhone7,2" : @"iPhone 6",
@"iPhone8,1" : @"iPhone 6s",
@"iPhone8,2" : @"iPhone 6s Plus",
@"iPhone8,4" : @"iPhone SE",
@"iPhone9,1" : @"iPhone 7",
@"iPhone9,2" : @"iPhone 7 Plus",
@"iPhone9,3" : @"iPhone 7",
@"iPhone9,4" : @"iPhone 7 Plus",
@"iPad1,1" : @"iPad 1",
@"iPad2,1" : @"iPad 2 (WiFi)",
@"iPad2,2" : @"iPad 2 (GSM)",
@"iPad2,3" : @"iPad 2 (CDMA)",
@"iPad2,4" : @"iPad 2",
@"iPad2,5" : @"iPad mini 1",
@"iPad2,6" : @"iPad mini 1",
@"iPad2,7" : @"iPad mini 1",
@"iPad3,1" : @"iPad 3 (WiFi)",
@"iPad3,2" : @"iPad 3 (4G)",
@"iPad3,3" : @"iPad 3 (4G)",
@"iPad3,4" : @"iPad 4",
@"iPad3,5" : @"iPad 4",
@"iPad3,6" : @"iPad 4",
@"iPad4,1" : @"iPad Air",
@"iPad4,2" : @"iPad Air",
@"iPad4,3" : @"iPad Air",
@"iPad4,4" : @"iPad mini 2",
@"iPad4,5" : @"iPad mini 2",
@"iPad4,6" : @"iPad mini 2",
@"iPad4,7" : @"iPad mini 3",
@"iPad4,8" : @"iPad mini 3",
@"iPad4,9" : @"iPad mini 3",
@"iPad5,1" : @"iPad mini 4",
@"iPad5,2" : @"iPad mini 4",
@"iPad5,3" : @"iPad Air 2",
@"iPad5,4" : @"iPad Air 2",
@"iPad6,3" : @"iPad Pro (9.7 inch)",
@"iPad6,4" : @"iPad Pro (9.7 inch)",
@"iPad6,7" : @"iPad Pro (12.9 inch)",
@"iPad6,8" : @"iPad Pro (12.9 inch)",
@"AppleTV2,1" : @"Apple TV 2",
@"AppleTV3,1" : @"Apple TV 3",
@"AppleTV3,2" : @"Apple TV 3",
@"AppleTV5,3" : @"Apple TV 4",
@"i386" : @"Simulator x86",
@"x86_64" : @"Simulator x64",
};
name = dic[model];
if (!name) name = model;
});
return name;
}
- (NSDate *)systemUptime {
NSTimeInterval time = [[NSProcessInfo processInfo] systemUptime];
return [[NSDate alloc] initWithTimeIntervalSinceNow:(0 - time)];
}
- (int64_t)diskSpace {
NSError *error = nil;
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error];
if (error) return -1;
int64_t space = [[attrs objectForKey:NSFileSystemSize] longLongValue];
if (space < 0) space = -1;
return space;
}
- (int64_t)diskSpaceFree {
NSError *error = nil;
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error];
if (error) return -1;
int64_t space = [[attrs objectForKey:NSFileSystemFreeSize] longLongValue];
if (space < 0) space = -1;
return space;
}
- (int64_t)diskSpaceUsed {
int64_t total = self.diskSpace;
int64_t free = self.diskSpaceFree;
if (total < 0 || free < 0) return -1;
int64_t used = total - free;
if (used < 0) used = -1;
return used;
}
- (int64_t)memoryTotal {
int64_t mem = [[NSProcessInfo processInfo] physicalMemory];
if (mem < -1) mem = -1;
return mem;
}
- (int64_t)memoryUsed {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return page_size * (vm_stat.active_count + vm_stat.inactive_count + vm_stat.wire_count);
}
- (int64_t)memoryFree {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return vm_stat.free_count * page_size;
}
- (int64_t)memoryActive {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return vm_stat.active_count * page_size;
}
- (int64_t)memoryInactive {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return vm_stat.inactive_count * page_size;
}
- (int64_t)memoryWired {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return vm_stat.wire_count * page_size;
}
- (int64_t)memoryPurgable {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return vm_stat.purgeable_count * page_size;
}
- (NSUInteger)cpuCount {
return [NSProcessInfo processInfo].activeProcessorCount;
}
- (float)cpuUsage {
float cpu = 0;
NSArray *cpus = [self cpuUsagePerProcessor];
if (cpus.count == 0) return -1;
for (NSNumber *n in cpus) {
cpu += n.floatValue;
}
return cpu;
}
- (NSArray *)cpuUsagePerProcessor {
processor_info_array_t _cpuInfo, _prevCPUInfo = nil;
mach_msg_type_number_t _numCPUInfo, _numPrevCPUInfo = 0;
unsigned _numCPUs;
NSLock *_cpuUsageLock;
int _mib[2U] = { CTL_HW, HW_NCPU };
size_t _sizeOfNumCPUs = sizeof(_numCPUs);
int _status = sysctl(_mib, 2U, &_numCPUs, &_sizeOfNumCPUs, NULL, 0U);
if (_status)
_numCPUs = 1;
_cpuUsageLock = [[NSLock alloc] init];
natural_t _numCPUsU = 0U;
kern_return_t err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &_numCPUsU, &_cpuInfo, &_numCPUInfo);
if (err == KERN_SUCCESS) {
[_cpuUsageLock lock];
NSMutableArray *cpus = [NSMutableArray new];
for (unsigned i = 0U; i < _numCPUs; ++i) {
Float32 _inUse, _total;
if (_prevCPUInfo) {
_inUse = (
(_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER])
+ (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM])
+ (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE])
);
_total = _inUse + (_cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE] - _prevCPUInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE]);
} else {
_inUse = _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_USER] + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_SYSTEM] + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_NICE];
_total = _inUse + _cpuInfo[(CPU_STATE_MAX * i) + CPU_STATE_IDLE];
}
[cpus addObject:@(_inUse / _total)];
}
[_cpuUsageLock unlock];
if (_prevCPUInfo) {
size_t prevCpuInfoSize = sizeof(integer_t) * _numPrevCPUInfo;
vm_deallocate(mach_task_self(), (vm_address_t)_prevCPUInfo, prevCpuInfoSize);
}
return cpus;
} else {
return nil;
}
}
@end

View File

@@ -0,0 +1,153 @@
//
// UIFont+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <CoreGraphics/CoreGraphics.h>
#import <CoreText/CoreText.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIFont`.
*/
@interface UIFont (YYAdd) <NSCoding>
#pragma mark - Font Traits
///=============================================================================
/// @name Font Traits
///=============================================================================
@property (nonatomic, readonly) BOOL isBold NS_AVAILABLE_IOS(7_0); ///< Whether the font is bold.
@property (nonatomic, readonly) BOOL isItalic NS_AVAILABLE_IOS(7_0); ///< Whether the font is italic.
@property (nonatomic, readonly) BOOL isMonoSpace NS_AVAILABLE_IOS(7_0); ///< Whether the font is mono space.
@property (nonatomic, readonly) BOOL isColorGlyphs NS_AVAILABLE_IOS(7_0); ///< Whether the font is color glyphs (such as Emoji).
@property (nonatomic, readonly) CGFloat fontWeight NS_AVAILABLE_IOS(7_0); ///< Font weight from -1.0 to 1.0. Regular weight is 0.0.
/**
Create a bold font from receiver.
@return A bold font, or nil if failed.
*/
- (nullable UIFont *)fontWithBold NS_AVAILABLE_IOS(7_0);
/**
Create a italic font from receiver.
@return A italic font, or nil if failed.
*/
- (nullable UIFont *)fontWithItalic NS_AVAILABLE_IOS(7_0);
/**
Create a bold and italic font from receiver.
@return A bold and italic font, or nil if failed.
*/
- (nullable UIFont *)fontWithBoldItalic NS_AVAILABLE_IOS(7_0);
/**
Create a normal (no bold/italic/...) font from receiver.
@return A normal font, or nil if failed.
*/
- (nullable UIFont *)fontWithNormal NS_AVAILABLE_IOS(7_0);
#pragma mark - Create font
///=============================================================================
/// @name Create font
///=============================================================================
/**
Creates and returns a font object for the specified CTFontRef.
@param CTFont CoreText font.
*/
+ (nullable UIFont *)fontWithCTFont:(CTFontRef)CTFont;
/**
Creates and returns a font object for the specified CGFontRef and size.
@param CGFont CoreGraphic font.
@param size Font size.
*/
+ (nullable UIFont *)fontWithCGFont:(CGFontRef)CGFont size:(CGFloat)size;
/**
Creates and returns the CTFontRef object. (need call CFRelease() after used)
*/
- (nullable CTFontRef)CTFontRef CF_RETURNS_RETAINED;
/**
Creates and returns the CGFontRef object. (need call CFRelease() after used)
*/
- (nullable CGFontRef)CGFontRef CF_RETURNS_RETAINED;
#pragma mark - Load and unload font
///=============================================================================
/// @name Load and unload font
///=============================================================================
/**
Load the font from file path. Support format:TTF,OTF.
If return YES, font can be load use it PostScript Name: [UIFont fontWithName:...]
@param path font file's full path
*/
+ (BOOL)loadFontFromPath:(NSString *)path;
/**
Unload font from file path.
@param path font file's full path
*/
+ (void)unloadFontFromPath:(NSString *)path;
/**
Load the font from data. Support format:TTF,OTF.
@param data Font data.
@return UIFont object if load succeed, otherwise nil.
*/
+ (nullable UIFont *)loadFontFromData:(NSData *)data;
/**
Unload font which is loaded by loadFontFromData: function.
@param font the font loaded by loadFontFromData: function
@return YES if succeed, otherwise NO.
*/
+ (BOOL)unloadFontFromData:(UIFont *)font;
#pragma mark - Dump font data
///=============================================================================
/// @name Dump font data
///=============================================================================
/**
Serialize and return the font data.
@param font The font.
@return data in TTF, or nil if an error occurs.
*/
+ (nullable NSData *)dataFromFont:(UIFont *)font;
/**
Serialize and return the font data.
@param cgFont The font.
@return data in TTF, or nil if an error occurs.
*/
+ (nullable NSData *)dataFromCGFont:(CGFontRef)cgFont;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,269 @@
//
// UIFont+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIFont+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(UIFont_YYAdd)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprotocol"
// Apple has implemented UIFont<NSCoding>, but did not make it public.
@implementation UIFont (YYAdd)
- (BOOL)isBold {
if (![self respondsToSelector:@selector(fontDescriptor)]) return NO;
return (self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold) > 0;
}
- (BOOL)isItalic {
if (![self respondsToSelector:@selector(fontDescriptor)]) return NO;
return (self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic) > 0;
}
- (BOOL)isMonoSpace {
if (![self respondsToSelector:@selector(fontDescriptor)]) return NO;
return (self.fontDescriptor.symbolicTraits & UIFontDescriptorTraitMonoSpace) > 0;
}
- (BOOL)isColorGlyphs {
if (![self respondsToSelector:@selector(fontDescriptor)]) return NO;
return (CTFontGetSymbolicTraits((__bridge CTFontRef)self) & kCTFontTraitColorGlyphs) != 0;
}
- (CGFloat)fontWeight {
NSDictionary *traits = [self.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
return [traits[UIFontWeightTrait] floatValue];
}
- (UIFont *)fontWithBold {
if (![self respondsToSelector:@selector(fontDescriptor)]) return nil;
return [UIFont fontWithDescriptor:[self.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:self.pointSize];
}
- (UIFont *)fontWithItalic {
if (![self respondsToSelector:@selector(fontDescriptor)]) return nil;
return [UIFont fontWithDescriptor:[self.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic] size:self.pointSize];
}
- (UIFont *)fontWithBoldItalic {
if (![self respondsToSelector:@selector(fontDescriptor)]) return nil;
return [UIFont fontWithDescriptor:[self.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic] size:self.pointSize];
}
- (UIFont *)fontWithNormal {
if (![self respondsToSelector:@selector(fontDescriptor)]) return nil;
return [UIFont fontWithDescriptor:[self.fontDescriptor fontDescriptorWithSymbolicTraits:0] size:self.pointSize];
}
+ (UIFont *)fontWithCTFont:(CTFontRef)CTFont {
if (!CTFont) return nil;
CFStringRef name = CTFontCopyPostScriptName(CTFont);
if (!name) return nil;
CGFloat size = CTFontGetSize(CTFont);
UIFont *font = [self fontWithName:(__bridge NSString *)(name) size:size];
CFRelease(name);
return font;
}
+ (UIFont *)fontWithCGFont:(CGFontRef)CGFont size:(CGFloat)size {
if (!CGFont) return nil;
CFStringRef name = CGFontCopyPostScriptName(CGFont);
if (!name) return nil;
UIFont *font = [self fontWithName:(__bridge NSString *)(name) size:size];
CFRelease(name);
return font;
}
- (CTFontRef)CTFontRef CF_RETURNS_RETAINED {
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.fontName, self.pointSize, NULL);
return font;
}
- (CGFontRef)CGFontRef CF_RETURNS_RETAINED {
CGFontRef font = CGFontCreateWithFontName((__bridge CFStringRef)self.fontName);
return font;
}
+ (BOOL)loadFontFromPath:(NSString *)path {
NSURL *url = [NSURL fileURLWithPath:path];
CFErrorRef error;
BOOL suc = CTFontManagerRegisterFontsForURL((__bridge CFTypeRef)url, kCTFontManagerScopeNone, &error);
if (!suc) {
NSLog(@"Failed to load font: %@", error);
}
return suc;
}
+ (void)unloadFontFromPath:(NSString *)path {
NSURL *url = [NSURL fileURLWithPath:path];
CTFontManagerUnregisterFontsForURL((__bridge CFTypeRef)url, kCTFontManagerScopeNone, NULL);
}
+ (UIFont *)loadFontFromData:(NSData *)data {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if (!provider) return nil;
CGFontRef fontRef = CGFontCreateWithDataProvider(provider);
CGDataProviderRelease(provider);
if (!fontRef) return nil;
CFErrorRef errorRef;
BOOL suc = CTFontManagerRegisterGraphicsFont(fontRef, &errorRef);
if (!suc) {
CFRelease(fontRef);
NSLog(@"%@", errorRef);
return nil;
} else {
CFStringRef fontName = CGFontCopyPostScriptName(fontRef);
UIFont *font = [UIFont fontWithName:(__bridge NSString *)(fontName) size:[UIFont systemFontSize]];
if (fontName) CFRelease(fontName);
CGFontRelease(fontRef);
return font;
}
}
+ (BOOL)unloadFontFromData:(UIFont *)font {
CGFontRef fontRef = CGFontCreateWithFontName((__bridge CFStringRef)font.fontName);
if (!fontRef) return NO;
CFErrorRef errorRef;
BOOL suc = CTFontManagerUnregisterGraphicsFont(fontRef, &errorRef);
CFRelease(fontRef);
if (!suc) NSLog(@"%@", errorRef);
return suc;
}
+ (NSData *)dataFromFont:(UIFont *)font {
CGFontRef cgFont = font.CGFontRef;
NSData *data = [self dataFromCGFont:cgFont];
CGFontRelease(cgFont);
return data;
}
typedef struct FontHeader {
int32_t fVersion;
uint16_t fNumTables;
uint16_t fSearchRange;
uint16_t fEntrySelector;
uint16_t fRangeShift;
} FontHeader;
typedef struct TableEntry {
uint32_t fTag;
uint32_t fCheckSum;
uint32_t fOffset;
uint32_t fLength;
} TableEntry;
static uint32_t CalcTableCheckSum(const uint32_t *table, uint32_t numberOfBytesInTable) {
uint32_t sum = 0;
uint32_t nLongs = (numberOfBytesInTable + 3) / 4;
while (nLongs-- > 0) {
sum += CFSwapInt32HostToBig(*table++);
}
return sum;
}
//Reference:
//https://github.com/google/skia/blob/master/src%2Fports%2FSkFontHost_mac.cpp
+ (NSData *)dataFromCGFont:(CGFontRef)cgFont {
if (!cgFont) return nil;
CFRetain(cgFont);
CFArrayRef tags = CGFontCopyTableTags(cgFont);
if (!tags) return nil;
CFIndex tableCount = CFArrayGetCount(tags);
size_t *tableSizes = malloc(sizeof(size_t) * tableCount);
memset(tableSizes, 0, sizeof(size_t) * tableCount);
BOOL containsCFFTable = NO;
size_t totalSize = sizeof(FontHeader) + sizeof(TableEntry) * tableCount;
for (CFIndex index = 0; index < tableCount; index++) {
size_t tableSize = 0;
uint32_t aTag = (uint32_t)CFArrayGetValueAtIndex(tags, index);
if (aTag == kCTFontTableCFF && !containsCFFTable) {
containsCFFTable = YES;
}
CFDataRef tableDataRef = CGFontCopyTableForTag(cgFont, aTag);
if (tableDataRef) {
tableSize = CFDataGetLength(tableDataRef);
CFRelease(tableDataRef);
}
totalSize += (tableSize + 3) & ~3;
tableSizes[index] = tableSize;
}
unsigned char *stream = malloc(totalSize);
memset(stream, 0, totalSize);
char *dataStart = (char *)stream;
char *dataPtr = dataStart;
// compute font header entries
uint16_t entrySelector = 0;
uint16_t searchRange = 1;
while (searchRange < tableCount >> 1) {
entrySelector++;
searchRange <<= 1;
}
searchRange <<= 4;
uint16_t rangeShift = (tableCount << 4) - searchRange;
// write font header (also called sfnt header, offset subtable)
FontHeader *offsetTable = (FontHeader *)dataPtr;
//OpenType Font contains CFF Table use 'OTTO' as version, and with .otf extension
//otherwise 0001 0000
offsetTable->fVersion = containsCFFTable ? 'OTTO' : CFSwapInt16HostToBig(1);
offsetTable->fNumTables = CFSwapInt16HostToBig((uint16_t)tableCount);
offsetTable->fSearchRange = CFSwapInt16HostToBig((uint16_t)searchRange);
offsetTable->fEntrySelector = CFSwapInt16HostToBig((uint16_t)entrySelector);
offsetTable->fRangeShift = CFSwapInt16HostToBig((uint16_t)rangeShift);
dataPtr += sizeof(FontHeader);
// write tables
TableEntry *entry = (TableEntry *)dataPtr;
dataPtr += sizeof(TableEntry) * tableCount;
for (int index = 0; index < tableCount; ++index) {
uint32_t aTag = (uint32_t)CFArrayGetValueAtIndex(tags, index);
CFDataRef tableDataRef = CGFontCopyTableForTag(cgFont, aTag);
size_t tableSize = CFDataGetLength(tableDataRef);
memcpy(dataPtr, CFDataGetBytePtr(tableDataRef), tableSize);
entry->fTag = CFSwapInt32HostToBig((uint32_t)aTag);
entry->fCheckSum = CFSwapInt32HostToBig(CalcTableCheckSum((uint32_t *)dataPtr, (uint32_t)tableSize));
uint32_t offset = (uint32_t)dataPtr - (uint32_t)dataStart;
entry->fOffset = CFSwapInt32HostToBig((uint32_t)offset);
entry->fLength = CFSwapInt32HostToBig((uint32_t)tableSize);
dataPtr += (tableSize + 3) & ~3;
++entry;
CFRelease(tableDataRef);
}
CFRelease(cgFont);
CFRelease(tags);
free(tableSizes);
NSData *fontData = [NSData dataWithBytesNoCopy:stream length:totalSize freeWhenDone:YES];
return fontData;
}
@end
#pragma clang diagnostic pop

View File

@@ -0,0 +1,47 @@
//
// UIGestureRecognizer+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/10/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIGestureRecognizer`.
*/
@interface UIGestureRecognizer (YYAdd)
/**
Initializes an allocated gesture-recognizer object with a action block.
@param block An action block that to handle the gesture recognized by the
receiver. nil is invalid. It is retained by the gesture.
@return An initialized instance of a concrete UIGestureRecognizer subclass or
nil if an error occurred in the attempt to initialize the object.
*/
- (instancetype)initWithActionBlock:(void (^)(id sender))block;
/**
Adds an action block to a gesture-recognizer object. It is retained by the
gesture.
@param block A block invoked by the action message. nil is not a valid value.
*/
- (void)addActionBlock:(void (^)(id sender))block;
/**
Remove all action blocks.
*/
- (void)removeAllActionBlocks;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,78 @@
//
// UIGestureRecognizer+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/10/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIGestureRecognizer+YYAdd.h"
#import "YYCategoriesMacro.h"
#import <objc/runtime.h>
static const int block_key;
@interface _YYUIGestureRecognizerBlockTarget : NSObject
@property (nonatomic, copy) void (^block)(id sender);
- (id)initWithBlock:(void (^)(id sender))block;
- (void)invoke:(id)sender;
@end
@implementation _YYUIGestureRecognizerBlockTarget
- (id)initWithBlock:(void (^)(id sender))block{
self = [super init];
if (self) {
_block = [block copy];
}
return self;
}
- (void)invoke:(id)sender {
if (_block) _block(sender);
}
@end
@implementation UIGestureRecognizer (YYAdd)
- (instancetype)initWithActionBlock:(void (^)(id sender))block {
self = [self init];
[self addActionBlock:block];
return self;
}
- (void)addActionBlock:(void (^)(id sender))block {
_YYUIGestureRecognizerBlockTarget *target = [[_YYUIGestureRecognizerBlockTarget alloc] initWithBlock:block];
[self addTarget:target action:@selector(invoke:)];
NSMutableArray *targets = [self _yy_allUIGestureRecognizerBlockTargets];
[targets addObject:target];
}
- (void)removeAllActionBlocks{
NSMutableArray *targets = [self _yy_allUIGestureRecognizerBlockTargets];
[targets enumerateObjectsUsingBlock:^(id target, NSUInteger idx, BOOL *stop) {
[self removeTarget:target action:@selector(invoke:)];
}];
[targets removeAllObjects];
}
- (NSMutableArray *)_yy_allUIGestureRecognizerBlockTargets {
NSMutableArray *targets = objc_getAssociatedObject(self, &block_key);
if (!targets) {
targets = [NSMutableArray array];
objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return targets;
}
@end

View File

@@ -0,0 +1,377 @@
//
// UIImage+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide some commen method for `UIImage`.
Image process is based on CoreGraphic and vImage.
*/
@interface UIImage (YYAdd)
#pragma mark - Create image
///=============================================================================
/// @name Create image
///=============================================================================
/**
Create an animated image with GIF data. After created, you can access
the images via property '.images'. If the data is not animated gif, this
function is same as [UIImage imageWithData:data scale:scale];
@discussion It has a better display performance, but costs more memory
(width * height * frames Bytes). It only suited to display small
gif such as animated emoji. If you want to display large gif,
see `YYImage`.
@param data GIF data.
@param scale The scale factor
@return A new image created from GIF, or nil when an error occurs.
*/
+ (nullable UIImage *)imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale;
/**
Whether the data is animated GIF.
@param data Image data
@return Returns YES only if the data is gif and contains more than one frame,
otherwise returns NO.
*/
+ (BOOL)isAnimatedGIFData:(NSData *)data;
/**
Whether the file in the specified path is GIF.
@param path An absolute file path.
@return Returns YES if the file is gif, otherwise returns NO.
*/
+ (BOOL)isAnimatedGIFFile:(NSString *)path;
/**
Create an image from a PDF file data or path.
@discussion If the PDF has multiple page, is just return's the first page's
content. Image's scale is equal to current screen's scale, size is same as
PDF's origin size.
@param dataOrPath PDF data in `NSData`, or PDF file path in `NSString`.
@return A new image create from PDF, or nil when an error occurs.
*/
+ (nullable UIImage *)imageWithPDF:(id)dataOrPath;
/**
Create an image from a PDF file data or path.
@discussion If the PDF has multiple page, is just return's the first page's
content. Image's scale is equal to current screen's scale.
@param dataOrPath PDF data in `NSData`, or PDF file path in `NSString`.
@param size The new image's size, PDF's content will be stretched as needed.
@return A new image create from PDF, or nil when an error occurs.
*/
+ (nullable UIImage *)imageWithPDF:(id)dataOrPath size:(CGSize)size;
/**
Create a square image from apple emoji.
@discussion It creates a square image from apple emoji, image's scale is equal
to current screen's scale. The original emoji image in `AppleColorEmoji` font
is in size 160*160 px.
@param emoji single emoji, such as @"😄".
@param size image's size.
@return Image from emoji, or nil when an error occurs.
*/
+ (nullable UIImage *)imageWithEmoji:(NSString *)emoji size:(CGFloat)size;
/**
Create and return a 1x1 point size image with the given color.
@param color The color.
*/
+ (nullable UIImage *)imageWithColor:(UIColor *)color;
/**
Create and return a pure color image with the given color and size.
@param color The color.
@param size New image's type.
*/
+ (nullable UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size;
/**
Create and return an image with custom draw code.
@param size The image size.
@param drawBlock The draw block.
@return The new image.
*/
+ (nullable UIImage *)imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock;
#pragma mark - Image Info
///=============================================================================
/// @name Image Info
///=============================================================================
/**
Whether this image has alpha channel.
*/
- (BOOL)hasAlphaChannel;
#pragma mark - Modify Image
///=============================================================================
/// @name Modify Image
///=============================================================================
/**
Draws the entire image in the specified rectangle, content changed with
the contentMode.
@discussion This method draws the entire image in the current graphics context,
respecting the image's orientation setting. In the default coordinate system,
images are situated down and to the right of the origin of the specified
rectangle. This method respects any transforms applied to the current graphics
context, however.
@param rect The rectangle in which to draw the image.
@param contentMode Draw content mode
@param clips A Boolean value that determines whether content are confined to the rect.
*/
- (void)drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips;
/**
Returns a new image which is scaled from this image.
The image will be stretched as needed.
@param size The new size to be scaled, values should be positive.
@return The new image with the given size.
*/
- (nullable UIImage *)imageByResizeToSize:(CGSize)size;
/**
Returns a new image which is scaled from this image.
The image content will be changed with thencontentMode.
@param size The new size to be scaled, values should be positive.
@param contentMode The content mode for image content.
@return The new image with the given size.
*/
- (nullable UIImage *)imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode;
/**
Returns a new image which is cropped from this image.
@param rect Image's inner rect.
@return The new image, or nil if an error occurs.
*/
- (nullable UIImage *)imageByCropToRect:(CGRect)rect;
/**
Returns a new image which is edge inset from this image.
@param insets Inset (positive) for each of the edges, values can be negative to 'outset'.
@param color Extend edge's fill color, nil means clear color.
@return The new image, or nil if an error occurs.
*/
- (nullable UIImage *)imageByInsetEdge:(UIEdgeInsets)insets withColor:(nullable UIColor *)color;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to half
the width or height.
*/
- (nullable UIImage *)imageByRoundCornerRadius:(CGFloat)radius;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to
half the width or height.
@param borderWidth The inset border line width. Values larger than half the rectangle's
width or height are clamped appropriately to half the width
or height.
@param borderColor The border stroke color. nil means clear color.
*/
- (nullable UIImage *)imageByRoundCornerRadius:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
borderColor:(nullable UIColor *)borderColor;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to
half the width or height.
@param corners A bitmask value that identifies the corners that you want
rounded. You can use this parameter to round only a subset
of the corners of the rectangle.
@param borderWidth The inset border line width. Values larger than half the rectangle's
width or height are clamped appropriately to half the width
or height.
@param borderColor The border stroke color. nil means clear color.
@param borderLineJoin The border line join.
*/
- (nullable UIImage *)imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(nullable UIColor *)borderColor
borderLineJoin:(CGLineJoin)borderLineJoin;
/**
Returns a new rotated image (relative to the center).
@param radians Rotated radians in counterclockwise.⟲
@param fitSize YES: new image's size is extend to fit all content.
NO: image's size will not change, content may be clipped.
*/
- (nullable UIImage *)imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize;
/**
Returns a new image rotated counterclockwise by a quarterturn (90°). ⤺
The width and height will be exchanged.
*/
- (nullable UIImage *)imageByRotateLeft90;
/**
Returns a new image rotated clockwise by a quarterturn (90°). ⤼
The width and height will be exchanged.
*/
- (nullable UIImage *)imageByRotateRight90;
/**
Returns a new image rotated 180° . ↻
*/
- (nullable UIImage *)imageByRotate180;
/**
Returns a vertically flipped image. ⥯
*/
- (nullable UIImage *)imageByFlipVertical;
/**
Returns a horizontally flipped image. ⇋
*/
- (nullable UIImage *)imageByFlipHorizontal;
#pragma mark - Image Effect
///=============================================================================
/// @name Image Effect
///=============================================================================
/**
Tint the image in alpha channel with the given color.
@param color The color.
*/
- (nullable UIImage *)imageByTintColor:(UIColor *)color;
/**
Returns a grayscaled image.
*/
- (nullable UIImage *)imageByGrayscale;
/**
Applies a blur effect to this image. Suitable for blur any content.
*/
- (nullable UIImage *)imageByBlurSoft;
/**
Applies a blur effect to this image. Suitable for blur any content except pure white.
(same as iOS Control Panel)
*/
- (nullable UIImage *)imageByBlurLight;
/**
Applies a blur effect to this image. Suitable for displaying black text.
(same as iOS Navigation Bar White)
*/
- (nullable UIImage *)imageByBlurExtraLight;
/**
Applies a blur effect to this image. Suitable for displaying white text.
(same as iOS Notification Center)
*/
- (nullable UIImage *)imageByBlurDark;
/**
Applies a blur and tint color to this image.
@param tintColor The tint color.
*/
- (nullable UIImage *)imageByBlurWithTint:(UIColor *)tintColor;
/**
Applies a blur, tint color, and saturation adjustment to this image,
optionally within the area specified by @a maskImage.
@param blurRadius The radius of the blur in points, 0 means no blur effect.
@param tintColor An optional UIColor object that is uniformly blended with
the result of the blur and saturation operations. The
alpha channel of this color determines how strong the
tint is. nil means no tint.
@param tintBlendMode The @a tintColor blend mode. Default is kCGBlendModeNormal (0).
@param saturation A value of 1.0 produces no change in the resulting image.
Values less than 1.0 will desaturation the resulting image
while values greater than 1.0 will have the opposite effect.
0 means gray scale.
@param maskImage If specified, @a inputImage is only modified in the area(s)
defined by this mask. This must be an image mask or it
must meet the requirements of the mask parameter of
CGContextClipToMask.
@return image with effect, or nil if an error occurs (e.g. no
enough memory).
*/
- (nullable UIImage *)imageByBlurRadius:(CGFloat)blurRadius
tintColor:(nullable UIColor *)tintColor
tintMode:(CGBlendMode)tintBlendMode
saturation:(CGFloat)saturation
maskImage:(nullable UIImage *)maskImage;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,752 @@
//
// UIImage+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIImage+YYAdd.h"
#import "UIDevice+YYAdd.h"
#import "NSString+YYAdd.h"
#import "YYCategoriesMacro.h"
#import "YYCGUtilities.h"
#import <ImageIO/ImageIO.h>
#import <Accelerate/Accelerate.h>
#import <CoreText/CoreText.h>
#import <objc/runtime.h>
#import "YYCGUtilities.h"
YYSYNTH_DUMMY_CLASS(UIImage_YYAdd)
static NSTimeInterval _yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index) {
NSTimeInterval delay = 0;
CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
if (dic) {
CFDictionaryRef dicGIF = CFDictionaryGetValue(dic, kCGImagePropertyGIFDictionary);
if (dicGIF) {
NSNumber *num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFUnclampedDelayTime);
if (num.doubleValue <= __FLT_EPSILON__) {
num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFDelayTime);
}
delay = num.doubleValue;
}
CFRelease(dic);
}
// http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
if (delay < 0.02) delay = 0.1;
return delay;
}
@implementation UIImage (YYAdd)
+ (UIImage *)imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale {
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)(data), NULL);
if (!source) return nil;
size_t count = CGImageSourceGetCount(source);
if (count <= 1) {
CFRelease(source);
return [self.class imageWithData:data scale:scale];
}
NSUInteger frames[count];
double oneFrameTime = 1 / 50.0; // 50 fps
NSTimeInterval totalTime = 0;
NSUInteger totalFrame = 0;
NSUInteger gcdFrame = 0;
for (size_t i = 0; i < count; i++) {
NSTimeInterval delay = _yy_CGImageSourceGetGIFFrameDelayAtIndex(source, i);
totalTime += delay;
NSInteger frame = lrint(delay / oneFrameTime);
if (frame < 1) frame = 1;
frames[i] = frame;
totalFrame += frames[i];
if (i == 0) gcdFrame = frames[i];
else {
NSUInteger frame = frames[i], tmp;
if (frame < gcdFrame) {
tmp = frame; frame = gcdFrame; gcdFrame = tmp;
}
while (true) {
tmp = frame % gcdFrame;
if (tmp == 0) break;
frame = gcdFrame;
gcdFrame = tmp;
}
}
}
NSMutableArray *array = [NSMutableArray new];
for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
CFRelease(source);
return nil;
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
if (width == 0 || height == 0) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// BGRA8888 (premultiplied) or BGRX8888
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
CGColorSpaceRelease(space);
if (!context) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
CGImageRef decoded = CGBitmapContextCreateImage(context);
CFRelease(context);
if (!decoded) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
UIImage *image = [UIImage imageWithCGImage:decoded scale:scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
CGImageRelease(decoded);
if (!image) {
CFRelease(source);
return nil;
}
for (size_t j = 0, max = frames[i] / gcdFrame; j < max; j++) {
[array addObject:image];
}
}
CFRelease(source);
UIImage *image = [self.class animatedImageWithImages:array duration:totalTime];
return image;
}
+ (BOOL)isAnimatedGIFData:(NSData *)data {
if (data.length < 16) return NO;
UInt32 magic = *(UInt32 *)data.bytes;
// http://www.w3.org/Graphics/GIF/spec-gif89a.txt
if ((magic & 0xFFFFFF) != '\0FIG') return NO;
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL);
if (!source) return NO;
size_t count = CGImageSourceGetCount(source);
CFRelease(source);
return count > 1;
}
+ (BOOL)isAnimatedGIFFile:(NSString *)path {
if (path.length == 0) return NO;
const char *cpath = path.UTF8String;
FILE *fd = fopen(cpath, "rb");
if (!fd) return NO;
BOOL isGIF = NO;
UInt32 magic = 0;
if (fread(&magic, sizeof(UInt32), 1, fd) == 1) {
if ((magic & 0xFFFFFF) == '\0FIG') isGIF = YES;
}
fclose(fd);
return isGIF;
}
+ (UIImage *)imageWithPDF:(id)dataOrPath {
return [self _yy_imageWithPDF:dataOrPath resize:NO size:CGSizeZero];
}
+ (UIImage *)imageWithPDF:(id)dataOrPath size:(CGSize)size {
return [self _yy_imageWithPDF:dataOrPath resize:YES size:size];
}
+ (UIImage *)imageWithEmoji:(NSString *)emoji size:(CGFloat)size {
if (emoji.length == 0) return nil;
if (size < 1) return nil;
CGFloat scale = [UIScreen mainScreen].scale;
CTFontRef font = CTFontCreateWithName(CFSTR("AppleColorEmoji"), size * scale, NULL);
if (!font) return nil;
NSAttributedString *str = [[NSAttributedString alloc] initWithString:emoji attributes:@{ (__bridge id)kCTFontAttributeName:(__bridge id)font, (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor whiteColor].CGColor }];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(NULL, size * scale, size * scale, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
CTLineRef line = CTLineCreateWithAttributedString((__bridge CFTypeRef)str);
CGRect bounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsUseGlyphPathBounds);
CGContextSetTextPosition(ctx, 0, -bounds.origin.y);
CTLineDraw(line, ctx);
CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
CFRelease(font);
CGColorSpaceRelease(colorSpace);
CGContextRelease(ctx);
if (line)CFRelease(line);
if (imageRef) CFRelease(imageRef);
return image;
}
+ (UIImage *)_yy_imageWithPDF:(id)dataOrPath resize:(BOOL)resize size:(CGSize)size {
CGPDFDocumentRef pdf = NULL;
if ([dataOrPath isKindOfClass:[NSData class]]) {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)dataOrPath);
pdf = CGPDFDocumentCreateWithProvider(provider);
CGDataProviderRelease(provider);
} else if ([dataOrPath isKindOfClass:[NSString class]]) {
pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:dataOrPath]);
}
if (!pdf) return nil;
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);
if (!page) {
CGPDFDocumentRelease(pdf);
return nil;
}
CGRect pdfRect = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
CGSize pdfSize = resize ? size : pdfRect.size;
CGFloat scale = [UIScreen mainScreen].scale;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(NULL, pdfSize.width * scale, pdfSize.height * scale, 8, 0, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
if (!ctx) {
CGColorSpaceRelease(colorSpace);
CGPDFDocumentRelease(pdf);
return nil;
}
CGContextScaleCTM(ctx, scale, scale);
CGContextTranslateCTM(ctx, -pdfRect.origin.x, -pdfRect.origin.y);
CGContextDrawPDFPage(ctx, page);
CGPDFDocumentRelease(pdf);
CGImageRef image = CGBitmapContextCreateImage(ctx);
UIImage *pdfImage = [[UIImage alloc] initWithCGImage:image scale:scale orientation:UIImageOrientationUp];
CGImageRelease(image);
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
return pdfImage;
}
+ (UIImage *)imageWithColor:(UIColor *)color {
return [self imageWithColor:color size:CGSizeMake(1, 1)];
}
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
if (!color || size.width <= 0 || size.height <= 0) return nil;
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (UIImage *)imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
if (!drawBlock) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) return nil;
drawBlock(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (BOOL)hasAlphaChannel {
if (self.CGImage == NULL) return NO;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
return (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
}
- (void)drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{
CGRect drawRect = YYCGRectFitWithContentMode(rect, self.size, contentMode);
if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
if (clips) {
CGContextRef context = UIGraphicsGetCurrentContext();
if (context) {
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
[self drawInRect:drawRect];
CGContextRestoreGState(context);
}
} else {
[self drawInRect:drawRect];
}
}
- (UIImage *)imageByResizeToSize:(CGSize)size {
if (size.width <= 0 || size.height <= 0) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
if (size.width <= 0 || size.height <= 0) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
[self drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)imageByCropToRect:(CGRect)rect {
rect.origin.x *= self.scale;
rect.origin.y *= self.scale;
rect.size.width *= self.scale;
rect.size.height *= self.scale;
if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imageRef);
return image;
}
- (UIImage *)imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color {
CGSize size = self.size;
size.width -= insets.left + insets.right;
size.height -= insets.top + insets.bottom;
if (size.width <= 0 || size.height <= 0) return nil;
CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (color) {
CGContextSetFillColorWithColor(context, color.CGColor);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
CGPathAddRect(path, NULL, rect);
CGContextAddPath(context, path);
CGContextEOFillPath(context);
CGPathRelease(path);
}
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius {
return [self imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
}
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor {
return [self imageByRoundCornerRadius:radius
corners:UIRectCornerAllCorners
borderWidth:borderWidth
borderColor:borderColor
borderLineJoin:kCGLineJoinMiter];
}
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor
borderLineJoin:(CGLineJoin)borderLineJoin {
if (corners != UIRectCornerAllCorners) {
UIRectCorner tmp = 0;
if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
corners = tmp;
}
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
[path closePath];
CGContextSaveGState(context);
[path addClip];
CGContextDrawImage(context, rect, self.CGImage);
CGContextRestoreGState(context);
}
if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
[path closePath];
path.lineWidth = borderWidth;
path.lineJoinStyle = borderLineJoin;
[borderColor setStroke];
[path stroke];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize {
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
(size_t)newRect.size.width,
(size_t)newRect.size.height,
8,
(size_t)newRect.size.width * 4,
colorSpace,
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
CGContextRotateCTM(context, radians);
CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imgRef);
CGContextRelease(context);
return img;
}
- (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
if (!self.CGImage) return nil;
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
size_t bytesPerRow = width * 4;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
if (!data) {
CGContextRelease(context);
return nil;
}
vImage_Buffer src = { data, height, width, bytesPerRow };
vImage_Buffer dest = { data, height, width, bytesPerRow };
if (vertical) {
vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
if (horizontal) {
vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imgRef);
return img;
}
- (UIImage *)imageByRotateLeft90 {
return [self imageByRotate:DegreesToRadians(90) fitSize:YES];
}
- (UIImage *)imageByRotateRight90 {
return [self imageByRotate:DegreesToRadians(-90) fitSize:YES];
}
- (UIImage *)imageByRotate180 {
return [self _yy_flipHorizontal:YES vertical:YES];
}
- (UIImage *)imageByFlipVertical {
return [self _yy_flipHorizontal:NO vertical:YES];
}
- (UIImage *)imageByFlipHorizontal {
return [self _yy_flipHorizontal:YES vertical:NO];
}
- (UIImage *)imageByTintColor:(UIColor *)color {
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
[color set];
UIRectFill(rect);
[self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (UIImage *)imageByGrayscale {
return [self imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
}
- (UIImage *)imageByBlurSoft {
return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)imageByBlurLight {
return [self imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)imageByBlurExtraLight {
return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)imageByBlurDark {
return [self imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)imageByBlurWithTint:(UIColor *)tintColor {
const CGFloat EffectColorAlpha = 0.6;
UIColor *effectColor = tintColor;
size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
if (componentCount == 2) {
CGFloat b;
if ([tintColor getWhite:&b alpha:NULL]) {
effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
}
} else {
CGFloat r, g, b;
if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
}
}
return [self imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
}
- (UIImage *)imageByBlurRadius:(CGFloat)blurRadius
tintColor:(UIColor *)tintColor
tintMode:(CGBlendMode)tintBlendMode
saturation:(CGFloat)saturation
maskImage:(UIImage *)maskImage {
if (self.size.width < 1 || self.size.height < 1) {
NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
return nil;
}
if (!self.CGImage) {
NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self);
return nil;
}
if (maskImage && !maskImage.CGImage) {
NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage);
return nil;
}
// iOS7 and above can use new func.
BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0;
BOOL hasBlur = blurRadius > __FLT_EPSILON__;
BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__;
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
CGImageRef imageRef = self.CGImage;
BOOL opaque = NO;
if (!hasBlur && !hasSaturation) {
return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
}
vImage_Buffer effect = { 0 }, scratch = { 0 };
vImage_Buffer *input = NULL, *output = NULL;
vImage_CGImageFormat format = {
.bitsPerComponent = 8,
.bitsPerPixel = 32,
.colorSpace = NULL,
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
.version = 0,
.decode = NULL,
.renderingIntent = kCGRenderingIntentDefault
};
if (hasNewFunc) {
vImage_Error err;
err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
if (err != kvImageNoError) {
NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
return nil;
}
err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
if (err != kvImageNoError) {
NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
return nil;
}
} else {
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef effectCtx = UIGraphicsGetCurrentContext();
CGContextScaleCTM(effectCtx, 1.0, -1.0);
CGContextTranslateCTM(effectCtx, 0, -size.height);
CGContextDrawImage(effectCtx, rect, imageRef);
effect.data = CGBitmapContextGetData(effectCtx);
effect.width = CGBitmapContextGetWidth(effectCtx);
effect.height = CGBitmapContextGetHeight(effectCtx);
effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx);
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef scratchCtx = UIGraphicsGetCurrentContext();
scratch.data = CGBitmapContextGetData(scratchCtx);
scratch.width = CGBitmapContextGetWidth(scratchCtx);
scratch.height = CGBitmapContextGetHeight(scratchCtx);
scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx);
}
input = &effect;
output = &scratch;
if (hasBlur) {
// A description of how to compute the box kernel width from the Gaussian
// radius (aka standard deviation) appears in the SVG spec:
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
//
// For larger values of 's' (s >= 2.0), an approximation can be used: Three
// successive box-blurs build a piece-wise quadratic convolution kernel, which
// approximates the Gaussian kernel to within roughly 3%.
//
// let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
CGFloat inputRadius = blurRadius * scale;
if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
radius |= 1; // force radius to be odd so that the three box-blur methodology works.
int iterations;
if (blurRadius * scale < 0.5) iterations = 1;
else if (blurRadius * scale < 1.5) iterations = 2;
else iterations = 3;
NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
void *temp = malloc(tempSize);
for (int i = 0; i < iterations; i++) {
vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
YY_SWAP(input, output);
}
free(temp);
}
if (hasSaturation) {
// These values appear in the W3C Filter Effects spec:
// https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent
CGFloat s = saturation;
CGFloat matrixFloat[] = {
0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
0, 0, 0, 1,
};
const int32_t divisor = 256;
NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]);
int16_t matrix[matrixSize];
for (NSUInteger i = 0; i < matrixSize; ++i) {
matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor);
}
vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags);
YY_SWAP(input, output);
}
UIImage *outputImage = nil;
if (hasNewFunc) {
CGImageRef effectCGImage = NULL;
effectCGImage = vImageCreateCGImageFromBuffer(input, &format, &_yy_cleanupBuffer, NULL, kvImageNoAllocate, NULL);
if (effectCGImage == NULL) {
effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
free(input->data);
}
free(output->data);
outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
CGImageRelease(effectCGImage);
} else {
CGImageRef effectCGImage;
UIImage *effectImage;
if (input != &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (input == &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
effectCGImage = effectImage.CGImage;
outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
}
return outputImage;
}
// Helper function to handle deferred cleanup of a buffer.
static void _yy_cleanupBuffer(void *userData, void *buf_data) {
free(buf_data);
}
// Helper function to add tint and mask.
- (UIImage *)_yy_mergeImageRef:(CGImageRef)effectCGImage
tintColor:(UIColor *)tintColor
tintBlendMode:(CGBlendMode)tintBlendMode
maskImage:(UIImage *)maskImage
opaque:(BOOL)opaque {
BOOL hasTint = tintColor != nil && CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
BOOL hasMask = maskImage != nil;
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
if (!hasTint && !hasMask) {
return [UIImage imageWithCGImage:effectCGImage];
}
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -size.height);
if (hasMask) {
CGContextDrawImage(context, rect, self.CGImage);
CGContextSaveGState(context);
CGContextClipToMask(context, rect, maskImage.CGImage);
}
CGContextDrawImage(context, rect, effectCGImage);
if (hasTint) {
CGContextSaveGState(context);
CGContextSetBlendMode(context, tintBlendMode);
CGContextSetFillColorWithColor(context, tintColor.CGColor);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
}
if (hasMask) {
CGContextRestoreGState(context);
}
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
@end

View File

@@ -0,0 +1,63 @@
//
// UIScreen+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIScreen`.
*/
@interface UIScreen (YYAdd)
/**
Main screen's scale
@return screen's scale
*/
+ (CGFloat)screenScale;
/**
Returns the bounds of the screen for the current device orientation.
@return A rect indicating the bounds of the screen.
@see boundsForOrientation:
*/
- (CGRect)currentBounds NS_EXTENSION_UNAVAILABLE_IOS("");
/**
Returns the bounds of the screen for a given device orientation.
`UIScreen`'s `bounds` method always returns the bounds of the
screen of it in the portrait orientation.
@param orientation The orientation to get the screen's bounds.
@return A rect indicating the bounds of the screen.
@see currentBounds
*/
- (CGRect)boundsForOrientation:(UIInterfaceOrientation)orientation;
/**
The screen's real size in pixel (width is always smaller than height).
This value may not be very accurate in an unknown device, or simulator.
e.g. (768,1024)
*/
@property (nonatomic, readonly) CGSize sizeInPixel;
/**
The screen's PPI.
This value may not be very accurate in an unknown device, or simulator.
Default value is 96.
*/
@property (nonatomic, readonly) CGFloat pixelsPerInch;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,176 @@
//
// UIScreen+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIScreen+YYAdd.h"
#import "YYCategoriesMacro.h"
#import "UIDevice+YYAdd.h"
YYSYNTH_DUMMY_CLASS(UIScreen_YYAdd);
@implementation UIScreen (YYAdd)
+ (CGFloat)screenScale {
static CGFloat screenScale = 0.0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([NSThread isMainThread]) {
screenScale = [[UIScreen mainScreen] scale];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
screenScale = [[UIScreen mainScreen] scale];
});
}
});
return screenScale;
}
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
- (CGRect)currentBounds {
return [self boundsForOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
}
#endif
- (CGRect)boundsForOrientation:(UIInterfaceOrientation)orientation {
CGRect bounds = [self bounds];
if (UIInterfaceOrientationIsLandscape(orientation)) {
CGFloat buffer = bounds.size.width;
bounds.size.width = bounds.size.height;
bounds.size.height = buffer;
}
return bounds;
}
- (CGSize)sizeInPixel {
CGSize size = CGSizeZero;
if ([[UIScreen mainScreen] isEqual:self]) {
NSString *model = [UIDevice currentDevice].machineModel;
if ([model hasPrefix:@"iPhone"]) {
if ([model isEqualToString:@"iPhone7,1"]) return CGSizeMake(1080, 1920);
if ([model isEqualToString:@"iPhone8,2"]) return CGSizeMake(1080, 1920);
if ([model isEqualToString:@"iPhone9,2"]) return CGSizeMake(1080, 1920);
if ([model isEqualToString:@"iPhone9,4"]) return CGSizeMake(1080, 1920);
}
if ([model hasPrefix:@"iPad"]) {
if ([model hasPrefix:@"iPad6,7"]) size = CGSizeMake(2048, 2732);
if ([model hasPrefix:@"iPad6,8"]) size = CGSizeMake(2048, 2732);
}
}
if (CGSizeEqualToSize(size, CGSizeZero)) {
if ([self respondsToSelector:@selector(nativeBounds)]) {
size = self.nativeBounds.size;
} else {
size = self.bounds.size;
size.width *= self.scale;
size.height *= self.scale;
}
if (size.height < size.width) {
CGFloat tmp = size.height;
size.height = size.width;
size.width = tmp;
}
}
return size;
}
- (CGFloat)pixelsPerInch {
if (![[UIScreen mainScreen] isEqual:self]) {
return 326;
}
static CGFloat ppi = 0;
static dispatch_once_t one;
static NSString *name;
dispatch_once(&one, ^{
NSDictionary<NSString*, NSNumber *> *dic = @{
@"Watch1,1" : @326, //@"Apple Watch 38mm",
@"Watch1,2" : @326, //@"Apple Watch 43mm",
@"Watch2,3" : @326, //@"Apple Watch Series 2 38mm",
@"Watch2,4" : @326, //@"Apple Watch Series 2 42mm",
@"Watch2,6" : @326, //@"Apple Watch Series 1 38mm",
@"Watch1,7" : @326, //@"Apple Watch Series 1 42mm",
@"iPod1,1" : @163, //@"iPod touch 1",
@"iPod2,1" : @163, //@"iPod touch 2",
@"iPod3,1" : @163, //@"iPod touch 3",
@"iPod4,1" : @326, //@"iPod touch 4",
@"iPod5,1" : @326, //@"iPod touch 5",
@"iPod7,1" : @326, //@"iPod touch 6",
@"iPhone1,1" : @163, //@"iPhone 1G",
@"iPhone1,2" : @163, //@"iPhone 3G",
@"iPhone2,1" : @163, //@"iPhone 3GS",
@"iPhone3,1" : @326, //@"iPhone 4 (GSM)",
@"iPhone3,2" : @326, //@"iPhone 4",
@"iPhone3,3" : @326, //@"iPhone 4 (CDMA)",
@"iPhone4,1" : @326, //@"iPhone 4S",
@"iPhone5,1" : @326, //@"iPhone 5",
@"iPhone5,2" : @326, //@"iPhone 5",
@"iPhone5,3" : @326, //@"iPhone 5c",
@"iPhone5,4" : @326, //@"iPhone 5c",
@"iPhone6,1" : @326, //@"iPhone 5s",
@"iPhone6,2" : @326, //@"iPhone 5s",
@"iPhone7,1" : @401, //@"iPhone 6 Plus",
@"iPhone7,2" : @326, //@"iPhone 6",
@"iPhone8,1" : @326, //@"iPhone 6s",
@"iPhone8,2" : @401, //@"iPhone 6s Plus",
@"iPhone8,4" : @326, //@"iPhone SE",
@"iPhone9,1" : @326, //@"iPhone 7",
@"iPhone9,2" : @401, //@"iPhone 7 Plus",
@"iPhone9,3" : @326, //@"iPhone 7",
@"iPhone9,4" : @401, //@"iPhone 7 Plus",
@"iPad1,1" : @132, //@"iPad 1",
@"iPad2,1" : @132, //@"iPad 2 (WiFi)",
@"iPad2,2" : @132, //@"iPad 2 (GSM)",
@"iPad2,3" : @132, //@"iPad 2 (CDMA)",
@"iPad2,4" : @132, //@"iPad 2",
@"iPad2,5" : @264, //@"iPad mini 1",
@"iPad2,6" : @264, //@"iPad mini 1",
@"iPad2,7" : @264, //@"iPad mini 1",
@"iPad3,1" : @324, //@"iPad 3 (WiFi)",
@"iPad3,2" : @324, //@"iPad 3 (4G)",
@"iPad3,3" : @324, //@"iPad 3 (4G)",
@"iPad3,4" : @324, //@"iPad 4",
@"iPad3,5" : @324, //@"iPad 4",
@"iPad3,6" : @324, //@"iPad 4",
@"iPad4,1" : @324, //@"iPad Air",
@"iPad4,2" : @324, //@"iPad Air",
@"iPad4,3" : @324, //@"iPad Air",
@"iPad4,4" : @264, //@"iPad mini 2",
@"iPad4,5" : @264, //@"iPad mini 2",
@"iPad4,6" : @264, //@"iPad mini 2",
@"iPad4,7" : @264, //@"iPad mini 3",
@"iPad4,8" : @264, //@"iPad mini 3",
@"iPad4,9" : @264, //@"iPad mini 3",
@"iPad5,1" : @264, //@"iPad mini 4",
@"iPad5,2" : @264, //@"iPad mini 4",
@"iPad5,3" : @324, //@"iPad Air 2",
@"iPad5,4" : @324, //@"iPad Air 2",
@"iPad6,3" : @324, //@"iPad Pro (9.7 inch)",
@"iPad6,4" : @324, //@"iPad Pro (9.7 inch)",
@"iPad6,7" : @264, //@"iPad Pro (12.9 inch)",
@"iPad6,8" : @264, //@"iPad Pro (12.9 inch)",
};
NSString *model = [UIDevice currentDevice].machineModel;
if (model) {
ppi = dic[name].doubleValue;
}
if (ppi == 0) ppi = 326;
});
return ppi;
}
@end

View File

@@ -0,0 +1,71 @@
//
// UIScrollView+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIScrollView`.
*/
@interface UIScrollView (YYAdd)
/**
Scroll content to top with animation.
*/
- (void)scrollToTop;
/**
Scroll content to bottom with animation.
*/
- (void)scrollToBottom;
/**
Scroll content to left with animation.
*/
- (void)scrollToLeft;
/**
Scroll content to right with animation.
*/
- (void)scrollToRight;
/**
Scroll content to top.
@param animated Use animation.
*/
- (void)scrollToTopAnimated:(BOOL)animated;
/**
Scroll content to bottom.
@param animated Use animation.
*/
- (void)scrollToBottomAnimated:(BOOL)animated;
/**
Scroll content to left.
@param animated Use animation.
*/
- (void)scrollToLeftAnimated:(BOOL)animated;
/**
Scroll content to right.
@param animated Use animation.
*/
- (void)scrollToRightAnimated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,60 @@
//
// UIScrollView+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIScrollView+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(UIScrollView_YYAdd)
@implementation UIScrollView (YYAdd)
- (void)scrollToTop {
[self scrollToTopAnimated:YES];
}
- (void)scrollToBottom {
[self scrollToBottomAnimated:YES];
}
- (void)scrollToLeft {
[self scrollToLeftAnimated:YES];
}
- (void)scrollToRight {
[self scrollToRightAnimated:YES];
}
- (void)scrollToTopAnimated:(BOOL)animated {
CGPoint off = self.contentOffset;
off.y = 0 - self.contentInset.top;
[self setContentOffset:off animated:animated];
}
- (void)scrollToBottomAnimated:(BOOL)animated {
CGPoint off = self.contentOffset;
off.y = self.contentSize.height - self.bounds.size.height + self.contentInset.bottom;
[self setContentOffset:off animated:animated];
}
- (void)scrollToLeftAnimated:(BOOL)animated {
CGPoint off = self.contentOffset;
off.x = 0 - self.contentInset.left;
[self setContentOffset:off animated:animated];
}
- (void)scrollToRightAnimated:(BOOL)animated {
CGPoint off = self.contentOffset;
off.x = self.contentSize.width - self.bounds.size.width + self.contentInset.right;
[self setContentOffset:off animated:animated];
}
@end

View File

@@ -0,0 +1,188 @@
//
// UITableView+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/12.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UITableView`.
*/
@interface UITableView (YYAdd)
/**
Perform a series of method calls that insert, delete, or select rows and
sections of the receiver.
@discussion Perform a series of method calls that insert, delete, or select
rows and sections of the table. Call this method if you want
subsequent insertions, deletion, and selection operations (for
example, cellForRowAtIndexPath: and indexPathsForVisibleRows)
to be animated simultaneously.
@discussion If you do not make the insertion, deletion, and selection calls
inside this block, table attributes such as row count might become
invalid. You should not call reloadData within the block; if you
call this method within the group, you will need to perform any
animations yourself.
@param block A block combine a series of method calls.
*/
- (void)updateWithBlock:(void (^)(UITableView *tableView))block;
/**
Scrolls the receiver until a row or section location on the screen.
@discussion Invoking this method does not cause the delegate to
receive a scrollViewDidScroll: message, as is normal for
programmatically-invoked user interface operations.
@param row Row index in section. NSNotFound is a valid value for
scrolling to a section with zero rows.
@param section Section index in table.
@param scrollPosition A constant that identifies a relative position in the
receiving table view (top, middle, bottom) for row when
scrolling concludes.
@param animated YES if you want to animate the change in position,
NO if it should be immediate.
*/
- (void)scrollToRow:(NSUInteger)row inSection:(NSUInteger)section atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
/**
Inserts a row in the receiver with an option to animate the insertion.
@param row Row index in section.
@param section Section index in table.
@param animation A constant that either specifies the kind of animation to
perform when inserting the cell or requests no animation.
*/
- (void)insertRow:(NSUInteger)row inSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation;
/**
Reloads the specified row using a certain animation effect.
@param row Row index in section.
@param section Section index in table.
@param animation A constant that indicates how the reloading is to be animated,
for example, fade out or slide out from the bottom. The animation
constant affects the direction in which both the old and the
new rows slide. For example, if the animation constant is
UITableViewRowAnimationRight, the old rows slide out to the
right and the new cells slide in from the right.
*/
- (void)reloadRow:(NSUInteger)row inSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation;
/**
Deletes the row with an option to animate the deletion.
@param row Row index in section.
@param section Section index in table.
@param animation A constant that indicates how the deletion is to be animated,
for example, fade out or slide out from the bottom.
*/
- (void)deleteRow:(NSUInteger)row inSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation;
/**
Inserts the row in the receiver at the locations identified by the indexPath,
with an option to animate the insertion.
@param indexPath An NSIndexPath object representing a row index and section
index that together identify a row in the table view.
@param animation A constant that either specifies the kind of animation to
perform when inserting the cell or requests no animation.
*/
- (void)insertRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation;
/**
Reloads the specified row using a certain animation effect.
@param indexPath An NSIndexPath object representing a row index and section
index that together identify a row in the table view.
@param animation A constant that indicates how the reloading is to be animated,
for example, fade out or slide out from the bottom. The animation
constant affects the direction in which both the old and the
new rows slide. For example, if the animation constant is
UITableViewRowAnimationRight, the old rows slide out to the
right and the new cells slide in from the right.
*/
- (void)reloadRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation;
/**
Deletes the row specified by an array of index paths,
with an option to animate the deletion.
@param indexPath An NSIndexPath object representing a row index and section
index that together identify a row in the table view.
@param animation A constant that indicates how the deletion is to be animated,
for example, fade out or slide out from the bottom.
*/
- (void)deleteRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation;
/**
Inserts a section in the receiver, with an option to animate the insertion.
@param section An index specifies the section to insert in the receiving
table view. If a section already exists at the specified
index location, it is moved down one index location.
@param animation A constant that indicates how the insertion is to be animated,
for example, fade in or slide in from the left.
*/
- (void)insertSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation;
/**
Deletes a section in the receiver, with an option to animate the deletion.
@param section An index that specifies the sections to delete from the
receiving table view. If a section exists after the specified
index location, it is moved up one index location.
@param animation A constant that either specifies the kind of animation to
perform when deleting the section or requests no animation.
*/
- (void)deleteSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation;
/**
Reloads the specified section using a given animation effect.
@param section An index identifying the section to reload.
@param animation A constant that indicates how the reloading is to be animated,
for example, fade out or slide out from the bottom. The
animation constant affects the direction in which both the
old and the new section rows slide. For example, if the
animation constant is UITableViewRowAnimationRight, the old
rows slide out to the right and the new cells slide in from the right.
*/
- (void)reloadSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation;
/**
Unselect all rows in tableView.
@param animated YES to animate the transition, NO to make the transition immediate.
*/
- (void)clearSelectedRowsAnimated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,80 @@
//
// UITableView+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/12.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UITableView+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(UITableView_YYAdd)
@implementation UITableView (YYAdd)
- (void)updateWithBlock:(void (^)(UITableView *tableView))block {
[self beginUpdates];
block(self);
[self endUpdates];
}
- (void)scrollToRow:(NSUInteger)row inSection:(NSUInteger)section atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];
[self scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
}
- (void)insertRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation {
[self insertRowsAtIndexPaths:@[indexPath] withRowAnimation:animation];
}
- (void)insertRow:(NSUInteger)row inSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation {
NSIndexPath *toInsert = [NSIndexPath indexPathForRow:row inSection:section];
[self insertRowAtIndexPath:toInsert withRowAnimation:animation];
}
- (void)reloadRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation {
[self reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:animation];
}
- (void)reloadRow:(NSUInteger)row inSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation {
NSIndexPath *toReload = [NSIndexPath indexPathForRow:row inSection:section];
[self reloadRowAtIndexPath:toReload withRowAnimation:animation];
}
- (void)deleteRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation {
[self deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:animation];
}
- (void)deleteRow:(NSUInteger)row inSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation {
NSIndexPath *toDelete = [NSIndexPath indexPathForRow:row inSection:section];
[self deleteRowAtIndexPath:toDelete withRowAnimation:animation];
}
- (void)insertSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation {
NSIndexSet *sections = [NSIndexSet indexSetWithIndex:section];
[self insertSections:sections withRowAnimation:animation];
}
- (void)deleteSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation {
NSIndexSet *sections = [NSIndexSet indexSetWithIndex:section];
[self deleteSections:sections withRowAnimation:animation];
}
- (void)reloadSection:(NSUInteger)section withRowAnimation:(UITableViewRowAnimation)animation {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[self reloadSections:indexSet withRowAnimation:animation];
}
- (void)clearSelectedRowsAnimated:(BOOL)animated {
NSArray *indexs = [self indexPathsForSelectedRows];
[indexs enumerateObjectsUsingBlock:^(NSIndexPath* path, NSUInteger idx, BOOL *stop) {
[self deselectRowAtIndexPath:path animated:animated];
}];
}
@end

View File

@@ -0,0 +1,35 @@
//
// UITextField+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/12.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UITextField`.
*/
@interface UITextField (YYAdd)
/**
Set all text selected.
*/
- (void)selectAllText;
/**
Set text in range selected.
@param range The range of selected text in a document.
*/
- (void)setSelectedRange:(NSRange)range;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,33 @@
//
// UITextField+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/5/12.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UITextField+YYAdd.h"
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(UITextField_YYAdd)
@implementation UITextField (YYAdd)
- (void)selectAllText {
UITextRange *range = [self textRangeFromPosition:self.beginningOfDocument toPosition:self.endOfDocument];
[self setSelectedTextRange:range];
}
- (void)setSelectedRange:(NSRange)range {
UITextPosition *beginning = self.beginningOfDocument;
UITextPosition *startPosition = [self positionFromPosition:beginning offset:range.location];
UITextPosition *endPosition = [self positionFromPosition:beginning offset:NSMaxRange(range)];
UITextRange *selectionRange = [self textRangeFromPosition:startPosition toPosition:endPosition];
[self setSelectedTextRange:selectionRange];
}
@end

View File

@@ -0,0 +1,117 @@
//
// UIView+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provides extensions for `UIView`.
*/
@interface UIView (YYAdd)
/**
Create a snapshot image of the complete view hierarchy.
*/
- (nullable UIImage *)snapshotImage;
/**
Create a snapshot image of the complete view hierarchy.
@discussion It's faster than "snapshotImage", but may cause screen updates.
See -[UIView drawViewHierarchyInRect:afterScreenUpdates:] for more information.
*/
- (nullable UIImage *)snapshotImageAfterScreenUpdates:(BOOL)afterUpdates;
/**
Create a snapshot PDF of the complete view hierarchy.
*/
- (nullable NSData *)snapshotPDF;
/**
Shortcut to set the view.layer's shadow
@param color Shadow Color
@param offset Shadow offset
@param radius Shadow radius
*/
- (void)setLayerShadow:(nullable UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius;
/**
Remove all subviews.
@warning Never call this method inside your view's drawRect: method.
*/
- (void)removeAllSubviews;
/**
Returns the view's view controller (may be nil).
*/
@property (nullable, nonatomic, readonly) UIViewController *viewController;
/**
Returns the visible alpha on screen, taking into account superview and window.
*/
@property (nonatomic, readonly) CGFloat visibleAlpha;
/**
Converts a point from the receiver's coordinate system to that of the specified view or window.
@param point A point specified in the local coordinate system (bounds) of the receiver.
@param view The view or window into whose coordinate system point is to be converted.
If view is nil, this method instead converts to window base coordinates.
@return The point converted to the coordinate system of view.
*/
- (CGPoint)convertPoint:(CGPoint)point toViewOrWindow:(nullable UIView *)view;
/**
Converts a point from the coordinate system of a given view or window to that of the receiver.
@param point A point specified in the local coordinate system (bounds) of view.
@param view The view or window with point in its coordinate system.
If view is nil, this method instead converts from window base coordinates.
@return The point converted to the local coordinate system (bounds) of the receiver.
*/
- (CGPoint)convertPoint:(CGPoint)point fromViewOrWindow:(nullable UIView *)view;
/**
Converts a rectangle from the receiver's coordinate system to that of another view or window.
@param rect A rectangle specified in the local coordinate system (bounds) of the receiver.
@param view The view or window that is the target of the conversion operation. If view is nil, this method instead converts to window base coordinates.
@return The converted rectangle.
*/
- (CGRect)convertRect:(CGRect)rect toViewOrWindow:(nullable UIView *)view;
/**
Converts a rectangle from the coordinate system of another view or window to that of the receiver.
@param rect A rectangle specified in the local coordinate system (bounds) of view.
@param view The view or window with rect in its coordinate system.
If view is nil, this method instead converts from window base coordinates.
@return The converted rectangle.
*/
- (CGRect)convertRect:(CGRect)rect fromViewOrWindow:(nullable UIView *)view;
@property (nonatomic) CGFloat left; ///< Shortcut for frame.origin.x.
@property (nonatomic) CGFloat top; ///< Shortcut for frame.origin.y
@property (nonatomic) CGFloat right; ///< Shortcut for frame.origin.x + frame.size.width
@property (nonatomic) CGFloat bottom; ///< Shortcut for frame.origin.y + frame.size.height
@property (nonatomic) CGFloat width; ///< Shortcut for frame.size.width.
@property (nonatomic) CGFloat height; ///< Shortcut for frame.size.height.
@property (nonatomic) CGFloat centerX; ///< Shortcut for center.x
@property (nonatomic) CGFloat centerY; ///< Shortcut for center.y
@property (nonatomic) CGPoint origin; ///< Shortcut for frame.origin.
@property (nonatomic) CGSize size; ///< Shortcut for frame.size.
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,272 @@
//
// UIView+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIView+YYAdd.h"
#import <QuartzCore/QuartzCore.h>
#import "YYCategoriesMacro.h"
YYSYNTH_DUMMY_CLASS(UIView_YYAdd)
@implementation UIView (YYAdd)
- (UIImage *)snapshotImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *snap = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snap;
}
- (UIImage *)snapshotImageAfterScreenUpdates:(BOOL)afterUpdates {
if (![self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
return [self snapshotImage];
}
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:afterUpdates];
UIImage *snap = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snap;
}
- (NSData *)snapshotPDF {
CGRect bounds = self.bounds;
NSMutableData* data = [NSMutableData data];
CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData((__bridge CFMutableDataRef)data);
CGContextRef context = CGPDFContextCreate(consumer, &bounds, NULL);
CGDataConsumerRelease(consumer);
if (!context) return nil;
CGPDFContextBeginPage(context, NULL);
CGContextTranslateCTM(context, 0, bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
[self.layer renderInContext:context];
CGPDFContextEndPage(context);
CGPDFContextClose(context);
CGContextRelease(context);
return data;
}
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius {
self.layer.shadowColor = color.CGColor;
self.layer.shadowOffset = offset;
self.layer.shadowRadius = radius;
self.layer.shadowOpacity = 1;
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
}
- (void)removeAllSubviews {
//[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
while (self.subviews.count) {
[self.subviews.lastObject removeFromSuperview];
}
}
- (UIViewController *)viewController {
for (UIView *view = self; view; view = view.superview) {
UIResponder *nextResponder = [view nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)nextResponder;
}
}
return nil;
}
- (CGFloat)visibleAlpha {
if ([self isKindOfClass:[UIWindow class]]) {
if (self.hidden) return 0;
return self.alpha;
}
if (!self.window) return 0;
CGFloat alpha = 1;
UIView *v = self;
while (v) {
if (v.hidden) {
alpha = 0;
break;
}
alpha *= v.alpha;
v = v.superview;
}
return alpha;
}
- (CGPoint)convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view {
if (!view) {
if ([self isKindOfClass:[UIWindow class]]) {
return [((UIWindow *)self) convertPoint:point toWindow:nil];
} else {
return [self convertPoint:point toView:nil];
}
}
UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
if ((!from || !to) || (from == to)) return [self convertPoint:point toView:view];
point = [self convertPoint:point toView:from];
point = [to convertPoint:point fromWindow:from];
point = [view convertPoint:point fromView:to];
return point;
}
- (CGPoint)convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view {
if (!view) {
if ([self isKindOfClass:[UIWindow class]]) {
return [((UIWindow *)self) convertPoint:point fromWindow:nil];
} else {
return [self convertPoint:point fromView:nil];
}
}
UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
if ((!from || !to) || (from == to)) return [self convertPoint:point fromView:view];
point = [from convertPoint:point fromView:view];
point = [to convertPoint:point fromWindow:from];
point = [self convertPoint:point fromView:to];
return point;
}
- (CGRect)convertRect:(CGRect)rect toViewOrWindow:(UIView *)view {
if (!view) {
if ([self isKindOfClass:[UIWindow class]]) {
return [((UIWindow *)self) convertRect:rect toWindow:nil];
} else {
return [self convertRect:rect toView:nil];
}
}
UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
if (!from || !to) return [self convertRect:rect toView:view];
if (from == to) return [self convertRect:rect toView:view];
rect = [self convertRect:rect toView:from];
rect = [to convertRect:rect fromWindow:from];
rect = [view convertRect:rect fromView:to];
return rect;
}
- (CGRect)convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view {
if (!view) {
if ([self isKindOfClass:[UIWindow class]]) {
return [((UIWindow *)self) convertRect:rect fromWindow:nil];
} else {
return [self convertRect:rect fromView:nil];
}
}
UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
if ((!from || !to) || (from == to)) return [self convertRect:rect fromView:view];
rect = [from convertRect:rect fromView:view];
rect = [to convertRect:rect fromWindow:from];
rect = [self convertRect:rect fromView:to];
return rect;
}
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)top {
return self.frame.origin.y;
}
- (void)setTop:(CGFloat)y {
CGRect frame = self.frame;
frame.origin.y = y;
self.frame = frame;
}
- (CGFloat)right {
return self.frame.origin.x + self.frame.size.width;
}
- (void)setRight:(CGFloat)right {
CGRect frame = self.frame;
frame.origin.x = right - frame.size.width;
self.frame = frame;
}
- (CGFloat)bottom {
return self.frame.origin.y + self.frame.size.height;
}
- (void)setBottom:(CGFloat)bottom {
CGRect frame = self.frame;
frame.origin.y = bottom - frame.size.height;
self.frame = frame;
}
- (CGFloat)width {
return self.frame.size.width;
}
- (void)setWidth:(CGFloat)width {
CGRect frame = self.frame;
frame.size.width = width;
self.frame = frame;
}
- (CGFloat)height {
return self.frame.size.height;
}
- (void)setHeight:(CGFloat)height {
CGRect frame = self.frame;
frame.size.height = height;
self.frame = frame;
}
- (CGFloat)centerX {
return self.center.x;
}
- (void)setCenterX:(CGFloat)centerX {
self.center = CGPointMake(centerX, self.center.y);
}
- (CGFloat)centerY {
return self.center.y;
}
- (void)setCenterY:(CGFloat)centerY {
self.center = CGPointMake(self.center.x, centerY);
}
- (CGPoint)origin {
return self.frame.origin;
}
- (void)setOrigin:(CGPoint)origin {
CGRect frame = self.frame;
frame.origin = origin;
self.frame = frame;
}
- (CGSize)size {
return self.frame.size;
}
- (void)setSize:(CGSize)size {
CGRect frame = self.frame;
frame.size = size;
self.frame = frame;
}
@end

View File

@@ -0,0 +1,81 @@
//
// YYCategories.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/3/29.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYCategories/YYCategories.h>)
FOUNDATION_EXPORT double YYCategoriesVersionNumber;
FOUNDATION_EXPORT const unsigned char YYCategoriesVersionString[];
#import <YYCategories/YYCategoriesMacro.h>
#import <YYCategories/NSObject+YYAdd.h>
#import <YYCategories/NSObject+YYAddForKVO.h>
#import <YYCategories/NSObject+YYAddForARC.h>
#import <YYCategories/NSData+YYAdd.h>
#import <YYCategories/NSString+YYAdd.h>
#import <YYCategories/NSArray+YYAdd.h>
#import <YYCategories/NSDictionary+YYAdd.h>
#import <YYCategories/NSDate+YYAdd.h>
#import <YYCategories/NSNumber+YYAdd.h>
#import <YYCategories/NSNotificationCenter+YYAdd.h>
#import <YYCategories/NSKeyedUnarchiver+YYAdd.h>
#import <YYCategories/NSTimer+YYAdd.h>
#import <YYCategories/NSBundle+YYAdd.h>
#import <YYCategories/NSThread+YYAdd.h>
#import <YYCategories/UIColor+YYAdd.h>
#import <YYCategories/UIImage+YYAdd.h>
#import <YYCategories/UIControl+YYAdd.h>
#import <YYCategories/UIBarButtonItem+YYAdd.h>
#import <YYCategories/UIGestureRecognizer+YYAdd.h>
#import <YYCategories/UIView+YYAdd.h>
#import <YYCategories/UIScrollView+YYAdd.h>
#import <YYCategories/UITableView+YYAdd.h>
#import <YYCategories/UITextField+YYAdd.h>
#import <YYCategories/UIScreen+YYAdd.h>
#import <YYCategories/UIDevice+YYAdd.h>
#import <YYCategories/UIApplication+YYAdd.h>
#import <YYCategories/UIFont+YYAdd.h>
#import <YYCategories/UIBezierPath+YYAdd.h>
#import <YYCategories/CALayer+YYAdd.h>
#import <YYCategories/YYCGUtilities.h>
#else
#import "YYCategoriesMacro.h"
#import "NSObject+YYAdd.h"
#import "NSObject+YYAddForKVO.h"
#import "NSObject+YYAddForARC.h"
#import "NSData+YYAdd.h"
#import "NSString+YYAdd.h"
#import "NSArray+YYAdd.h"
#import "NSDictionary+YYAdd.h"
#import "NSDate+YYAdd.h"
#import "NSNumber+YYAdd.h"
#import "NSNotificationCenter+YYAdd.h"
#import "NSKeyedUnarchiver+YYAdd.h"
#import "NSTimer+YYAdd.h"
#import "NSBundle+YYAdd.h"
#import "NSThread+YYAdd.h"
#import "UIColor+YYAdd.h"
#import "UIImage+YYAdd.h"
#import "UIControl+YYAdd.h"
#import "UIBarButtonItem+YYAdd.h"
#import "UIGestureRecognizer+YYAdd.h"
#import "UIView+YYAdd.h"
#import "UIScrollView+YYAdd.h"
#import "UITableView+YYAdd.h"
#import "UITextField+YYAdd.h"
#import "UIScreen+YYAdd.h"
#import "UIDevice+YYAdd.h"
#import "UIApplication+YYAdd.h"
#import "UIFont+YYAdd.h"
#import "UIBezierPath+YYAdd.h"
#import "CALayer+YYAdd.h"
#import "YYCGUtilities.h"
#endif

View File

@@ -0,0 +1,337 @@
//
// YYCategoriesMacro.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/3/29.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <sys/time.h>
#import <pthread.h>
#ifndef YYCategoriesMacro_h
#define YYCategoriesMacro_h
#ifdef __cplusplus
#define YY_EXTERN_C_BEGIN extern "C" {
#define YY_EXTERN_C_END }
#else
#define YY_EXTERN_C_BEGIN
#define YY_EXTERN_C_END
#endif
YY_EXTERN_C_BEGIN
#ifndef YY_CLAMP // return the clamped value
#define YY_CLAMP(_x_, _low_, _high_) (((_x_) > (_high_)) ? (_high_) : (((_x_) < (_low_)) ? (_low_) : (_x_)))
#endif
#ifndef YY_SWAP // swap two value
#define YY_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0)
#endif
#define YYAssertNil(condition, description, ...) NSAssert(!(condition), (description), ##__VA_ARGS__)
#define YYCAssertNil(condition, description, ...) NSCAssert(!(condition), (description), ##__VA_ARGS__)
#define YYAssertNotNil(condition, description, ...) NSAssert((condition), (description), ##__VA_ARGS__)
#define YYCAssertNotNil(condition, description, ...) NSCAssert((condition), (description), ##__VA_ARGS__)
#define YYAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread")
#define YYCAssertMainThread() NSCAssert([NSThread isMainThread], @"This method must be called on the main thread")
/**
Add this macro before each category implementation, so we don't have to use
-all_load or -force_load to load object files from static libraries that only
contain categories and no classes.
More info: http://developer.apple.com/library/mac/#qa/qa2006/qa1490.html .
*******************************************************************************
Example:
YYSYNTH_DUMMY_CLASS(NSString_YYAdd)
*/
#ifndef YYSYNTH_DUMMY_CLASS
#define YYSYNTH_DUMMY_CLASS(_name_) \
@interface YYSYNTH_DUMMY_CLASS_ ## _name_ : NSObject @end \
@implementation YYSYNTH_DUMMY_CLASS_ ## _name_ @end
#endif
/**
Synthsize a dynamic object property in @implementation scope.
It allows us to add custom properties to existing classes in categories.
@param association ASSIGN / RETAIN / COPY / RETAIN_NONATOMIC / COPY_NONATOMIC
@warning #import <objc/runtime.h>
*******************************************************************************
Example:
@interface NSObject (MyAdd)
@property (nonatomic, retain) UIColor *myColor;
@end
#import <objc/runtime.h>
@implementation NSObject (MyAdd)
YYSYNTH_DYNAMIC_PROPERTY_OBJECT(myColor, setMyColor, RETAIN, UIColor *)
@end
*/
#ifndef YYSYNTH_DYNAMIC_PROPERTY_OBJECT
#define YYSYNTH_DYNAMIC_PROPERTY_OBJECT(_getter_, _setter_, _association_, _type_) \
- (void)_setter_ : (_type_)object { \
[self willChangeValueForKey:@#_getter_]; \
objc_setAssociatedObject(self, _cmd, object, OBJC_ASSOCIATION_ ## _association_); \
[self didChangeValueForKey:@#_getter_]; \
} \
- (_type_)_getter_ { \
return objc_getAssociatedObject(self, @selector(_setter_:)); \
}
#endif
/**
Synthsize a dynamic c type property in @implementation scope.
It allows us to add custom properties to existing classes in categories.
@warning #import <objc/runtime.h>
*******************************************************************************
Example:
@interface NSObject (MyAdd)
@property (nonatomic, retain) CGPoint myPoint;
@end
#import <objc/runtime.h>
@implementation NSObject (MyAdd)
YYSYNTH_DYNAMIC_PROPERTY_CTYPE(myPoint, setMyPoint, CGPoint)
@end
*/
#ifndef YYSYNTH_DYNAMIC_PROPERTY_CTYPE
#define YYSYNTH_DYNAMIC_PROPERTY_CTYPE(_getter_, _setter_, _type_) \
- (void)_setter_ : (_type_)object { \
[self willChangeValueForKey:@#_getter_]; \
NSValue *value = [NSValue value:&object withObjCType:@encode(_type_)]; \
objc_setAssociatedObject(self, _cmd, value, OBJC_ASSOCIATION_RETAIN); \
[self didChangeValueForKey:@#_getter_]; \
} \
- (type)_getter_ { \
_type_ cValue = { 0 }; \
NSValue *value = objc_getAssociatedObject(self, @selector(_setter_:)); \
[value getValue:&cValue]; \
return cValue; \
}
#endif
/**
Synthsize a weak or strong reference.
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
/**
Convert CFRange to NSRange
@param range CFRange @return NSRange
*/
static inline NSRange YYNSRangeFromCFRange(CFRange range) {
return NSMakeRange(range.location, range.length);
}
/**
Convert NSRange to CFRange
@param range NSRange @return CFRange
*/
static inline CFRange YYCFRangeFromNSRange(NSRange range) {
return CFRangeMake(range.location, range.length);
}
/**
Same as CFAutorelease(), compatible for iOS6
@param arg CFObject @return same as input
*/
static inline CFTypeRef YYCFAutorelease(CFTypeRef CF_RELEASES_ARGUMENT arg) {
if (((long)CFAutorelease + 1) != 1) {
return CFAutorelease(arg);
} else {
id __autoreleasing obj = CFBridgingRelease(arg);
return (__bridge CFTypeRef)obj;
}
}
/**
Profile time cost.
@param ^block code to benchmark
@param ^complete code time cost (millisecond)
Usage:
YYBenchmark(^{
// code
}, ^(double ms) {
NSLog("time cost: %.2f ms",ms);
});
*/
static inline void YYBenchmark(void (^block)(void), void (^complete)(double ms)) {
// <QuartzCore/QuartzCore.h> version
/*
extern double CACurrentMediaTime (void);
double begin, end, ms;
begin = CACurrentMediaTime();
block();
end = CACurrentMediaTime();
ms = (end - begin) * 1000.0;
complete(ms);
*/
// <sys/time.h> version
struct timeval t0, t1;
gettimeofday(&t0, NULL);
block();
gettimeofday(&t1, NULL);
double ms = (double)(t1.tv_sec - t0.tv_sec) * 1e3 + (double)(t1.tv_usec - t0.tv_usec) * 1e-3;
complete(ms);
}
static inline NSDate *_YYCompileTime(const char *data, const char *time) {
NSString *timeStr = [NSString stringWithFormat:@"%s %s",data,time];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"MMM dd yyyy HH:mm:ss"];
[formatter setLocale:locale];
return [formatter dateFromString:timeStr];
}
/**
Get compile timestamp.
@return A new date object set to the compile date and time.
*/
#ifndef YYCompileTime
// use macro to avoid compile warning when use pch file
#define YYCompileTime() _YYCompileTime(__DATE__, __TIME__)
#endif
/**
Returns a dispatch_time delay from now.
*/
static inline dispatch_time_t dispatch_time_delay(NSTimeInterval second) {
return dispatch_time(DISPATCH_TIME_NOW, (int64_t)(second * NSEC_PER_SEC));
}
/**
Returns a dispatch_wall_time delay from now.
*/
static inline dispatch_time_t dispatch_walltime_delay(NSTimeInterval second) {
return dispatch_walltime(DISPATCH_TIME_NOW, (int64_t)(second * NSEC_PER_SEC));
}
/**
Returns a dispatch_wall_time from NSDate.
*/
static inline dispatch_time_t dispatch_walltime_date(NSDate *date) {
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time, 0);
return milestone;
}
/**
Whether in main queue/thread.
*/
static inline bool dispatch_is_main_queue() {
return pthread_main_np() != 0;
}
/**
Submits a block for asynchronous execution on a main queue and returns immediately.
*/
static inline void dispatch_async_on_main_queue(void (^block)()) {
if (pthread_main_np()) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
/**
Submits a block for execution on a main queue and waits until the block completes.
*/
static inline void dispatch_sync_on_main_queue(void (^block)()) {
if (pthread_main_np()) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
/**
Initialize a pthread mutex.
*/
static inline void pthread_mutex_init_recursive(pthread_mutex_t *mutex, bool recursive) {
#define YYMUTEX_ASSERT_ON_ERROR(x_) do { \
__unused volatile int res = (x_); \
assert(res == 0); \
} while (0)
assert(mutex != NULL);
if (!recursive) {
YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, NULL));
} else {
pthread_mutexattr_t attr;
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init (mutex, &attr));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));
}
#undef YYMUTEX_ASSERT_ON_ERROR
}
YY_EXTERN_C_END
#endif