FLEXUtility.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. //
  2. // FLEXUtility.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 4/18/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXUtility.h"
  9. #import "FLEXResources.h"
  10. #import <ImageIO/ImageIO.h>
  11. #import <zlib.h>
  12. @implementation FLEXUtility
  13. + (UIColor *)consistentRandomColorForObject:(id)object
  14. {
  15. CGFloat hue = (((NSUInteger)object >> 4) % 256) / 255.0;
  16. return [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0];
  17. }
  18. + (NSString *)descriptionForView:(UIView *)view includingFrame:(BOOL)includeFrame
  19. {
  20. NSString *description = [[view class] description];
  21. NSString *viewControllerDescription = [[[self viewControllerForView:view] class] description];
  22. if ([viewControllerDescription length] > 0) {
  23. description = [description stringByAppendingFormat:@" (%@)", viewControllerDescription];
  24. }
  25. if (includeFrame) {
  26. description = [description stringByAppendingFormat:@" %@", [self stringForCGRect:view.frame]];
  27. }
  28. if ([view.accessibilityLabel length] > 0) {
  29. description = [description stringByAppendingFormat:@" · %@", view.accessibilityLabel];
  30. }
  31. return description;
  32. }
  33. + (NSString *)stringForCGRect:(CGRect)rect
  34. {
  35. return [NSString stringWithFormat:@"{(%g, %g), (%g, %g)}", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height];
  36. }
  37. + (UIViewController *)viewControllerForView:(UIView *)view
  38. {
  39. UIViewController *viewController = nil;
  40. SEL viewDelSel = NSSelectorFromString([NSString stringWithFormat:@"%@ewDelegate", @"_vi"]);
  41. if ([view respondsToSelector:viewDelSel]) {
  42. #pragma clang diagnostic push
  43. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  44. viewController = [view performSelector:viewDelSel];
  45. #pragma clang diagnostic pop
  46. }
  47. return viewController;
  48. }
  49. + (NSString *)detailDescriptionForView:(UIView *)view
  50. {
  51. return [NSString stringWithFormat:@"frame %@", [self stringForCGRect:view.frame]];
  52. }
  53. + (UIImage *)circularImageWithColor:(UIColor *)color radius:(CGFloat)radius
  54. {
  55. CGFloat diameter = radius * 2.0;
  56. UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), NO, 0.0);
  57. CGContextRef imageContext = UIGraphicsGetCurrentContext();
  58. CGContextSetFillColorWithColor(imageContext, [color CGColor]);
  59. CGContextFillEllipseInRect(imageContext, CGRectMake(0, 0, diameter, diameter));
  60. UIImage *circularImage = UIGraphicsGetImageFromCurrentImageContext();
  61. UIGraphicsEndImageContext();
  62. return circularImage;
  63. }
  64. + (UIColor *)scrollViewGrayColor
  65. {
  66. return [UIColor colorWithRed:239.0/255.0 green:239.0/255.0 blue:244.0/255.0 alpha:1.0];
  67. }
  68. + (UIColor *)hierarchyIndentPatternColor
  69. {
  70. static UIColor *patternColor = nil;
  71. static dispatch_once_t onceToken;
  72. dispatch_once(&onceToken, ^{
  73. UIImage *indentationPatternImage = [FLEXResources hierarchyIndentPattern];
  74. patternColor = [UIColor colorWithPatternImage:indentationPatternImage];
  75. });
  76. return patternColor;
  77. }
  78. + (NSString *)applicationImageName
  79. {
  80. return [[NSBundle mainBundle] executablePath];
  81. }
  82. + (NSString *)applicationName
  83. {
  84. return [[[FLEXUtility applicationImageName] componentsSeparatedByString:@"/"] lastObject];
  85. }
  86. + (NSString *)safeDescriptionForObject:(id)object
  87. {
  88. // Don't assume that we have an NSObject subclass.
  89. // Check to make sure the object responds to the description methods.
  90. NSString *description = nil;
  91. if ([object respondsToSelector:@selector(debugDescription)]) {
  92. description = [object debugDescription];
  93. } else if ([object respondsToSelector:@selector(description)]) {
  94. description = [object description];
  95. }
  96. return description;
  97. }
  98. + (UIFont *)defaultFontOfSize:(CGFloat)size
  99. {
  100. return [UIFont fontWithName:@"HelveticaNeue" size:size];
  101. }
  102. + (UIFont *)defaultTableViewCellLabelFont
  103. {
  104. return [self defaultFontOfSize:12.0];
  105. }
  106. + (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString
  107. {
  108. static NSDictionary *escapingDictionary = nil;
  109. static NSRegularExpression *regex = nil;
  110. static dispatch_once_t onceToken;
  111. dispatch_once(&onceToken, ^{
  112. escapingDictionary = @{ @" " : @"&nbsp;",
  113. @">" : @"&gt;",
  114. @"<" : @"&lt;",
  115. @"&" : @"&amp;",
  116. @"'" : @"&apos;",
  117. @"\"" : @"&quot;",
  118. @"«" : @"&laquo;",
  119. @"»" : @"&raquo;"
  120. };
  121. regex = [NSRegularExpression regularExpressionWithPattern:@"(&|>|<|'|\"|«|»)" options:0 error:NULL];
  122. });
  123. NSMutableString *mutableString = [originalString mutableCopy];
  124. NSArray *matches = [regex matchesInString:mutableString options:0 range:NSMakeRange(0, [mutableString length])];
  125. for (NSTextCheckingResult *result in [matches reverseObjectEnumerator]) {
  126. NSString *foundString = [mutableString substringWithRange:result.range];
  127. NSString *replacementString = escapingDictionary[foundString];
  128. if (replacementString) {
  129. [mutableString replaceCharactersInRange:result.range withString:replacementString];
  130. }
  131. }
  132. return [mutableString copy];
  133. }
  134. + (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask
  135. {
  136. NSArray *supportedOrientations = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"];
  137. UIInterfaceOrientationMask supportedOrientationsMask = 0;
  138. if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortrait"]) {
  139. supportedOrientationsMask |= UIInterfaceOrientationMaskPortrait;
  140. }
  141. if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskLandscapeRight"]) {
  142. supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeRight;
  143. }
  144. if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskPortraitUpsideDown"]) {
  145. supportedOrientationsMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
  146. }
  147. if ([supportedOrientations containsObject:@"UIInterfaceOrientationLandscapeLeft"]) {
  148. supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeLeft;
  149. }
  150. return supportedOrientationsMask;
  151. }
  152. + (NSString *)searchBarPlaceholderText
  153. {
  154. return @"Filter";
  155. }
  156. + (BOOL)isImagePathExtension:(NSString *)extension
  157. {
  158. // https://developer.apple.com/library/ios/documentation/uikit/reference/UIImage_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006890-CH3-SW3
  159. return [@[@"jpg", @"jpeg", @"png", @"gif", @"tiff", @"tif"] containsObject:extension];
  160. }
  161. + (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data
  162. {
  163. UIImage *thumbnail = nil;
  164. CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, 0);
  165. if (imageSource) {
  166. NSDictionary *options = @{ (__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
  167. (__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
  168. (__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(dimension) };
  169. CGImageRef scaledImageRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
  170. if (scaledImageRef) {
  171. thumbnail = [UIImage imageWithCGImage:scaledImageRef];
  172. CFRelease(scaledImageRef);
  173. }
  174. CFRelease(imageSource);
  175. }
  176. return thumbnail;
  177. }
  178. + (NSString *)stringFromRequestDuration:(NSTimeInterval)duration
  179. {
  180. NSString *string = @"0s";
  181. if (duration > 0.0) {
  182. if (duration < 1.0) {
  183. string = [NSString stringWithFormat:@"%dms", (int)(duration * 1000)];
  184. } else if (duration < 10.0) {
  185. string = [NSString stringWithFormat:@"%.2fs", duration];
  186. } else {
  187. string = [NSString stringWithFormat:@"%.1fs", duration];
  188. }
  189. }
  190. return string;
  191. }
  192. + (NSString *)statusCodeStringFromURLResponse:(NSURLResponse *)response
  193. {
  194. NSString *httpResponseString = nil;
  195. if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
  196. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  197. NSString *statusCodeDescription = nil;
  198. if (httpResponse.statusCode == 200) {
  199. // Prefer OK to the default "no error"
  200. statusCodeDescription = @"OK";
  201. } else {
  202. statusCodeDescription = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
  203. }
  204. httpResponseString = [NSString stringWithFormat:@"%ld %@", (long)httpResponse.statusCode, statusCodeDescription];
  205. }
  206. return httpResponseString;
  207. }
  208. + (NSDictionary *)dictionaryFromQuery:(NSString *)query
  209. {
  210. NSMutableDictionary *queryDictionary = [NSMutableDictionary dictionary];
  211. // [a=1, b=2, c=3]
  212. NSArray *queryComponents = [query componentsSeparatedByString:@"&"];
  213. for (NSString *keyValueString in queryComponents) {
  214. // [a, 1]
  215. NSArray *components = [keyValueString componentsSeparatedByString:@"="];
  216. if ([components count] == 2) {
  217. NSString *key = [[components firstObject] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  218. id value = [[components lastObject] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  219. // Handle multiple entries under the same key as an array
  220. id existingEntry = [queryDictionary objectForKey:key];
  221. if (existingEntry) {
  222. if ([existingEntry isKindOfClass:[NSArray class]]) {
  223. value = [existingEntry arrayByAddingObject:value];
  224. } else {
  225. value = @[existingEntry, value];
  226. }
  227. }
  228. [queryDictionary setObject:value forKey:key];
  229. }
  230. }
  231. return queryDictionary;
  232. }
  233. + (NSString *)prettyJSONStringFromData:(NSData *)data
  234. {
  235. NSString *prettyString = nil;
  236. id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
  237. if ([NSJSONSerialization isValidJSONObject:jsonObject]) {
  238. prettyString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL] encoding:NSUTF8StringEncoding];
  239. // NSJSONSerialization escapes forward slashes. We want pretty json, so run through and unescape the slashes.
  240. prettyString = [prettyString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
  241. } else {
  242. prettyString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  243. }
  244. return prettyString;
  245. }
  246. + (NSData *)deflatedDataFromCompressedData:(NSData *)compressedData
  247. {
  248. NSData *deflatedData = nil;
  249. NSUInteger compressedDataLength = [compressedData length];
  250. if (compressedDataLength > 0) {
  251. z_stream stream;
  252. stream.zalloc = Z_NULL;
  253. stream.zfree = Z_NULL;
  254. stream.avail_in = (uInt)compressedDataLength;
  255. stream.next_in = (void *)[compressedData bytes];
  256. stream.total_out = 0;
  257. stream.avail_out = 0;
  258. NSMutableData *mutableData = [NSMutableData dataWithLength:compressedDataLength * 1.5];
  259. if (inflateInit2(&stream, 15 + 32) == Z_OK) {
  260. int status = Z_OK;
  261. while (status == Z_OK) {
  262. if (stream.total_out >= [mutableData length]) {
  263. mutableData.length += compressedDataLength / 2;
  264. }
  265. stream.next_out = (uint8_t *)[mutableData mutableBytes] + stream.total_out;
  266. stream.avail_out = (uInt)([mutableData length] - stream.total_out);
  267. status = inflate(&stream, Z_SYNC_FLUSH);
  268. }
  269. if (inflateEnd(&stream) == Z_OK) {
  270. if (status == Z_STREAM_END) {
  271. mutableData.length = stream.total_out;
  272. deflatedData = [mutableData copy];
  273. }
  274. }
  275. }
  276. }
  277. return deflatedData;
  278. }
  279. @end