1// Copyright © 2018 650 Industries. All rights reserved. 2 3#import <ExpoModulesCore/EXDefines.h> 4#import <ExpoModulesCore/EXUtilities.h> 5 6@interface EXUtilities () 7 8@property (nonatomic, nullable, weak) EXModuleRegistry *moduleRegistry; 9 10@end 11 12@protocol EXUtilService 13 14- (UIViewController *)currentViewController; 15 16- (nullable NSDictionary *)launchOptions; 17 18@end 19 20@implementation EXUtilities 21 22EX_REGISTER_MODULE(); 23 24+ (const NSArray<Protocol *> *)exportedInterfaces 25{ 26 return @[@protocol(EXUtilitiesInterface)]; 27} 28 29- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry 30{ 31 _moduleRegistry = moduleRegistry; 32} 33 34- (nullable NSDictionary *)launchOptions 35{ 36 id<EXUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"]; 37 return [utilService launchOptions]; 38} 39 40- (UIViewController *)currentViewController 41{ 42 id<EXUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"]; 43 44 if (utilService != nil) { 45 return [utilService currentViewController]; 46 } 47 48 UIViewController *controller = [[[UIApplication sharedApplication] keyWindow] rootViewController]; 49 UIViewController *presentedController = controller.presentedViewController; 50 51 while (presentedController && ![presentedController isBeingDismissed]) { 52 controller = presentedController; 53 presentedController = controller.presentedViewController; 54 } 55 return controller; 56} 57 58+ (void)performSynchronouslyOnMainThread:(void (^)(void))block 59{ 60 if ([NSThread isMainThread]) { 61 block(); 62 } else { 63 dispatch_sync(dispatch_get_main_queue(), block); 64 } 65} 66 67// Copied from RN 68+ (BOOL)isMainQueue 69{ 70 static void *mainQueueKey = &mainQueueKey; 71 static dispatch_once_t onceToken; 72 73 dispatch_once(&onceToken, ^{ 74 dispatch_queue_set_specific(dispatch_get_main_queue(), 75 mainQueueKey, mainQueueKey, NULL); 76 }); 77 78 return dispatch_get_specific(mainQueueKey) == mainQueueKey; 79} 80 81// Copied from RN 82+ (void)unsafeExecuteOnMainQueueOnceSync:(dispatch_once_t *)onceToken block:(dispatch_block_t)block 83{ 84 // The solution was borrowed from a post by Ben Alpert: 85 // https://benalpert.com/2014/04/02/dispatch-once-initialization-on-the-main-thread.html 86 // See also: https://www.mikeash.com/pyblog/friday-qa-2014-06-06-secrets-of-dispatch_once.html 87 if ([self isMainQueue]) { 88 dispatch_once(onceToken, block); 89 } else { 90 if (DISPATCH_EXPECT(*onceToken == 0L, NO)) { 91 dispatch_sync(dispatch_get_main_queue(), ^{ 92 dispatch_once(onceToken, block); 93 }); 94 } 95 } 96} 97 98// Copied from RN 99+ (CGFloat)screenScale 100{ 101 static dispatch_once_t onceToken; 102 static CGFloat scale; 103 104 [self unsafeExecuteOnMainQueueOnceSync:&onceToken block:^{ 105 scale = [UIScreen mainScreen].scale; 106 }]; 107 108 return scale; 109} 110 111 112// Kind of copied from RN to make UIColor:(id)json work 113+ (NSArray<NSNumber *> *)NSNumberArray:(id)json 114{ 115 return json; 116} 117 118+ (NSNumber *)NSNumber:(id)json 119{ 120 return json; 121} 122 123+ (CGFloat)CGFloat:(id)json 124{ 125 return [[self NSNumber:json] floatValue]; 126} 127 128+ (NSInteger)NSInteger:(id)json 129{ 130 return [[self NSNumber:json] integerValue]; 131} 132 133+ (NSUInteger)NSUInteger:(id)json 134{ 135 return [[self NSNumber:json] unsignedIntegerValue]; 136} 137 138// Copied from RN 139+ (UIColor *)UIColor:(id)json 140{ 141 if (!json) { 142 return nil; 143 } 144 if ([json isKindOfClass:[NSArray class]]) { 145 NSArray *components = [self NSNumberArray:json]; 146 CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0; 147 return [UIColor colorWithRed:[self CGFloat:components[0]] 148 green:[self CGFloat:components[1]] 149 blue:[self CGFloat:components[2]] 150 alpha:alpha]; 151 } else if ([json isKindOfClass:[NSNumber class]]) { 152 NSUInteger argb = [self NSUInteger:json]; 153 CGFloat a = ((argb >> 24) & 0xFF) / 255.0; 154 CGFloat r = ((argb >> 16) & 0xFF) / 255.0; 155 CGFloat g = ((argb >> 8) & 0xFF) / 255.0; 156 CGFloat b = (argb & 0xFF) / 255.0; 157 return [UIColor colorWithRed:r green:g blue:b alpha:a]; 158 } else { 159 EXLogInfo(@"%@ cannot be converted to a UIColor", json); 160 return nil; 161 } 162} 163 164// Copied from RN 165+ (NSDate *)NSDate:(id)json 166{ 167 if ([json isKindOfClass:[NSNumber class]]) { 168 return [NSDate dateWithTimeIntervalSince1970:[json doubleValue] / 1000.0]; 169 } else if ([json isKindOfClass:[NSString class]]) { 170 static NSDateFormatter *formatter; 171 static dispatch_once_t onceToken; 172 dispatch_once(&onceToken, ^{ 173 formatter = [NSDateFormatter new]; 174 formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"; 175 formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; 176 formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; 177 }); 178 NSDate *date = [formatter dateFromString:json]; 179 if (!date) { 180 EXLogError(@"JSON String '%@' could not be interpreted as a date. " 181 "Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json); 182 } 183 return date; 184 } else if (json) { 185 EXLogError(json, @"a date"); 186 } 187 return nil; 188} 189 190// https://stackoverflow.com/questions/14051807/how-can-i-get-a-hex-string-from-uicolor-or-from-rgb 191+ (NSString *)hexStringWithCGColor:(CGColorRef)color 192{ 193 const CGFloat *components = CGColorGetComponents(color); 194 size_t count = CGColorGetNumberOfComponents(color); 195 196 if (count == 2) { 197 return [NSString stringWithFormat:@"#%02lX%02lX%02lX", 198 lroundf(components[0] * 255.0), 199 lroundf(components[0] * 255.0), 200 lroundf(components[0] * 255.0)]; 201 } else { 202 return [NSString stringWithFormat:@"#%02lX%02lX%02lX", 203 lroundf(components[0] * 255.0), 204 lroundf(components[1] * 255.0), 205 lroundf(components[2] * 255.0)]; 206 } 207} 208 209+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error 210{ 211 @try { 212 tryBlock(); 213 return YES; 214 } 215 @catch (NSException *exception) { 216 *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo]; 217 return NO; 218 } 219} 220 221@end 222 223UIApplication * EXSharedApplication(void) 224{ 225 if ([[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"]) { 226 return nil; 227 } 228 return [[UIApplication class] performSelector:@selector(sharedApplication)]; 229} 230 231NSError *EXErrorWithMessage(NSString *message) 232{ 233 NSDictionary<NSString *, id> *errorInfo = @{NSLocalizedDescriptionKey: message}; 234 return [[NSError alloc] initWithDomain:@"EXModulesErrorDomain" code:0 userInfo:errorInfo]; 235} 236