Last active
January 29, 2020 03:11
-
-
Save erkanyildiz/bac85bd1fa07c44603fd to your computer and use it in GitHub Desktop.
Utilities for Cocoa
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// erkanyildiz | |
// 20191228-2138+0900 | |
// | |
// EYUtils.h | |
#import <Foundation/Foundation.h> | |
#import <UIKit/UIKit.h> | |
#ifndef DEBUG_MODE | |
#define DEBUG_MODE 1 | |
#endif | |
#if DEBUG_MODE | |
#define DLOG(...) NSLog(__VA_ARGS__) | |
#else | |
#define DLOG(...) | |
#endif | |
#define MLOG DLOG(@"%s",__FUNCTION__) | |
#pragma mark --- | |
#define SCREEN_W (MIN(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height)) | |
#define SCREEN_H (MAX(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height)) | |
#define IS_IPAD (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) | |
#define IS_1024 (IS_IPAD && SCREEN_H == 1024) | |
#define IS_1080 (IS_IPAD && SCREEN_H == 1080) | |
#define IS_1112 (IS_IPAD && SCREEN_H == 1112) | |
#define IS_1366 (IS_IPAD && SCREEN_H == 1366) | |
#define IS_IPHONE (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) | |
#define IS_480 (IS_IPHONE && SCREEN_H == 480) | |
#define IS_568 (IS_IPHONE && SCREEN_H == 568) | |
#define IS_667 (IS_IPHONE && SCREEN_H == 667) | |
#define IS_736 (IS_IPHONE && SCREEN_H == 736) | |
#define IS_812 (IS_IPHONE && SCREEN_H == 812) | |
#define IS_896 (IS_IPHONE && SCREEN_H == 896) | |
#define IS_W320 (IS_IPHONE && SCREEN_W == 320) | |
#define IS_W375 (IS_IPHONE && SCREEN_W == 375) | |
#define IS_W414 (IS_IPHONE && SCREEN_W == 414) | |
#define VFH(a,b,c,d,e,f) (IS_480?(a):IS_568?(b):IS_667?(c):IS_736?(d):IS_812?(e):IS_896?(f):(f)) | |
#define VFW(a,b,c) (IS_W320?(a):IS_W375?(b):IS_W414?(c):(c)) | |
#pragma mark --- | |
#define IS_IOS8 ([UIDevice.currentDevice.systemVersion hasPrefix:@"8"]) | |
#define IS_IOS9 ([UIDevice.currentDevice.systemVersion hasPrefix:@"9"]) | |
#define IS_IOS10 ([UIDevice.currentDevice.systemVersion hasPrefix:@"10"]) | |
#define IS_IOS11 ([UIDevice.currentDevice.systemVersion hasPrefix:@"11"]) | |
#define IS_IOS12 ([UIDevice.currentDevice.systemVersion hasPrefix:@"12"]) | |
#define IS_IOS13 ([UIDevice.currentDevice.systemVersion hasPrefix:@"13"]) | |
#pragma mark --- | |
#define IMG(n) [UIImage imageNamed:(n)] | |
#define FONT(n,s) [UIFont fontWithName:(n) size:(s)] | |
#define RGB(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1] | |
#define RGBA(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)] | |
#define RGBW(w, a) [UIColor colorWithWhite:(w)/255.0 alpha:(a)] | |
#define NSUD NSUserDefaults.standardUserDefaults | |
#define LS(str) NSLocalizedString(str, nil) | |
#define FS(...) [NSString stringWithFormat:__VA_ARGS__] | |
#pragma mark --- | |
#define D2R(angle) ((angle) * M_PI / 180.0) | |
#define R2D(angle) ((angle) * 180.0 / M_PI) | |
#define RATIO_16_9 (16.0 / 9.0) | |
#pragma mark - | |
void afterDelay(NSTimeInterval delay, void(^block)(void)); | |
void onMainThread(void(^block)(void)); | |
#pragma mark - | |
@interface EYUtils : NSObject | |
+ (instancetype)sharedInstance; | |
+ (void)setShouldUseLightActivityIndicatorStyle:(BOOL)shouldUseLightActivityIndicatorStyle; | |
@end | |
#pragma mark - | |
@interface NSObject (EYUtils) | |
- (void)dump; | |
- (void)dumpWith:(NSString *)logText; | |
@end | |
#pragma mark - | |
@interface NSString (EYUtils) | |
+ (NSString *)randomStringWithLength:(NSUInteger)length; | |
- (NSString *)stringByDeletingNewLines; | |
#pragma mark Validation | |
- (BOOL)isValidEMail; | |
#pragma mark Encryption | Hashing | |
- (NSString *)stringByEncryptingWithKey:(NSString *)key; | |
- (NSString *)stringByDecryptingWithKey:(NSString *)key; | |
- (NSString *)SHA256; | |
- (NSString *)MD5; | |
#pragma mark Network Operations | |
- (NSURL *)URL; | |
- (NSURLRequest *)request; | |
@end | |
#pragma mark - | |
@interface NSURLRequest (EYUtils) | |
- (void)fetchJSON:(void (^)(id JSONResponse, NSError* error))handler; | |
- (void)fetchImage:(void (^)(UIImage* image, NSError* error))handler; | |
- (void)fetchData:(void (^)(NSData* data, NSError* error))handler; | |
- (NSString *)cURLCommand; | |
@end | |
#pragma mark - | |
@interface NSURL (EYUtils) | |
- (NSURL *)URLByAddingQueryParameters:(NSDictionary *)parameters; | |
- (NSString *)valueForQueryParameter:(NSString *)parameter; | |
@end | |
#pragma mark - | |
@interface NSArray (EYUtils) | |
- (NSString *)JSON; | |
@end | |
#pragma mark - | |
@interface NSDictionary (EYUtils) | |
- (NSString *)JSON; | |
@end | |
#pragma mark - | |
@interface NSMutableData (EYUtils) | |
- (void)appendString:(NSString *)string; | |
@end | |
#pragma mark - | |
@interface UIColor (EYUtils) | |
- (UIColor *)colorByMixingWithColor:(UIColor *)color atRatio:(CGFloat)ratio; | |
+ (UIColor *)colorFromHexString:(NSString *)hexString; | |
@end | |
#pragma mark - | |
@interface UIImage (EYUtils) | |
- (UIImage *)imageByCombiningWithImage:(UIImage *)upperImage; | |
+ (UIImage *)imageWithColor:(UIColor *)color andSize:(CGSize)size; | |
@end | |
#pragma mark - | |
@interface UIApplication (EYUtils) | |
+ (NSString *)displayName; | |
+ (NSString *)version; | |
+ (NSString *)build; | |
@end | |
#pragma mark - | |
@interface UIWindow (EYUtils) | |
+ (void)alert:(NSString *)message; | |
@end | |
#pragma mark - | |
@interface UIViewController (EYUtils) | |
+ (instancetype)createFromInterfaceFile; | |
+ (instancetype)createFromInterfaceFileWithName:(NSString *)interfaceFileName; | |
- (UIViewController *)previousViewControllerOnStack; | |
- (BOOL)isFirstAppearance; | |
#pragma mark Activity | |
- (void)showActivityIndicator; | |
- (void)hideActivityIndicator; | |
#pragma mark Alert | |
- (void)alert:(NSString *)message; | |
- (void)alert:(NSString *)message title:(NSString *)title; | |
- (void)alert:(NSString *)message dismissText:(NSString *)dismissText; | |
- (void)alert:(NSString *)message title:(NSString *)title dismissText:(NSString *)dismissText; | |
- (void)alert:(NSString *)message title:(NSString *)title dismissText:(NSString *)dismissText completion:(void(^)(UIAlertController* alertController))completion; | |
@end | |
#pragma mark - | |
@interface UIButton (EYUtils) | |
@property (nonatomic, copy) void (^onClick)(id sender); | |
@end | |
#pragma mark - | |
@interface UIScrollView (EYUtils) | |
- (BOOL)isAtTheBottom; | |
#if TARGET_OS_IOS | |
- (void)setupAutoKeyboardAdjustment; | |
#endif | |
@end | |
#pragma mark - | |
@interface UITextField (EYUtils) | |
- (void)addLeftPadding:(CGFloat)padding; | |
@end | |
#pragma mark - | |
@interface UILabel (EYUtils) | |
- (void)setLineSpacing:(CGFloat)lineSpacing andKerning:(CGFloat)kerning; | |
- (NSInteger)lineCount; | |
@end | |
#pragma mark - | |
@interface UITableView (EYUtils) | |
- (void)updateCellHeightsWithBufferDelay; | |
@end | |
#pragma mark - | |
@interface UIView (EYUtils) | |
#pragma mark Positioning | |
- (void)moveRight:(CGFloat)value; | |
- (void)moveLeft:(CGFloat)value; | |
- (void)moveUp:(CGFloat)value; | |
- (void)moveDown:(CGFloat)value; | |
- (void)centerInSuperView; | |
- (void)centerRightInSuperView; | |
- (void)centerLeftInSuperView; | |
- (void)centerTopInSuperView; | |
- (void)centerBottomInSuperView; | |
- (void)stickToRightInSuperView; | |
- (void)stickToLeftInSuperView; | |
- (void)stickToTopInSuperView; | |
- (void)stickToBottomInSuperView; | |
- (void)stickToBottomOfView:(UIView *)targetView; | |
- (void)stickToBottomOfView:(UIView *)targetView withMargin:(CGFloat)margin; | |
- (void)stickToRightOfView:(UIView *)targetView; | |
- (void)stickToRightOfView:(UIView *)targetView withMargin:(CGFloat)margin; | |
- (void)setCenterX:(CGFloat)value; | |
- (void)setCenterY:(CGFloat)value; | |
- (void)setOriginX:(CGFloat)value; | |
- (void)setOriginY:(CGFloat)value; | |
- (void)setOrigin:(CGPoint)origin; | |
#pragma mark Resizing | |
- (void)setHeight:(CGFloat)value; | |
- (void)setWidth:(CGFloat)value; | |
- (void)expandRight:(CGFloat)value; | |
- (void)expandLeft:(CGFloat)value; | |
- (void)expandUp:(CGFloat)value; | |
- (void)expandDown:(CGFloat)value; | |
#pragma mark Border & Shadow | |
//NOTE: Borders will be drawn using CALayer's, thus do not support auto resizing. | |
- (void)addBottomBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth; | |
- (void)addLeftBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth; | |
- (void)addRightBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth; | |
- (void)addTopBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth; | |
- (void)addBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth; | |
- (void)addDropShadow; | |
- (void)roundCorners; | |
- (void)roundCornersWithRadius:(CGFloat)radius; | |
#pragma mark Animated Positioning | |
- (void)moveAnimatedRight:(CGFloat)value; | |
- (void)moveAnimatedRight:(CGFloat)value duration:(NSTimeInterval)duration; | |
- (void)moveAnimatedLeft:(CGFloat)value; | |
- (void)moveAnimatedLeft:(CGFloat)value duration:(NSTimeInterval)duration; | |
- (void)moveAnimatedUp:(CGFloat)value; | |
- (void)moveAnimatedUp:(CGFloat)value duration:(NSTimeInterval)duration; | |
- (void)moveAnimatedDown:(CGFloat)value; | |
- (void)moveAnimatedDown:(CGFloat)value duration:(NSTimeInterval)duration; | |
- (void)centerAnimatedInSuperView; | |
- (void)centerAnimatedInSuperViewWithDuration:(NSTimeInterval)duration; | |
#pragma mark Fading | |
- (void)fadeIn; | |
- (void)fadeInWithDuration:(NSTimeInterval)duration; | |
- (void)fadeOut; | |
- (void)fadeOutWithDuration:(NSTimeInterval)duration; | |
#pragma mark Animated Resizing | |
- (void)scaleTo:(CGFloat)ratio; | |
- (void)scaleTo:(CGFloat)ratio duration:(NSTimeInterval)duration; | |
- (void)shrink; | |
- (void)shrinkWithDuration:(NSTimeInterval)duration; | |
- (void)unshrink; | |
- (void)unshrinkWithDuration:(NSTimeInterval)duration; | |
#pragma mark Image Rendering | |
- (UIImage *)renderedImage; | |
- (BOOL)saveRenderedImageToDocuments; | |
#pragma mark Activity | |
- (void)showActivityIndicator; | |
- (void)hideActivityIndicator; | |
#pragma mark Shimmering | |
- (void)startShimmering; | |
- (void)stopShimmering; | |
#pragma mark Creating | |
+ (instancetype)createFromXIB; | |
#pragma mark Debugging | |
- (void)dumpFrame:(NSString *)comment; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// erkanyildiz | |
// 20191228-2138+0900 | |
// | |
// EYUtils.m | |
#import "EYUtils.h" | |
#import <objc/runtime.h> | |
#include <CommonCrypto/CommonDigest.h> | |
#pragma mark - EYUtils Class Core | |
@interface EYUtils () | |
@property (nonatomic) BOOL shouldUseLightActivityIndicatorStyle; | |
@property (nonatomic) UIWindow* windowsForFloatingAlerts; | |
@property (nonatomic) NSInteger countForFloatingAlerts; | |
@end | |
@implementation EYUtils | |
+ (instancetype)sharedInstance | |
{ | |
static id s_sharedInstance = nil; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ s_sharedInstance = self.new; }); | |
return s_sharedInstance; | |
} | |
+ (void)setShouldUseLightActivityIndicatorStyle:(BOOL)shouldUseLightActivityIndicatorStyle | |
{ | |
EYUtils.sharedInstance.shouldUseLightActivityIndicatorStyle = shouldUseLightActivityIndicatorStyle; | |
} | |
@end | |
#pragma mark - Helper Functions | |
#pragma mark - | |
NSString* JSONFromObject(id object) | |
{ | |
if ([NSJSONSerialization isValidJSONObject:object]) | |
{ | |
NSError* error = nil; | |
NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error]; | |
if (error) { NSLog(@"NSJSONSerialization internal error:\n%@", error); } | |
return [NSString.alloc initWithData:data encoding:NSUTF8StringEncoding]; | |
} | |
return nil; | |
} | |
void afterDelay(NSTimeInterval delay, void(^block)(void)) | |
{ | |
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), block); | |
} | |
void onMainThread(void(^block)(void)) | |
{ | |
if (NSThread.isMainThread) | |
block(); | |
else | |
dispatch_async(dispatch_get_main_queue(), block); | |
} | |
#pragma mark - Helper Subclasses | |
#pragma mark - | |
@interface EYUtilsAlertController : UIAlertController | |
@end | |
@implementation EYUtilsAlertController | |
#if TARGET_OS_IOS | |
- (BOOL)shouldAutorotate | |
{ | |
return [self.presentingViewController shouldAutorotate]; | |
} | |
- (BOOL)prefersStatusBarHidden | |
{ | |
return [self.presentingViewController prefersStatusBarHidden]; | |
} | |
- (BOOL)prefersHomeIndicatorAutoHidden | |
{ | |
return [self.presentingViewController prefersHomeIndicatorAutoHidden]; | |
} | |
- (UIStatusBarStyle)preferredStatusBarStyle | |
{ | |
return [self.presentingViewController preferredStatusBarStyle]; | |
} | |
#endif | |
@end | |
#pragma mark - | |
@interface EYUtilsActivityContainer : UIVisualEffectView | |
@property (nonatomic) NSInteger count; | |
@end | |
@implementation EYUtilsActivityContainer | |
- (instancetype)initWithFrame:(CGRect)frame | |
{ | |
self = [super initWithFrame:frame]; | |
if (self) | |
{ | |
BOOL isLight = EYUtils.sharedInstance.shouldUseLightActivityIndicatorStyle; | |
self.count = 1; | |
self.userInteractionEnabled = NO; | |
self.effect = [UIBlurEffect effectWithStyle:isLight ? UIBlurEffectStyleExtraLight : UIBlurEffectStyleDark]; | |
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | |
UIActivityIndicatorView* aiv = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; | |
aiv.color = isLight ? UIColor.blackColor : nil; | |
aiv.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; | |
[self.contentView addSubview:aiv]; | |
[aiv centerInSuperView]; | |
[aiv startAnimating]; | |
} | |
return self; | |
} | |
@end | |
#pragma mark - Categories | |
#pragma mark - | |
@implementation NSObject (EYUtils) | |
- (void)dump | |
{ | |
NSLog(@"%@", [self description]); | |
} | |
- (void)dumpWith:(NSString *)logText | |
{ | |
NSLog(@"%@: %@", logText, [self description]); | |
} | |
@end | |
#pragma mark - | |
@implementation NSString (EYUtils) | |
+ (NSString *)randomStringWithLength:(NSUInteger)length | |
{ | |
NSMutableString* random = [NSMutableString stringWithCapacity:length]; | |
for (NSUInteger i=0; i<length; i++) | |
{ | |
char c = '0' + (unichar)arc4random()%36; | |
if(c > '9') c += ('a'-'9'-1); | |
[random appendFormat:@"%c", c]; | |
} | |
return random; | |
} | |
- (NSString *)stringByDeletingNewLines | |
{ | |
NSString* lineFeedCleared = [self stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; | |
NSString* carriageReturnCleared = [lineFeedCleared stringByReplacingOccurrencesOfString:@"\r" withString:@" "]; | |
return carriageReturnCleared; | |
} | |
#pragma mark Validation | |
- (BOOL)isValidEMail | |
{ | |
NSString* emailRegex = @"^[a-zA-Z0-9!'#$%&*+\\/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!'#$%&*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$"; | |
NSRegularExpression* regExp = [NSRegularExpression regularExpressionWithPattern:emailRegex options:0 error:nil]; | |
NSUInteger numberOfMatches = [regExp numberOfMatchesInString:self options:0 range:(NSRange){0, self.length}]; | |
if (numberOfMatches == 0) | |
return NO; | |
return YES; | |
} | |
#pragma mark Encryption | |
- (NSString *)stringByEncryptingWithKey:(NSString *)key | |
{ | |
NSData* rawData = [self dataUsingEncoding:NSUTF8StringEncoding]; | |
const char* rawBytes = rawData.bytes; | |
NSUInteger keyLength = key.length; | |
NSMutableData* encData = NSMutableData.data; | |
for (int i = 0; i < rawData.length; i++) | |
{ | |
NSUInteger keyIndex = i % keyLength; | |
char encChar = rawBytes[i] ^ [key characterAtIndex:keyIndex]; | |
[encData appendBytes:&encChar length:1]; | |
} | |
return [encData base64EncodedStringWithOptions:0]; | |
} | |
- (NSString *)stringByDecryptingWithKey:(NSString *)key | |
{ | |
NSData* encData = [NSData.alloc initWithBase64EncodedString:self options:0]; | |
const char* encBytes = encData.bytes; | |
NSUInteger keyLength = key.length; | |
NSMutableData* rawData = NSMutableData.data; | |
for (int i = 0; i < encData.length; i++) | |
{ | |
NSUInteger keyIndex = i % keyLength; | |
char encChar = encBytes[i] ^ [key characterAtIndex:keyIndex]; | |
[rawData appendBytes:&encChar length:1]; | |
} | |
return [NSString.alloc initWithData:rawData encoding:NSUTF8StringEncoding]; | |
} | |
#pragma mark Hashing | |
- (NSString *)SHA256 | |
{ | |
const char* s = [self UTF8String]; | |
unsigned char digest[CC_SHA256_DIGEST_LENGTH]; | |
CC_SHA256(s, (CC_LONG)strlen(s), digest); | |
NSMutableString *hash = NSMutableString.new; | |
for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) | |
[hash appendFormat:@"%02x", digest[i]]; | |
return hash; | |
} | |
- (NSString *)MD5 | |
{ | |
const char *s = [self UTF8String]; | |
unsigned char digest[CC_MD5_DIGEST_LENGTH]; | |
CC_MD5(s, (CC_LONG)strlen(s), digest); | |
NSMutableString *hash = NSMutableString.new; | |
for (int i = 0; i < 16; i++) | |
[hash appendFormat:@"%02x", digest[i]]; | |
return hash; | |
} | |
#pragma mark Network Operations | |
- (NSURL *)URL | |
{ | |
return [NSURL URLWithString:self]; | |
} | |
- (NSURLRequest *)request | |
{ | |
return [NSURLRequest requestWithURL:[self URL]]; | |
} | |
@end | |
#pragma mark - | |
@implementation NSURLRequest (EYUtils) | |
- (void)fetchJSON:(void (^)(id JSONResponse, NSError* error))handler | |
{ | |
NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:self completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) | |
{ | |
if(error) | |
{ | |
NSMutableDictionary* userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; | |
userInfo[@"response"] = response; | |
NSError* errorWithResponse = [NSError.alloc initWithDomain:error.domain code:error.code userInfo:userInfo]; | |
onMainThread(^ | |
{ | |
handler(nil, errorWithResponse); | |
}); | |
} | |
else | |
{ | |
NSError* jsonError = nil; | |
id JSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; | |
onMainThread(^ | |
{ | |
if(jsonError) | |
{ | |
handler(nil, jsonError); | |
} | |
else | |
{ | |
handler(JSON, nil); | |
} | |
}); | |
} | |
}]; | |
[task resume]; | |
} | |
- (void)fetchImage:(void (^)(UIImage* image, NSError* error))handler | |
{ | |
NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:self completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) | |
{ | |
if(error) | |
{ | |
NSMutableDictionary* userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; | |
userInfo[@"response"] = response; | |
NSError* errorWithResponse = [NSError.alloc initWithDomain:error.domain code:error.code userInfo:userInfo]; | |
onMainThread(^ | |
{ | |
handler(nil, errorWithResponse); | |
}); | |
} | |
else | |
{ | |
UIImage* image = [UIImage imageWithData:data]; | |
onMainThread(^ | |
{ | |
if(image) | |
{ | |
handler(image, nil); | |
} | |
else | |
{ | |
handler(nil, [NSError errorWithDomain:@"EYUtilsRequestImageError" code:1000 userInfo:@{@"Description:":@"Invalid image data"}]); | |
} | |
}); | |
} | |
}]; | |
[task resume]; | |
} | |
- (void)fetchData:(void (^)(NSData* data, NSError* error))handler | |
{ | |
NSURLSessionTask* task = [NSURLSession.sharedSession dataTaskWithRequest:self completionHandler:^(NSData* data, NSURLResponse* response, NSError* error) | |
{ | |
if(error) | |
{ | |
NSMutableDictionary* userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; | |
userInfo[@"response"] = response; | |
NSError* errorWithResponse = [NSError.alloc initWithDomain:error.domain code:error.code userInfo:userInfo]; | |
onMainThread(^ | |
{ | |
handler(nil, errorWithResponse); | |
}); | |
} | |
else | |
{ | |
onMainThread(^ | |
{ | |
handler(data, nil); | |
}); | |
} | |
}]; | |
[task resume]; | |
} | |
- (NSString *)cURLCommand | |
{ | |
__block NSMutableString* command = [NSMutableString stringWithFormat:@"curl -v %@", self.HTTPMethod]; | |
[command appendFormat:@" \'%@\'", self.URL.absoluteString]; | |
[self.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id val, BOOL *stop) | |
{ | |
[command appendFormat:@" -H \'%@: %@\'", key, val]; | |
}]; | |
if (self.HTTPBody) | |
{ | |
NSString* body = [NSString.alloc initWithData:self.HTTPBody encoding:NSUTF8StringEncoding]; | |
body = [body stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]; | |
[command appendFormat:@" -d \'%@\'", body]; | |
} | |
return command; | |
} | |
@end | |
#pragma mark - | |
@implementation NSURL (EYUtils) | |
- (NSURL *)URLByAddingQueryParameters:(NSDictionary *)parameters | |
{ | |
if (!parameters) | |
return self; | |
NSURLComponents* components = [NSURLComponents.alloc initWithURL:self resolvingAgainstBaseURL:NO]; | |
NSMutableArray* queryItems = components.queryItems ? components.queryItems.mutableCopy : @[].mutableCopy; | |
[parameters enumerateKeysAndObjectsUsingBlock:^(NSString* key, id rawValue, BOOL* stop) | |
{ | |
NSString* value = nil; | |
if ([rawValue isKindOfClass:NSString.class]) | |
value = rawValue; | |
else if ([rawValue isKindOfClass:NSNumber.class]) | |
value = [rawValue stringValue]; | |
else if ([rawValue isKindOfClass:NSArray.class] || [rawValue isKindOfClass:NSDictionary.class]) | |
value = [rawValue JSON]; | |
else | |
value = [value description]; | |
if (value) | |
{ | |
NSURLQueryItem* queryItem = [NSURLQueryItem.alloc initWithName:key value:value]; | |
[queryItems addObject:queryItem]; | |
} | |
}]; | |
[components setQueryItems:queryItems]; | |
return [components URL]; | |
} | |
- (NSString *)valueForQueryParameter:(NSString *)parameter | |
{ | |
NSURLComponents* components = [NSURLComponents componentsWithURL:self resolvingAgainstBaseURL:NO]; | |
NSArray* queryItems = components.queryItems; | |
for (NSURLQueryItem* queryItem in queryItems) | |
if ([queryItem.name isEqualToString:parameter]) | |
return queryItem.value; | |
return nil; | |
} | |
@end | |
#pragma mark - | |
@implementation NSArray (EYUtils) | |
- (NSString *)JSON | |
{ | |
return JSONFromObject(self); | |
} | |
@end | |
#pragma mark - | |
@implementation NSDictionary (EYUtils) | |
- (NSString *)JSON | |
{ | |
return JSONFromObject(self); | |
} | |
@end | |
#pragma mark - | |
@implementation NSMutableData (EYUtils) | |
- (void)appendString:(NSString *)string | |
{ | |
[self appendData:[string dataUsingEncoding:NSUTF8StringEncoding]]; | |
} | |
@end | |
#pragma mark - | |
@implementation UIColor (EYUtils) | |
- (UIColor *)colorByMixingWithColor:(UIColor *)color atRatio:(CGFloat)ratio | |
{ | |
CGFloat oR, oG, oB, oA; | |
[self getRed:&oR green:&oG blue:&oB alpha:&oA]; | |
CGFloat cR, cG, cB, cA; | |
[color getRed:&cR green:&cG blue:&cB alpha:&cA]; | |
CGFloat dR = cR - oR; | |
CGFloat dG = cG - oG; | |
CGFloat dB = cB - oB; | |
CGFloat dA = cA - oA; | |
CGFloat r = MAX(MIN(ratio, 1.0),0.0); | |
return [UIColor colorWithRed:oR + dR * r green:oG + dG * r blue:oB + dB * r alpha:oA + dA * r]; | |
} | |
+ (UIColor *)colorFromHexString:(NSString *)hexString | |
{ | |
hexString = [hexString stringByReplacingOccurrencesOfString:@"#" withString:@""]; | |
unsigned hexInt = 0; | |
[[NSScanner scannerWithString:hexString] scanHexInt:&hexInt]; | |
return RGB((hexInt & 0xFF0000) >> 16, (hexInt & 0x00FF00) >> 8, (hexInt & 0x0000FF)); | |
} | |
@end | |
#pragma mark - | |
@implementation UIImage (EYUtils) | |
- (UIImage *)imageByCombiningWithImage:(UIImage *)upperImage | |
{ | |
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); | |
[self drawAtPoint:CGPointZero]; | |
CGPoint p = (CGPoint){0.5 * (self.size.width - upperImage.size.width), 0.5 * (self.size.height - upperImage.size.height)}; | |
[upperImage drawAtPoint:p]; | |
UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); | |
UIGraphicsEndImageContext(); | |
return image; | |
} | |
+ (UIImage *)imageWithColor:(UIColor *)color andSize:(CGSize)size | |
{ | |
CGRect rect = (CGRect){0.0f, 0.0f, size.width, size.height}; | |
UIGraphicsBeginImageContext(rect.size); | |
CGContextRef context = UIGraphicsGetCurrentContext(); | |
CGContextSetFillColorWithColor(context, color.CGColor); | |
CGContextFillRect(context, rect); | |
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | |
UIGraphicsEndImageContext(); | |
return image; | |
} | |
@end | |
#pragma mark - | |
@implementation UIApplication (EYUtils) | |
+ (NSString *)displayName | |
{ | |
return [NSBundle.mainBundle.infoDictionary objectForKey:@"CFBundleDisplayName"]; | |
} | |
+ (NSString *)version | |
{ | |
return [NSBundle.mainBundle.infoDictionary objectForKey:@"CFBundleShortVersionString"]; | |
} | |
+ (NSString *)build | |
{ | |
return [NSBundle.mainBundle.infoDictionary objectForKey:(NSString *)kCFBundleVersionKey]; | |
} | |
@end | |
#pragma mark - | |
@implementation UIWindow (EYUtils) | |
+ (void)alert:(NSString *)message | |
{ | |
if (!EYUtils.sharedInstance.windowsForFloatingAlerts) | |
{ | |
EYUtils.sharedInstance.windowsForFloatingAlerts = [UIWindow.alloc initWithFrame:UIScreen.mainScreen.bounds]; | |
EYUtils.sharedInstance.windowsForFloatingAlerts.windowLevel = UIWindowLevelAlert; | |
UIViewController* tempVC = UIViewController.new; | |
tempVC.view.backgroundColor = UIColor.clearColor; | |
EYUtils.sharedInstance.windowsForFloatingAlerts.rootViewController = tempVC; | |
[EYUtils.sharedInstance.windowsForFloatingAlerts makeKeyAndVisible]; | |
} | |
EYUtils.sharedInstance.countForFloatingAlerts++; | |
[EYUtils.sharedInstance.windowsForFloatingAlerts.rootViewController alert:message title:nil dismissText:nil completion:^(UIAlertController *alertController) | |
{ | |
EYUtils.sharedInstance.countForFloatingAlerts--; | |
if (EYUtils.sharedInstance.countForFloatingAlerts <= 0) | |
EYUtils.sharedInstance.windowsForFloatingAlerts = nil; | |
}]; | |
} | |
@end | |
#pragma mark - | |
@implementation UIViewController (EYUtils) | |
+ (instancetype)createFromInterfaceFile | |
{ | |
NSString* fileName = NSStringFromClass(self.class); | |
return [self createFromInterfaceFileWithName:fileName]; | |
} | |
+ (instancetype)createFromInterfaceFileWithName:(NSString *)interfaceFileName | |
{ | |
NSString* fileName = interfaceFileName; | |
#if TARGET_OS_TV | |
if (![fileName hasPrefix:@"tv"]) | |
fileName = [@"tv" stringByAppendingString:fileName]; | |
#endif | |
if ([NSBundle.mainBundle pathForResource:fileName ofType:@"nib"]) | |
return [self.alloc initWithNibName:fileName bundle:nil]; | |
if ([NSBundle.mainBundle pathForResource:fileName ofType:@"storyboardc"]) | |
{ | |
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:fileName bundle:nil]; | |
return [storyboard instantiateViewControllerWithIdentifier:fileName]; | |
} | |
return [self.alloc init]; | |
} | |
- (UIViewController *)previousViewControllerOnStack | |
{ | |
NSInteger count = self.navigationController.viewControllers.count; | |
if (count < 2) | |
return nil; | |
else | |
return [self.navigationController.viewControllers objectAtIndex:count - 2]; | |
} | |
- (BOOL)isFirstAppearance | |
{ | |
return self.isBeingPresented || self.isMovingToParentViewController; | |
} | |
#pragma mark Activity | |
- (void)showActivityIndicator; | |
{ | |
[self.view showActivityIndicator]; | |
} | |
- (void)hideActivityIndicator | |
{ | |
[self.view hideActivityIndicator]; | |
} | |
#pragma mark Alert | |
- (void)alert:(NSString *)message | |
{ | |
[self alert:message title:nil dismissText:nil completion:nil]; | |
} | |
- (void)alert:(NSString *)message title:(NSString *)title | |
{ | |
[self alert:message title:title dismissText:nil completion:nil]; | |
} | |
- (void)alert:(NSString *)message dismissText:(NSString *)dismissText | |
{ | |
[self alert:message title:nil dismissText:dismissText completion:nil]; | |
} | |
- (void)alert:(NSString *)message title:(NSString *)title dismissText:(NSString *)dismissText | |
{ | |
[self alert:message title:title dismissText:dismissText completion:nil]; | |
} | |
- (void)alert:(NSString *)message title:(NSString *)title dismissText:(NSString *)dismissText completion:(void(^)(UIAlertController* alertController))completion | |
{ | |
EYUtilsAlertController* controller = [EYUtilsAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; | |
__weak typeof(controller) wController = controller; | |
UIAlertAction* action = [UIAlertAction actionWithTitle:dismissText ?: self.localizedDismissText style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) | |
{ | |
if (completion) | |
completion (wController); | |
}]; | |
[controller addAction:action]; | |
[self presentViewController:controller animated:YES completion:nil]; | |
} | |
- (NSString *)localizedDismissText | |
{ | |
NSString* langDesignator = [NSLocale.preferredLanguages.firstObject substringToIndex:2]; | |
NSDictionary* dict = | |
@{ | |
@"en": @"Dismiss", | |
@"tr": @"Kapat", | |
@"ja": @"閉じる", | |
}; | |
NSString* localizedDismissText = dict[langDesignator]; | |
if (!localizedDismissText) | |
localizedDismissText = dict[@"en"]; | |
return localizedDismissText; | |
} | |
@end | |
#pragma mark - | |
@implementation UIButton (EYUtils) | |
- (void(^)(__strong id))onClick | |
{ | |
return objc_getAssociatedObject(self, @selector(setOnClick:)); | |
} | |
- (void)setOnClick:(void (^)(id))onClick | |
{ | |
objc_setAssociatedObject(self, @selector(setOnClick:), onClick, OBJC_ASSOCIATION_COPY_NONATOMIC); | |
[self addTarget:self action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside]; | |
} | |
- (void)clickAction:(id)sender | |
{ | |
void(^onClick)(id sender) = objc_getAssociatedObject(self, @selector(setOnClick:)); | |
if (onClick) | |
onClick(sender); | |
} | |
@end | |
#pragma mark - | |
@implementation UIScrollView (EYUtils) | |
- (BOOL)isAtTheBottom | |
{ | |
return (self.contentOffset.y >= self.contentSize.height - self.bounds.size.height); | |
} | |
#if TARGET_OS_IOS | |
- (void)setupAutoKeyboardAdjustment | |
{ | |
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onKeyboardWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil]; | |
} | |
- (void)onKeyboardWillChange:(NSNotification *)notification | |
{ | |
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]; | |
UIViewAnimationCurve curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; | |
UIViewAnimationOptions options = (UIViewAnimationOptions)curve << 16; | |
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; | |
CGRect convertedFrame = [self.superview convertRect:keyboardFrame fromView:nil]; | |
CGFloat newHeight = convertedFrame.origin.y - self.frame.origin.y; | |
[UIView animateWithDuration:duration delay:0 options:options animations:^ | |
{ | |
[self setHeight:newHeight]; | |
} completion:nil]; | |
} | |
#endif | |
@end | |
#pragma mark - | |
@implementation UITextField (EYUtils) | |
- (void)addLeftPadding:(CGFloat)padding | |
{ | |
UIView* paddingView = [UIView.alloc initWithFrame:self.bounds]; | |
[paddingView setWidth:padding]; | |
self.leftView = paddingView; | |
self.leftViewMode = UITextFieldViewModeAlways; | |
} | |
@end | |
#pragma mark - | |
@implementation UILabel (EYUtils) | |
- (void)setLineSpacing:(CGFloat)lineSpacing andKerning:(CGFloat)kerning | |
{ | |
if (!self.text) | |
return; | |
NSMutableParagraphStyle* paragraphStyle = NSMutableParagraphStyle.new; | |
paragraphStyle.lineSpacing = lineSpacing; | |
paragraphStyle.alignment = self.textAlignment; | |
paragraphStyle.lineBreakMode = self.lineBreakMode; | |
NSDictionary* attributes = | |
@{ | |
NSFontAttributeName: self.font, | |
NSParagraphStyleAttributeName: paragraphStyle, | |
NSKernAttributeName: @(kerning), | |
}; | |
NSMutableAttributedString* attributedString = [NSMutableAttributedString.alloc initWithString:self.text attributes:attributes]; | |
self.attributedText = attributedString; | |
} | |
- (NSInteger)lineCount | |
{ | |
CGSize textSize = CGSizeMake(self.frame.size.width, MAXFLOAT); | |
NSInteger totalHeight = lroundf([self sizeThatFits:textSize].height); | |
NSInteger rowHeight = lroundf(self.font.lineHeight); | |
NSInteger lineCount = totalHeight / rowHeight; | |
return lineCount; | |
} | |
@end | |
#pragma mark - | |
@implementation UITableView (EYUtils) | |
- (void)updateCellHeightsWithBufferDelay | |
{ | |
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(updateCellHeights) object:nil]; | |
[self performSelector:@selector(updateCellHeights) withObject:nil afterDelay:0.1]; | |
} | |
- (void)updateCellHeights | |
{ | |
[self beginUpdates]; | |
[self endUpdates]; | |
} | |
@end | |
#pragma mark - | |
@implementation UIView (EYUtils) | |
#pragma mark Positioning | |
- (void)moveRight:(CGFloat)value | |
{ | |
CGPoint p = self.center; | |
p.x += value; | |
self.center = p; | |
} | |
- (void)moveLeft:(CGFloat)value | |
{ | |
CGPoint p = self.center; | |
p.x -= value; | |
self.center = p; | |
} | |
- (void)moveUp:(CGFloat)value | |
{ | |
CGPoint p = self.center; | |
p.y -= value; | |
self.center = p; | |
} | |
- (void)moveDown:(CGFloat)value | |
{ | |
CGPoint p = self.center; | |
p.y += value; | |
self.center = p; | |
} | |
- (void)centerInSuperView | |
{ | |
self.center = (CGPoint){0.5 * self.superview.bounds.size.width, 0.5 * self.superview.bounds.size.height}; | |
} | |
- (void)centerRightInSuperView | |
{ | |
[self centerInSuperView]; | |
[self stickToRightInSuperView]; | |
} | |
- (void)centerLeftInSuperView | |
{ | |
[self centerInSuperView]; | |
[self stickToLeftInSuperView]; | |
} | |
- (void)centerTopInSuperView | |
{ | |
[self centerInSuperView]; | |
[self stickToTopInSuperView]; | |
} | |
- (void)centerBottomInSuperView | |
{ | |
[self centerInSuperView]; | |
[self stickToBottomInSuperView]; | |
} | |
- (void)stickToRightInSuperView | |
{ | |
[self setOriginX:self.superview.bounds.size.width - self.bounds.size.width]; | |
} | |
- (void)stickToLeftInSuperView | |
{ | |
[self setOriginX:0.0]; | |
} | |
- (void)stickToTopInSuperView | |
{ | |
[self setOriginY:0.0]; | |
} | |
- (void)stickToBottomInSuperView | |
{ | |
[self setOriginY:self.superview.bounds.size.height - self.bounds.size.height]; | |
} | |
- (void)stickToBottomOfView:(UIView *)targetView | |
{ | |
[self stickToBottomOfView:targetView withMargin:0.0]; | |
} | |
- (void)stickToBottomOfView:(UIView *)targetView withMargin:(CGFloat)margin | |
{ | |
CGFloat y = CGRectGetMaxY(targetView.frame); | |
if (targetView.frame.size.height > 0.0) | |
y += margin; | |
[self setOriginY:y]; | |
} | |
- (void)stickToRightOfView:(UIView *)targetView | |
{ | |
[self stickToRightOfView:targetView withMargin:0.0]; | |
} | |
- (void)stickToRightOfView:(UIView *)targetView withMargin:(CGFloat)margin | |
{ | |
CGFloat x = CGRectGetMaxX(targetView.frame); | |
if (targetView.frame.size.width > 0.0) | |
x += margin; | |
[self setOriginX:x]; | |
} | |
- (void)setCenterX:(CGFloat)value | |
{ | |
self.center = (CGPoint){value, self.center.y}; | |
} | |
- (void)setCenterY:(CGFloat)value | |
{ | |
self.center = (CGPoint){self.center.x, value}; | |
} | |
- (void)setOriginX:(CGFloat)value | |
{ | |
CGRect r = self.frame; | |
r.origin.x = value; | |
self.frame = r; | |
} | |
- (void)setOriginY:(CGFloat)value | |
{ | |
CGRect r = self.frame; | |
r.origin.y = value; | |
self.frame = r; | |
} | |
- (void)setOrigin:(CGPoint)origin | |
{ | |
CGRect r = self.frame; | |
r.origin = origin; | |
self.frame = r; | |
} | |
#pragma mark Resizing | |
- (void)setHeight:(CGFloat)value | |
{ | |
CGRect r = self.frame; | |
r.size.height = value; | |
self.frame = r ; | |
} | |
- (void)setWidth:(CGFloat)value | |
{ | |
CGRect r = self.frame; | |
r.size.width = value; | |
self.frame = r; | |
} | |
- (void)expandRight:(CGFloat)value | |
{ | |
CGFloat newValue = self.bounds.size.width + value; | |
[self setWidth:newValue]; | |
} | |
- (void)expandLeft:(CGFloat)value | |
{ | |
[self moveLeft:value]; | |
[self expandRight:value]; | |
} | |
- (void)expandUp:(CGFloat)value | |
{ | |
[self moveUp:value]; | |
[self expandDown:value]; | |
} | |
- (void)expandDown:(CGFloat)value | |
{ | |
CGFloat newValue = self.bounds.size.height + value; | |
[self setHeight:newValue]; | |
} | |
#pragma mark Border & Shadow | |
- (void)addTopBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth | |
{ | |
CALayer* border = CALayer.layer; | |
border.backgroundColor = color.CGColor; | |
border.frame = (CGRect){0.0, 0.0, self.frame.size.width, borderWidth}; | |
[self.layer addSublayer:border]; | |
} | |
- (void)addBottomBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth | |
{ | |
CALayer *border = [CALayer layer]; | |
border.backgroundColor = color.CGColor; | |
border.frame = (CGRect){0.0, self.frame.size.height - borderWidth, self.frame.size.width, borderWidth}; | |
[self.layer addSublayer:border]; | |
} | |
- (void)addLeftBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth | |
{ | |
CALayer *border = [CALayer layer]; | |
border.backgroundColor = color.CGColor; | |
border.frame = (CGRect){0.0, 0.0, borderWidth, self.frame.size.height}; | |
[self.layer addSublayer:border]; | |
} | |
- (void)addRightBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth | |
{ | |
CALayer *border = [CALayer layer]; | |
border.backgroundColor = color.CGColor; | |
border.frame = (CGRect){self.frame.size.width - borderWidth, 0.0, borderWidth, self.frame.size.height}; | |
[self.layer addSublayer:border]; | |
} | |
- (void)addBorderWithColor:(UIColor *)color andWidth:(CGFloat)borderWidth | |
{ | |
self.layer.borderWidth = borderWidth; | |
self.layer.borderColor = color.CGColor; | |
} | |
- (void)addDropShadow | |
{ | |
self.layer.shadowColor = UIColor.blackColor.CGColor; | |
self.layer.shadowOpacity = 0.2; | |
self.layer.shadowOffset = CGSizeMake(0.0f, 3.0f); | |
self.layer.masksToBounds = NO; | |
} | |
- (void)roundCorners | |
{ | |
[self roundCornersWithRadius:self.bounds.size.height * 0.5]; | |
} | |
- (void)roundCornersWithRadius:(CGFloat)radius | |
{ | |
self.layer.cornerRadius = radius; | |
self.layer.masksToBounds = YES; | |
} | |
#pragma mark Animated Positioning | |
const NSTimeInterval kEYUtilsDefaultAnimationDuration = 0.25; | |
- (void)moveAnimatedRight:(CGFloat)value | |
{ | |
[self moveAnimatedRight:value duration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)moveAnimatedRight:(CGFloat)value duration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ [self moveRight:value]; } completion:nil]; | |
} | |
- (void)moveAnimatedLeft:(CGFloat)value | |
{ | |
[self moveAnimatedLeft:value duration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)moveAnimatedLeft:(CGFloat)value duration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ [self moveLeft:value]; } completion:nil]; | |
} | |
- (void)moveAnimatedUp:(CGFloat)value | |
{ | |
[self moveAnimatedUp:value duration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)moveAnimatedUp:(CGFloat)value duration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ [self moveUp:value]; } completion:nil]; | |
} | |
- (void)moveAnimatedDown:(CGFloat)value | |
{ | |
[self moveAnimatedDown:value duration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)moveAnimatedDown:(CGFloat)value duration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ [self moveDown:value]; } completion:nil]; | |
} | |
- (void)centerAnimatedInSuperView | |
{ | |
[self centerAnimatedInSuperViewWithDuration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)centerAnimatedInSuperViewWithDuration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ [self centerInSuperView]; } completion:nil]; | |
} | |
#pragma mark Fading | |
- (void)fadeIn | |
{ | |
[self fadeInWithDuration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)fadeInWithDuration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ self.alpha = 1.0; } completion:nil]; | |
} | |
- (void)fadeOut | |
{ | |
[self fadeOutWithDuration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)fadeOutWithDuration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ self.alpha = 0.0; } completion:nil]; | |
} | |
#pragma mark Animated Resizing | |
- (void)scaleTo:(CGFloat)ratio | |
{ | |
[self scaleTo:ratio duration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)scaleTo:(CGFloat)ratio duration:(NSTimeInterval)duration | |
{ | |
[UIView animateWithDuration:duration delay:0.0 | |
options:UIViewAnimationOptionCurveEaseInOut | |
animations:^{ self.transform = (ratio == 1) ? CGAffineTransformIdentity : CGAffineTransformMakeScale(ratio, ratio); } completion:nil]; | |
} | |
- (void)shrink | |
{ | |
[self shrinkWithDuration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)shrinkWithDuration:(NSTimeInterval)duration | |
{ | |
[self scaleTo:0.0001 duration:duration]; | |
} | |
- (void)unshrink | |
{ | |
[self unshrinkWithDuration:kEYUtilsDefaultAnimationDuration]; | |
} | |
- (void)unshrinkWithDuration:(NSTimeInterval)duration | |
{ | |
[self scaleTo:1 duration:duration]; | |
} | |
#pragma mark Image Rendering | |
- (UIImage *)renderedImage | |
{ | |
BOOL isOpaque = self.opaque; | |
BOOL isHidden = self.hidden; | |
self.opaque = NO; | |
self.hidden = NO; | |
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, UIScreen.mainScreen.scale); | |
[UIColor.clearColor set]; | |
[self.layer renderInContext:UIGraphicsGetCurrentContext()]; | |
UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); | |
UIGraphicsEndImageContext(); | |
self.opaque = isOpaque; | |
self.hidden = isHidden; | |
return image; | |
} | |
- (BOOL)saveRenderedImageToDocuments | |
{ | |
NSData* imageData = UIImagePNGRepresentation([self renderedImage]); | |
NSURL* documentsDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject; | |
NSString* fileName = [NSString stringWithFormat:@"%@-%@.png", NSStringFromClass(self.class), NSDate.date.description]; | |
NSURL *file = [documentsDirectory URLByAppendingPathComponent:fileName]; | |
return [imageData writeToFile:file.path atomically:YES]; | |
} | |
#pragma mark Activity | |
- (EYUtilsActivityContainer *)activityContainer | |
{ | |
return objc_getAssociatedObject(self, @selector(activityContainer)); | |
} | |
- (void)setActivityContainer:(EYUtilsActivityContainer *)activityContainer | |
{ | |
objc_setAssociatedObject(self, @selector(activityContainer), activityContainer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (void)showActivityIndicator; | |
{ | |
if (self.activityContainer) | |
{ | |
self.activityContainer.count++; | |
return; | |
} | |
self.activityContainer = [EYUtilsActivityContainer.alloc initWithFrame:self.bounds]; | |
[self addSubview:self.activityContainer]; | |
self.activityContainer.userInteractionEnabled = self.userInteractionEnabled; | |
self.userInteractionEnabled = NO; | |
if ([self isKindOfClass:UIScrollView.class]) | |
{ | |
UIScrollView* scrollView = (UIScrollView *)self; | |
[scrollView setContentOffset:scrollView.contentOffset animated:NO]; | |
} | |
} | |
- (void)hideActivityIndicator | |
{ | |
if (!self.activityContainer) | |
return; | |
self.activityContainer.count --; | |
if (self.activityContainer.count == 0) | |
{ | |
[self.activityContainer removeFromSuperview]; | |
self.userInteractionEnabled = self.activityContainer.userInteractionEnabled; | |
self.activityContainer = nil; | |
} | |
} | |
#pragma mark Shimmering | |
- (CALayer *)shimmerLayer | |
{ | |
return objc_getAssociatedObject(self, @selector(shimmerLayer)); | |
} | |
- (void)setShimmerLayer:(CALayer *)shimmerLayer | |
{ | |
objc_setAssociatedObject(self, @selector(shimmerLayer), shimmerLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (void)startShimmering | |
{ | |
if (self.shimmerLayer) | |
return; | |
CALayer* imageMaskLayer = nil; | |
if ([self isKindOfClass:UIImageView.class]) | |
{ | |
CALayer* imageOutline = CALayer.new; | |
imageOutline.frame = self.bounds; | |
imageOutline.contents = (id)((UIImageView *)self).image.CGImage; | |
imageMaskLayer = CALayer.new; | |
imageMaskLayer.frame = self.bounds; | |
imageMaskLayer.mask = imageOutline; | |
[self.layer addSublayer:imageMaskLayer]; | |
} | |
CALayer* shimmerLayer = CALayer.new; | |
shimmerLayer.frame = self.bounds; | |
shimmerLayer.backgroundColor = UIColor.whiteColor.CGColor; | |
[imageMaskLayer ?: self.layer addSublayer:shimmerLayer]; | |
self.shimmerLayer = shimmerLayer; | |
id light = (id)UIColor.whiteColor.CGColor; | |
id alpha = (id)[UIColor.whiteColor colorWithAlphaComponent:0.0].CGColor; | |
const CGFloat tilt = 0.022; | |
CAGradientLayer* gradient = CAGradientLayer.new; | |
gradient.colors = @[alpha, light, alpha]; | |
gradient.frame = shimmerLayer.bounds; | |
gradient.startPoint = (CGPoint){0.0, 0.5 - tilt}; | |
gradient.endPoint = (CGPoint){1.0, 0.5 + tilt}; | |
gradient.locations = @[@0.0, @0.0, @0.0]; //NOTE: initial values are not important as they will be animated, but they have to be set | |
shimmerLayer.mask = gradient; | |
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"locations"]; | |
animation.fromValue = @[@(-1.0), @(-0.5), @(0.0)]; | |
animation.toValue = @[@(1.0), @(1.5), @(2.0)]; | |
animation.duration = 1.0; | |
animation.repeatCount = MAXFLOAT; | |
animation.fillMode = kCAFillModeForwards; | |
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; | |
[gradient addAnimation:animation forKey:@"shimmer"]; | |
[self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil]; | |
} | |
- (void)stopShimmering | |
{ | |
if (!self.shimmerLayer) | |
return; | |
[self removeObserver:self forKeyPath:@"frame"]; | |
[self.shimmerLayer removeFromSuperlayer]; | |
self.shimmerLayer = nil; | |
} | |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context | |
{ | |
UIView* view = object; | |
if (view == self && [keyPath isEqualToString:@"frame"]) | |
{ | |
self.shimmerLayer.frame = view.bounds; | |
self.shimmerLayer.mask.frame = view.bounds; | |
} | |
} | |
#pragma mark Creating | |
+ (instancetype)createFromXIB; | |
{ | |
NSString* fileName = NSStringFromClass(self.class); | |
if ([NSBundle.mainBundle pathForResource:fileName ofType:@"nib"]) | |
return [NSBundle.mainBundle loadNibNamed:fileName owner:nil options:nil].firstObject; | |
return [self.alloc init]; | |
} | |
#pragma mark Debugging | |
- (void)dumpFrame:(NSString *)comment | |
{ | |
NSLog(@"\n%@Frame: %@\nCenter: %@", (comment) ? [comment stringByAppendingString:@": \n"] : @"", | |
NSStringFromCGRect(self.frame), | |
NSStringFromCGPoint(self.center)); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment