FLEXViewExplorerViewController.m 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. //
  2. // FLEXViewExplorerViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/11/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXViewExplorerViewController.h"
  9. #import "FLEXRuntimeUtility.h"
  10. #import "FLEXUtility.h"
  11. #import "FLEXObjectExplorerFactory.h"
  12. #import "FLEXViewSnapshotViewController.h"
  13. #import "FLEXPropertyEditorViewController.h"
  14. typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
  15. FLEXViewExplorerRowViewController,
  16. FLEXViewExplorerRowPreview
  17. };
  18. @interface FLEXViewExplorerViewController ()
  19. // Don't clash with UIViewController's view property
  20. @property (nonatomic, readonly) UIView *viewToExplore;
  21. @end
  22. @implementation FLEXViewExplorerViewController
  23. - (UIView *)viewToExplore
  24. {
  25. return [self.object isKindOfClass:[UIView class]] ? self.object : nil;
  26. }
  27. #pragma mark - Superclass Overrides
  28. - (NSString *)customSectionTitle
  29. {
  30. return @"Shortcuts";
  31. }
  32. - (NSArray *)customSectionRowCookies
  33. {
  34. NSMutableArray *rowCookies = [NSMutableArray array];
  35. if ([FLEXUtility viewControllerForView:self.viewToExplore]) {
  36. [rowCookies addObject:@(FLEXViewExplorerRowViewController)];
  37. }
  38. [rowCookies addObject:@(FLEXViewExplorerRowPreview)];
  39. [rowCookies addObjectsFromArray:[self shortcutPropertyNames]];
  40. return rowCookies;
  41. }
  42. - (NSArray *)shortcutPropertyNames
  43. {
  44. return @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled"];
  45. }
  46. - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
  47. {
  48. NSString *title = nil;
  49. if ([rowCookie isKindOfClass:[NSNumber class]]) {
  50. FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
  51. switch (row) {
  52. case FLEXViewExplorerRowViewController:
  53. title = @"View Controller";
  54. break;
  55. case FLEXViewExplorerRowPreview:
  56. title = @"Preview Image";
  57. break;
  58. }
  59. } else if ([rowCookie isKindOfClass:[NSString class]]) {
  60. objc_property_t property = [self viewPropertyForName:rowCookie];
  61. if (property) {
  62. NSString *prettyPropertyName = [FLEXRuntimeUtility prettyNameForProperty:property];
  63. // Since we're outside of the "properties" section, prepend @property for clarity.
  64. title = [NSString stringWithFormat:@"@property %@", prettyPropertyName];
  65. }
  66. }
  67. return title;
  68. }
  69. - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
  70. {
  71. NSString *subtitle = nil;
  72. if ([rowCookie isKindOfClass:[NSNumber class]]) {
  73. FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
  74. switch (row) {
  75. case FLEXViewExplorerRowViewController:
  76. subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[FLEXUtility viewControllerForView:self.viewToExplore]];
  77. break;
  78. case FLEXViewExplorerRowPreview:
  79. break;
  80. }
  81. } else if ([rowCookie isKindOfClass:[NSString class]]) {
  82. objc_property_t property = [self viewPropertyForName:rowCookie];
  83. if (property) {
  84. id value = [FLEXRuntimeUtility valueForProperty:property onObject:self.viewToExplore];
  85. subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:value];
  86. }
  87. }
  88. return subtitle;
  89. }
  90. - (objc_property_t)viewPropertyForName:(NSString *)propertyName
  91. {
  92. return class_getProperty([self.viewToExplore class], [propertyName UTF8String]);
  93. }
  94. - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
  95. {
  96. return YES;
  97. }
  98. - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
  99. {
  100. UIViewController *drillInViewController = nil;
  101. if ([rowCookie isKindOfClass:[NSNumber class]]) {
  102. FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
  103. switch (row) {
  104. case FLEXViewExplorerRowViewController:
  105. drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:[FLEXUtility viewControllerForView:self.viewToExplore]];
  106. break;
  107. case FLEXViewExplorerRowPreview:
  108. drillInViewController = [[self class] imagePreviewViewControllerForView:self.viewToExplore];
  109. break;
  110. }
  111. } else if ([rowCookie isKindOfClass:[NSString class]]) {
  112. objc_property_t property = [self viewPropertyForName:rowCookie];
  113. if (property) {
  114. drillInViewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.viewToExplore property:property];
  115. }
  116. }
  117. return drillInViewController;
  118. }
  119. + (UIViewController *)imagePreviewViewControllerForView:(UIView *)view
  120. {
  121. UIViewController *imagePreviewViewController = nil;
  122. if (!CGRectIsEmpty(view.bounds)) {
  123. CGSize viewSize = view.bounds.size;
  124. UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
  125. if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
  126. [view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
  127. } else {
  128. CGContextRef imageContext = UIGraphicsGetCurrentContext();
  129. [view.layer renderInContext:imageContext];
  130. }
  131. UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
  132. UIGraphicsEndImageContext();
  133. imagePreviewViewController = [[FLEXViewSnapshotViewController alloc] initWithImage:previewImage];
  134. }
  135. return imagePreviewViewController;
  136. }
  137. #pragma mark - Runtime Adjustment
  138. + (void)initialize
  139. {
  140. // A quirk of UIView: a lot of the "@property"s are not actually properties from the perspective of the runtime.
  141. // We add these properties to the class at runtime if they haven't been added yet.
  142. // This way, we can use our property editor to access and change them.
  143. // The property attributes match the declared attributes in UIView.h
  144. NSDictionary *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
  145. [FLEXRuntimeUtility tryAddPropertyWithName:"frame" attributes:frameAttributes toClass:[UIView class]];
  146. NSDictionary *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
  147. [FLEXRuntimeUtility tryAddPropertyWithName:"alpha" attributes:alphaAttributes toClass:[UIView class]];
  148. NSDictionary *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
  149. [FLEXRuntimeUtility tryAddPropertyWithName:"clipsToBounds" attributes:clipsAttributes toClass:[UIView class]];
  150. NSDictionary *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
  151. [FLEXRuntimeUtility tryAddPropertyWithName:"opaque" attributes:opaqueAttributes toClass:[UIView class]];
  152. NSDictionary *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
  153. [FLEXRuntimeUtility tryAddPropertyWithName:"hidden" attributes:hiddenAttributes toClass:[UIView class]];
  154. NSDictionary *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @"@\"UIColor\"", kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
  155. [FLEXRuntimeUtility tryAddPropertyWithName:"backgroundColor" attributes:backgroundColorAttributes toClass:[UIView class]];
  156. }
  157. @end