123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- //
- // FLEXUtility.m
- // Flipboard
- //
- // Created by Ryan Olson on 4/18/14.
- // Copyright (c) 2020 FLEX Team. All rights reserved.
- //
- #import "FLEXColor.h"
- #import "FLEXUtility.h"
- #import "FLEXResources.h"
- #import "FLEXWindow.h"
- #import <ImageIO/ImageIO.h>
- #import <objc/runtime.h>
- #import <zlib.h>
- BOOL FLEXConstructorsShouldRun() {
- #if FLEX_DISABLE_CTORS
- return NO;
- #else
- static BOOL _FLEXConstructorsShouldRun_storage = YES;
-
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSString *key = @"FLEX_SKIP_INIT";
- if (getenv(key.UTF8String) || [NSUserDefaults.standardUserDefaults boolForKey:key]) {
- _FLEXConstructorsShouldRun_storage = NO;
- }
- });
-
- return _FLEXConstructorsShouldRun_storage;
- #endif
- }
- @implementation FLEXUtility
- + (UIWindow *)appKeyWindow {
- // First, check UIApplication.keyWindow
- FLEXWindow *window = (id)UIApplication.sharedApplication.keyWindow;
- if (window) {
- if ([window isKindOfClass:[FLEXWindow class]]) {
- return window.previousKeyWindow;
- }
-
- return window;
- }
-
- // As of iOS 13, UIApplication.keyWindow does not return nil,
- // so this is more of a safeguard against it returning nil in the future.
- //
- // Also, these are obviously not all FLEXWindows; FLEXWindow is used
- // so we can call window.previousKeyWindow without an ugly cast
- for (FLEXWindow *window in UIApplication.sharedApplication.windows) {
- if (window.isKeyWindow) {
- if ([window isKindOfClass:[FLEXWindow class]]) {
- return window.previousKeyWindow;
- }
-
- return window;
- }
- }
-
- return nil;
- }
- #if FLEX_AT_LEAST_IOS13_SDK
- + (UIWindowScene *)activeScene {
- for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
- // Look for an active UIWindowScene
- if (scene.activationState == UISceneActivationStateForegroundActive &&
- [scene isKindOfClass:[UIWindowScene class]]) {
- return (UIWindowScene *)scene;
- }
- }
-
- return nil;
- }
- #endif
- + (UIViewController *)topViewControllerInWindow:(UIWindow *)window {
- UIViewController *topViewController = window.rootViewController;
- while (topViewController.presentedViewController) {
- topViewController = topViewController.presentedViewController;
- }
- return topViewController;
- }
- + (UIColor *)consistentRandomColorForObject:(id)object {
- CGFloat hue = (((NSUInteger)object >> 4) % 256) / 255.0;
- return [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0];
- }
- + (NSString *)descriptionForView:(UIView *)view includingFrame:(BOOL)includeFrame {
- NSString *description = [[view class] description];
-
- NSString *viewControllerDescription = [[[self viewControllerForView:view] class] description];
- if (viewControllerDescription.length > 0) {
- description = [description stringByAppendingFormat:@" (%@)", viewControllerDescription];
- }
-
- if (includeFrame) {
- description = [description stringByAppendingFormat:@" %@", [self stringForCGRect:view.frame]];
- }
-
- if (view.accessibilityLabel.length > 0) {
- description = [description stringByAppendingFormat:@" · %@", view.accessibilityLabel];
- }
-
- return description;
- }
- + (NSString *)stringForCGRect:(CGRect)rect {
- return [NSString stringWithFormat:@"{(%g, %g), (%g, %g)}",
- rect.origin.x, rect.origin.y, rect.size.width, rect.size.height
- ];
- }
- + (UIViewController *)viewControllerForView:(UIView *)view {
- NSString *viewDelegate = @"_viewDelegate";
- if ([view respondsToSelector:NSSelectorFromString(viewDelegate)]) {
- return [view valueForKey:viewDelegate];
- }
- return nil;
- }
- + (UIViewController *)viewControllerForAncestralView:(UIView *)view {
- NSString *_viewControllerForAncestor = @"_viewControllerForAncestor";
- if ([view respondsToSelector:NSSelectorFromString(_viewControllerForAncestor)]) {
- return [view valueForKey:_viewControllerForAncestor];
- }
- return nil;
- }
- + (UIImage *)previewImageForView:(UIView *)view {
- if (CGRectIsEmpty(view.bounds)) {
- return [UIImage new];
- }
-
- CGSize viewSize = view.bounds.size;
- UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
- [view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
- UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return previewImage;
- }
- + (UIImage *)previewImageForLayer:(CALayer *)layer {
- if (CGRectIsEmpty(layer.bounds)) {
- return nil;
- }
-
- UIGraphicsBeginImageContextWithOptions(layer.bounds.size, NO, 0.0);
- CGContextRef imageContext = UIGraphicsGetCurrentContext();
- [layer renderInContext:imageContext];
- UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return previewImage;
- }
- + (NSString *)detailDescriptionForView:(UIView *)view {
- return [NSString stringWithFormat:@"frame %@", [self stringForCGRect:view.frame]];
- }
- + (UIImage *)circularImageWithColor:(UIColor *)color radius:(CGFloat)radius {
- CGFloat diameter = radius * 2.0;
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), NO, 0.0);
- CGContextRef imageContext = UIGraphicsGetCurrentContext();
- CGContextSetFillColorWithColor(imageContext, color.CGColor);
- CGContextFillEllipseInRect(imageContext, CGRectMake(0, 0, diameter, diameter));
- UIImage *circularImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return circularImage;
- }
- + (UIColor *)hierarchyIndentPatternColor {
- static UIColor *patternColor = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- UIImage *indentationPatternImage = FLEXResources.hierarchyIndentPattern;
- patternColor = [UIColor colorWithPatternImage:indentationPatternImage];
- #if FLEX_AT_LEAST_IOS13_SDK
- if (@available(iOS 13.0, *)) {
- // Create a dark mode version
- UIGraphicsBeginImageContextWithOptions(
- indentationPatternImage.size, NO, indentationPatternImage.scale
- );
- [FLEXColor.iconColor set];
- [indentationPatternImage drawInRect:CGRectMake(
- 0, 0, indentationPatternImage.size.width, indentationPatternImage.size.height
- )];
- UIImage *darkModePatternImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- // Create dynamic color provider
- patternColor = [UIColor colorWithDynamicProvider:^UIColor *(UITraitCollection *traitCollection) {
- return (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight
- ? [UIColor colorWithPatternImage:indentationPatternImage]
- : [UIColor colorWithPatternImage:darkModePatternImage]);
- }];
- }
- #endif
- });
- return patternColor;
- }
- + (NSString *)applicationImageName {
- return NSBundle.mainBundle.executablePath;
- }
- + (NSString *)applicationName {
- return FLEXUtility.applicationImageName.lastPathComponent;
- }
- + (NSString *)pointerToString:(void *)ptr {
- return [NSString stringWithFormat:@"%p", ptr];
- }
- + (NSString *)addressOfObject:(id)object {
- return [NSString stringWithFormat:@"%p", object];
- }
- + (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString {
- static NSDictionary<NSString *, NSString *> *escapingDictionary = nil;
- static NSRegularExpression *regex = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- escapingDictionary = @{ @" " : @" ",
- @">" : @">",
- @"<" : @"<",
- @"&" : @"&",
- @"'" : @"'",
- @"\"" : @""",
- @"«" : @"«",
- @"»" : @"»"
- };
- regex = [NSRegularExpression regularExpressionWithPattern:@"(&|>|<|'|\"|«|»)" options:0 error:NULL];
- });
-
- NSMutableString *mutableString = originalString.mutableCopy;
-
- NSArray<NSTextCheckingResult *> *matches = [regex
- matchesInString:mutableString options:0 range:NSMakeRange(0, mutableString.length)
- ];
- for (NSTextCheckingResult *result in matches.reverseObjectEnumerator) {
- NSString *foundString = [mutableString substringWithRange:result.range];
- NSString *replacementString = escapingDictionary[foundString];
- if (replacementString) {
- [mutableString replaceCharactersInRange:result.range withString:replacementString];
- }
- }
-
- return [mutableString copy];
- }
- #if !TARGET_OS_TV
- + (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask {
- #else
- + (NSUInteger)infoPlistSupportedInterfaceOrientationsMask {
- return 0;
- #endif
- NSArray<NSString *> *supportedOrientations = NSBundle.mainBundle.infoDictionary[@"UISupportedInterfaceOrientations"];
- #if !TARGET_OS_TV
- UIInterfaceOrientationMask supportedOrientationsMask = 0;
- if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortrait"]) {
- supportedOrientationsMask |= UIInterfaceOrientationMaskPortrait;
- }
- if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskLandscapeRight"]) {
- supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeRight;
- }
- if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskPortraitUpsideDown"]) {
- supportedOrientationsMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
- }
- if ([supportedOrientations containsObject:@"UIInterfaceOrientationLandscapeLeft"]) {
- supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeLeft;
- }
- return supportedOrientationsMask;
- #endif
- }
- + (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data {
- UIImage *thumbnail = nil;
- CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, 0);
- if (imageSource) {
- NSDictionary<NSString *, id> *options = @{
- (__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
- (__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
- (__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(dimension)
- };
- CGImageRef scaledImageRef = CGImageSourceCreateThumbnailAtIndex(
- imageSource, 0, (__bridge CFDictionaryRef)options
- );
- if (scaledImageRef) {
- thumbnail = [UIImage imageWithCGImage:scaledImageRef];
- CFRelease(scaledImageRef);
- }
- CFRelease(imageSource);
- }
- return thumbnail;
- }
- + (NSString *)stringFromRequestDuration:(NSTimeInterval)duration {
- NSString *string = @"0s";
- if (duration > 0.0) {
- if (duration < 1.0) {
- string = [NSString stringWithFormat:@"%dms", (int)(duration * 1000)];
- } else if (duration < 10.0) {
- string = [NSString stringWithFormat:@"%.2fs", duration];
- } else {
- string = [NSString stringWithFormat:@"%.1fs", duration];
- }
- }
- return string;
- }
- + (NSString *)statusCodeStringFromURLResponse:(NSURLResponse *)response {
- NSString *httpResponseString = nil;
- if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
- NSString *statusCodeDescription = nil;
- if (httpResponse.statusCode == 200) {
- // Prefer OK to the default "no error"
- statusCodeDescription = @"OK";
- } else {
- statusCodeDescription = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
- }
- httpResponseString = [NSString stringWithFormat:@"%ld %@", (long)httpResponse.statusCode, statusCodeDescription];
- }
- return httpResponseString;
- }
- + (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response {
- if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
- return httpResponse.statusCode >= 400;
- }
-
- return NO;
- }
- + (NSArray<NSURLQueryItem *> *)itemsFromQueryString:(NSString *)query {
- NSMutableArray<NSURLQueryItem *> *items = [NSMutableArray new];
- // [a=1, b=2, c=3]
- NSArray<NSString *> *queryComponents = [query componentsSeparatedByString:@"&"];
- for (NSString *keyValueString in queryComponents) {
- // [a, 1]
- NSArray<NSString *> *components = [keyValueString componentsSeparatedByString:@"="];
- if (components.count == 2) {
- NSString *key = components.firstObject.stringByRemovingPercentEncoding;
- NSString *value = components.lastObject.stringByRemovingPercentEncoding;
- [items addObject:[NSURLQueryItem queryItemWithName:key value:value]];
- }
- }
- return items.copy;
- }
- + (NSString *)prettyJSONStringFromData:(NSData *)data {
- NSString *prettyString = nil;
-
- id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
- if ([NSJSONSerialization isValidJSONObject:jsonObject]) {
- // Thanks RaziPour1993
- prettyString = [[NSString alloc]
- initWithData:[NSJSONSerialization
- dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL
- ]
- encoding:NSUTF8StringEncoding
- ];
- // NSJSONSerialization escapes forward slashes.
- // We want pretty json, so run through and unescape the slashes.
- prettyString = [prettyString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
- } else {
- prettyString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- }
-
- return prettyString;
- }
- + (BOOL)isValidJSONData:(NSData *)data {
- return [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL] ? YES : NO;
- }
- // Thanks to the following links for help with this method
- // https://www.cocoanetics.com/2012/02/decompressing-files-into-memory/
- // https://github.com/nicklockwood/GZIP
- + (NSData *)inflatedDataFromCompressedData:(NSData *)compressedData {
- NSData *inflatedData = nil;
- NSUInteger compressedDataLength = compressedData.length;
- if (compressedDataLength > 0) {
- z_stream stream;
- stream.zalloc = Z_NULL;
- stream.zfree = Z_NULL;
- stream.avail_in = (uInt)compressedDataLength;
- stream.next_in = (void *)compressedData.bytes;
- stream.total_out = 0;
- stream.avail_out = 0;
- NSMutableData *mutableData = [NSMutableData dataWithLength:compressedDataLength * 1.5];
- if (inflateInit2(&stream, 15 + 32) == Z_OK) {
- int status = Z_OK;
- while (status == Z_OK) {
- if (stream.total_out >= mutableData.length) {
- mutableData.length += compressedDataLength / 2;
- }
- stream.next_out = (uint8_t *)[mutableData mutableBytes] + stream.total_out;
- stream.avail_out = (uInt)(mutableData.length - stream.total_out);
- status = inflate(&stream, Z_SYNC_FLUSH);
- }
- if (inflateEnd(&stream) == Z_OK) {
- if (status == Z_STREAM_END) {
- mutableData.length = stream.total_out;
- inflatedData = [mutableData copy];
- }
- }
- }
- }
- return inflatedData;
- }
- + (NSArray<UIWindow *> *)allWindows {
- BOOL includeInternalWindows = YES;
- BOOL onlyVisibleWindows = NO;
- // Obfuscating selector allWindowsIncludingInternalWindows:onlyVisibleWindows:
- NSArray<NSString *> *allWindowsComponents = @[
- @"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:"
- ];
- SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
- NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
- invocation.target = [UIWindow class];
- invocation.selector = allWindowsSelector;
- [invocation setArgument:&includeInternalWindows atIndex:2];
- [invocation setArgument:&onlyVisibleWindows atIndex:3];
- [invocation invoke];
- __unsafe_unretained NSArray<UIWindow *> *windows = nil;
- [invocation getReturnValue:&windows];
- return windows;
- }
- + (UIAlertController *)alert:(NSString *)title message:(NSString *)message {
- return [UIAlertController
- alertControllerWithTitle:title
- message:message
- preferredStyle:UIAlertControllerStyleAlert
- ];
- }
- + (SEL)swizzledSelectorForSelector:(SEL)selector {
- return NSSelectorFromString([NSString stringWithFormat:
- @"_flex_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)
- ]);
- }
- + (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls {
- if ([cls instancesRespondToSelector:selector]) {
- unsigned int numMethods = 0;
- Method *methods = class_copyMethodList(cls, &numMethods);
-
- BOOL implementsSelector = NO;
- for (int index = 0; index < numMethods; index++) {
- SEL methodSelector = method_getName(methods[index]);
- if (selector == methodSelector) {
- implementsSelector = YES;
- break;
- }
- }
-
- free(methods);
-
- if (!implementsSelector) {
- return YES;
- }
- }
-
- return NO;
- }
- + (void)replaceImplementationOfKnownSelector:(SEL)originalSelector
- onClass:(Class)class
- withBlock:(id)block
- swizzledSelector:(SEL)swizzledSelector {
- // This method is only intended for swizzling methods that are know to exist on the class.
- // Bail if that isn't the case.
- Method originalMethod = class_getInstanceMethod(class, originalSelector);
- if (!originalMethod) {
- return;
- }
-
- IMP implementation = imp_implementationWithBlock(block);
- class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalMethod));
- Method newMethod = class_getInstanceMethod(class, swizzledSelector);
- method_exchangeImplementations(originalMethod, newMethod);
- }
- + (void)replaceImplementationOfSelector:(SEL)selector
- withSelector:(SEL)swizzledSelector
- forClass:(Class)cls
- withMethodDescription:(struct objc_method_description)methodDescription
- implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock {
- if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
- return;
- }
-
- IMP implementation = imp_implementationWithBlock((id)(
- [cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock)
- );
-
- Method oldMethod = class_getInstanceMethod(cls, selector);
- const char *types = methodDescription.types;
- if (oldMethod) {
- if (!types) {
- types = method_getTypeEncoding(oldMethod);
- }
- class_addMethod(cls, swizzledSelector, implementation, types);
- Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
- method_exchangeImplementations(oldMethod, newMethod);
- } else {
- if (!types) {
- // Some protocol method descriptions don't have .types populated
- // Set the return type to void and ignore arguments
- types = "v@:";
- }
- class_addMethod(cls, selector, implementation, types);
- }
- }
- #if TARGET_OS_TV
- + (BOOL)airdropAvailable {
- return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"airdropper://"]];
- }
-
- + (void)airDropFile:(NSString *)file {
- NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"airdropper://%@", file]];
- UIApplication *application = [UIApplication sharedApplication];
- [application openURL:url options:@{} completionHandler:nil];
- }
-
- #endif
-
- @end
|