FLEXViewExplorerViewController.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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 "FLEXImagePreviewViewController.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. NSArray *propertyNames = @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled", @"layer"];
  45. if ([self.viewToExplore isKindOfClass:[UILabel class]]) {
  46. propertyNames = [@[@"text", @"font", @"textColor"] arrayByAddingObjectsFromArray:propertyNames];
  47. }
  48. return propertyNames;
  49. }
  50. - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
  51. {
  52. NSString *title = nil;
  53. if ([rowCookie isKindOfClass:[NSNumber class]]) {
  54. FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
  55. switch (row) {
  56. case FLEXViewExplorerRowViewController:
  57. title = @"View Controller";
  58. break;
  59. case FLEXViewExplorerRowPreview:
  60. title = @"Preview Image";
  61. break;
  62. }
  63. } else if ([rowCookie isKindOfClass:[NSString class]]) {
  64. objc_property_t property = [self viewPropertyForName:rowCookie];
  65. if (property) {
  66. NSString *prettyPropertyName = [FLEXRuntimeUtility prettyNameForProperty:property];
  67. // Since we're outside of the "properties" section, prepend @property for clarity.
  68. title = [NSString stringWithFormat:@"@property %@", prettyPropertyName];
  69. }
  70. }
  71. return title;
  72. }
  73. - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
  74. {
  75. NSString *subtitle = nil;
  76. if ([rowCookie isKindOfClass:[NSNumber class]]) {
  77. FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
  78. switch (row) {
  79. case FLEXViewExplorerRowViewController:
  80. subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[FLEXUtility viewControllerForView:self.viewToExplore]];
  81. break;
  82. case FLEXViewExplorerRowPreview:
  83. break;
  84. }
  85. } else if ([rowCookie isKindOfClass:[NSString class]]) {
  86. objc_property_t property = [self viewPropertyForName:rowCookie];
  87. if (property) {
  88. id value = [FLEXRuntimeUtility valueForProperty:property onObject:self.viewToExplore];
  89. subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:value];
  90. }
  91. }
  92. return subtitle;
  93. }
  94. - (objc_property_t)viewPropertyForName:(NSString *)propertyName
  95. {
  96. return class_getProperty([self.viewToExplore class], [propertyName UTF8String]);
  97. }
  98. - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
  99. {
  100. return YES;
  101. }
  102. - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
  103. {
  104. UIViewController *drillInViewController = nil;
  105. if ([rowCookie isKindOfClass:[NSNumber class]]) {
  106. FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
  107. switch (row) {
  108. case FLEXViewExplorerRowViewController:
  109. drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:[FLEXUtility viewControllerForView:self.viewToExplore]];
  110. break;
  111. case FLEXViewExplorerRowPreview:
  112. drillInViewController = [[self class] imagePreviewViewControllerForView:self.viewToExplore];
  113. break;
  114. }
  115. } else if ([rowCookie isKindOfClass:[NSString class]]) {
  116. objc_property_t property = [self viewPropertyForName:rowCookie];
  117. if (property) {
  118. id currentValue = [FLEXRuntimeUtility valueForProperty:property onObject:self.viewToExplore];
  119. if ([FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue]) {
  120. drillInViewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.object property:property];
  121. } else {
  122. drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  123. }
  124. }
  125. }
  126. return drillInViewController;
  127. }
  128. + (UIViewController *)imagePreviewViewControllerForView:(UIView *)view
  129. {
  130. UIViewController *imagePreviewViewController = nil;
  131. if (!CGRectIsEmpty(view.bounds)) {
  132. CGSize viewSize = view.bounds.size;
  133. UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
  134. [view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
  135. UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
  136. UIGraphicsEndImageContext();
  137. imagePreviewViewController = [[FLEXImagePreviewViewController alloc] initWithImage:previewImage];
  138. }
  139. return imagePreviewViewController;
  140. }
  141. #pragma mark - Runtime Adjustment
  142. + (void)initialize
  143. {
  144. // A quirk of UIView: a lot of the "@property"s are not actually properties from the perspective of the runtime.
  145. // We add these properties to the class at runtime if they haven't been added yet.
  146. // This way, we can use our property editor to access and change them.
  147. // The property attributes match the declared attributes in UIView.h
  148. NSDictionary *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
  149. [FLEXRuntimeUtility tryAddPropertyWithName:"frame" attributes:frameAttributes toClass:[UIView class]];
  150. NSDictionary *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
  151. [FLEXRuntimeUtility tryAddPropertyWithName:"alpha" attributes:alphaAttributes toClass:[UIView class]];
  152. NSDictionary *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
  153. [FLEXRuntimeUtility tryAddPropertyWithName:"clipsToBounds" attributes:clipsAttributes toClass:[UIView class]];
  154. NSDictionary *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
  155. [FLEXRuntimeUtility tryAddPropertyWithName:"opaque" attributes:opaqueAttributes toClass:[UIView class]];
  156. NSDictionary *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
  157. [FLEXRuntimeUtility tryAddPropertyWithName:"hidden" attributes:hiddenAttributes toClass:[UIView class]];
  158. NSDictionary *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(FLEXEncodeClass(UIColor)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
  159. [FLEXRuntimeUtility tryAddPropertyWithName:"backgroundColor" attributes:backgroundColorAttributes toClass:[UIView class]];
  160. }
  161. @end