// Copyright © 2018 650 Industries. All rights reserved. #import <UMCore/UMDefines.h> #import <UMCore/UMUtilities.h> @interface UMUtilities () @property (nonatomic, nullable, weak) UMModuleRegistry *moduleRegistry; @end @protocol UMUtilService - (UIViewController *)currentViewController; - (nullable NSDictionary *)launchOptions; @end @implementation UMUtilities UM_REGISTER_MODULE(); + (const NSArray<Protocol *> *)exportedInterfaces { return @[@protocol(UMUtilitiesInterface)]; } - (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry { _moduleRegistry = moduleRegistry; } - (nullable NSDictionary *)launchOptions { id<UMUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"]; return [utilService launchOptions]; } - (UIViewController *)currentViewController { id<UMUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"]; if (utilService != nil) { return [utilService currentViewController]; } UIViewController *controller = [[[UIApplication sharedApplication] keyWindow] rootViewController]; UIViewController *presentedController = controller.presentedViewController; while (presentedController && ![presentedController isBeingDismissed]) { controller = presentedController; presentedController = controller.presentedViewController; } return controller; } + (void)performSynchronouslyOnMainThread:(void (^)(void))block { if ([NSThread isMainThread]) { block(); } else { dispatch_sync(dispatch_get_main_queue(), block); } } // Copied from RN + (BOOL)isMainQueue { static void *mainQueueKey = &mainQueueKey; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueKey, NULL); }); return dispatch_get_specific(mainQueueKey) == mainQueueKey; } // Copied from RN + (void)unsafeExecuteOnMainQueueOnceSync:(dispatch_once_t *)onceToken block:(dispatch_block_t)block { // The solution was borrowed from a post by Ben Alpert: // https://benalpert.com/2014/04/02/dispatch-once-initialization-on-the-main-thread.html // See also: https://www.mikeash.com/pyblog/friday-qa-2014-06-06-secrets-of-dispatch_once.html if ([self isMainQueue]) { dispatch_once(onceToken, block); } else { if (DISPATCH_EXPECT(*onceToken == 0L, NO)) { dispatch_sync(dispatch_get_main_queue(), ^{ dispatch_once(onceToken, block); }); } } } // Copied from RN + (CGFloat)screenScale { static dispatch_once_t onceToken; static CGFloat scale; [self unsafeExecuteOnMainQueueOnceSync:&onceToken block:^{ scale = [UIScreen mainScreen].scale; }]; return scale; } // Kind of copied from RN to make UIColor:(id)json work + (NSArray<NSNumber *> *)NSNumberArray:(id)json { return json; } + (NSNumber *)NSNumber:(id)json { return json; } + (CGFloat)CGFloat:(id)json { return [[self NSNumber:json] floatValue]; } + (NSInteger)NSInteger:(id)json { return [[self NSNumber:json] integerValue]; } + (NSUInteger)NSUInteger:(id)json { return [[self NSNumber:json] unsignedIntegerValue]; } // Copied from RN + (UIColor *)UIColor:(id)json { if (!json) { return nil; } if ([json isKindOfClass:[NSArray class]]) { NSArray *components = [self NSNumberArray:json]; CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0; return [UIColor colorWithRed:[self CGFloat:components[0]] green:[self CGFloat:components[1]] blue:[self CGFloat:components[2]] alpha:alpha]; } else if ([json isKindOfClass:[NSNumber class]]) { NSUInteger argb = [self NSUInteger:json]; CGFloat a = ((argb >> 24) & 0xFF) / 255.0; CGFloat r = ((argb >> 16) & 0xFF) / 255.0; CGFloat g = ((argb >> 8) & 0xFF) / 255.0; CGFloat b = (argb & 0xFF) / 255.0; return [UIColor colorWithRed:r green:g blue:b alpha:a]; } else { UMLogInfo(@"%@ cannot be converted to a UIColor", json); return nil; } } // Copied from RN + (NSDate *)NSDate:(id)json { if ([json isKindOfClass:[NSNumber class]]) { return [NSDate dateWithTimeIntervalSince1970:[json doubleValue] / 1000.0]; } else if ([json isKindOfClass:[NSString class]]) { static NSDateFormatter *formatter; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [NSDateFormatter new]; formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"; formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; }); NSDate *date = [formatter dateFromString:json]; if (!date) { UMLogError(@"JSON String '%@' could not be interpreted as a date. " "Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json); } return date; } else if (json) { UMLogError(json, @"a date"); } return nil; } // https://stackoverflow.com/questions/14051807/how-can-i-get-a-hex-string-from-uicolor-or-from-rgb + (NSString *)hexStringWithCGColor:(CGColorRef)color { const CGFloat *components = CGColorGetComponents(color); size_t count = CGColorGetNumberOfComponents(color); if (count == 2) { return [NSString stringWithFormat:@"#%02lX%02lX%02lX", lroundf(components[0] * 255.0), lroundf(components[0] * 255.0), lroundf(components[0] * 255.0)]; } else { return [NSString stringWithFormat:@"#%02lX%02lX%02lX", lroundf(components[0] * 255.0), lroundf(components[1] * 255.0), lroundf(components[2] * 255.0)]; } } @end UIApplication * UMSharedApplication(void) { if ([[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"]) { return nil; } return [[UIApplication class] performSelector:@selector(sharedApplication)]; } NSError *UMErrorWithMessage(NSString *message) { NSDictionary<NSString *, id> *errorInfo = @{NSLocalizedDescriptionKey: message}; return [[NSError alloc] initWithDomain:@"UMModulesErrorDomain" code:0 userInfo:errorInfo]; }