FLEXViewShortcuts.m 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. //
  2. // FLEXViewShortcuts.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 12/11/19.
  6. // Copyright © 2019 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXViewShortcuts.h"
  9. #import "FLEXShortcut.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "FLEXObjectExplorerFactory.h"
  12. #import "FLEXImagePreviewViewController.h"
  13. @interface FLEXViewShortcuts ()
  14. @property (nonatomic, readonly) UIView *view;
  15. @end
  16. @implementation FLEXViewShortcuts
  17. #pragma mark - Internal
  18. - (UIView *)view {
  19. return self.object;
  20. }
  21. + (UIViewController *)viewControllerForView:(UIView *)view {
  22. NSString *viewDelegate = @"viewDelegate";
  23. if ([view respondsToSelector:NSSelectorFromString(viewDelegate)]) {
  24. return [view valueForKey:viewDelegate];
  25. }
  26. return nil;
  27. }
  28. + (UIViewController *)viewControllerForAncestralView:(UIView *)view {
  29. NSString *_viewControllerForAncestor = @"_viewControllerForAncestor";
  30. if ([view respondsToSelector:NSSelectorFromString(_viewControllerForAncestor)]) {
  31. return [view valueForKey:_viewControllerForAncestor];
  32. }
  33. return nil;
  34. }
  35. + (UIViewController *)nearestViewControllerForView:(UIView *)view {
  36. return [self viewControllerForView:view] ?: [self viewControllerForAncestralView:view];
  37. }
  38. + (UIViewController *)imagePreviewViewControllerForView:(UIView *)view {
  39. if (!CGRectIsEmpty(view.bounds)) {
  40. CGSize viewSize = view.bounds.size;
  41. UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
  42. [view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
  43. UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
  44. UIGraphicsEndImageContext();
  45. return [FLEXImagePreviewViewController forImage:previewImage];
  46. }
  47. return nil;
  48. }
  49. #pragma mark - Overrides
  50. + (instancetype)forObject:(UIView *)view {
  51. // In the past, FLEX would not hold a strong reference to something like this.
  52. // After using FLEX for so long, I am certain it is more useful to eagerly
  53. // reference something as useful as a view controller so that the reference
  54. // is not lost and swept out from under you before you can access it.
  55. //
  56. // The alternative here is to use a future in place of `controller` which would
  57. // dynamically grab a reference to the view controller. 99% of the time, however,
  58. // it is not all that useful. If you need it to refresh, you can simply go back
  59. // and go forward again and it will show if the view controller is nil or changed.
  60. UIViewController *controller = [FLEXViewShortcuts nearestViewControllerForView:view];
  61. return [self forObject:view additionalRows:@[
  62. [FLEXActionShortcut title:@"Nearest View Controller"
  63. subtitle:^NSString *(id view) {
  64. return [FLEXRuntimeUtility safeDescriptionForObject:controller];
  65. }
  66. viewer:^UIViewController *(id view) {
  67. return [FLEXObjectExplorerFactory explorerViewControllerForObject:controller];
  68. }
  69. accessoryType:^UITableViewCellAccessoryType(id view) {
  70. return controller ? UITableViewCellAccessoryDisclosureIndicator : 0;
  71. }
  72. ],
  73. [FLEXActionShortcut title:@"Preview Image" subtitle:nil
  74. viewer:^UIViewController *(id view) {
  75. return [FLEXViewShortcuts imagePreviewViewControllerForView:view];
  76. }
  77. accessoryType:^UITableViewCellAccessoryType(id view) {
  78. return UITableViewCellAccessoryDisclosureIndicator;
  79. }
  80. ]
  81. ]];
  82. }
  83. #pragma mark - Runtime Adjustment
  84. #define PropertyKey(suffix) kFLEXPropertyAttributeKey##suffix : @""
  85. #define PropertyKeyGetter(getter) kFLEXPropertyAttributeKeyCustomGetter : NSStringFromSelector(@selector(getter))
  86. #define PropertyKeySetter(setter) kFLEXPropertyAttributeKeyCustomSetter : NSStringFromSelector(@selector(setter))
  87. /// Takes: min iOS version, property name, target class, property type, and a list of attributes
  88. #define FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, type, ...) ({ \
  89. if (@available(iOS iOS_atLeast, *)) { \
  90. NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithDictionary:@{ \
  91. kFLEXPropertyAttributeKeyTypeEncoding : @(type), \
  92. __VA_ARGS__ \
  93. }]; \
  94. [FLEXRuntimeUtility \
  95. tryAddPropertyWithName:#name \
  96. attributes:attrs \
  97. toClass:[cls class] \
  98. ]; \
  99. } \
  100. })
  101. /// Takes: min iOS version, property name, target class, property type, and a list of attributes
  102. #define FLEXRuntimeUtilityTryAddNonatomicProperty(iOS_atLeast, name, cls, type, ...) \
  103. FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, @encode(type), PropertyKey(NonAtomic), __VA_ARGS__);
  104. /// Takes: min iOS version, property name, target class, property type (class name), and a list of attributes
  105. #define FLEXRuntimeUtilityTryAddObjectProperty(iOS_atLeast, name, cls, type, ...) \
  106. FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, FLEXEncodeClass(type), PropertyKey(NonAtomic), __VA_ARGS__);
  107. + (void)load {
  108. // A quirk of UIView and some other classes: a lot of the `@property`s are
  109. // not actually properties from the perspective of the runtime.
  110. //
  111. // We add these properties to the class at runtime if they haven't been added yet.
  112. // This way, we can use our property editor to access and change them.
  113. // The property attributes match the declared attributes in their headers.
  114. // UIView, public
  115. FLEXRuntimeUtilityTryAddNonatomicProperty(2, frame, UIView, CGRect);
  116. FLEXRuntimeUtilityTryAddNonatomicProperty(2, alpha, UIView, CGFloat);
  117. FLEXRuntimeUtilityTryAddNonatomicProperty(2, clipsToBounds, UIView, BOOL);
  118. FLEXRuntimeUtilityTryAddNonatomicProperty(2, opaque, UIView, BOOL, PropertyKeyGetter(isOpaque));
  119. FLEXRuntimeUtilityTryAddNonatomicProperty(2, hidden, UIView, BOOL, PropertyKeyGetter(isHidden));
  120. FLEXRuntimeUtilityTryAddObjectProperty(2, backgroundColor, UIView, UIColor, PropertyKey(Copy));
  121. FLEXRuntimeUtilityTryAddObjectProperty(6, constraints, UIView, NSArray, PropertyKey(ReadOnly));
  122. FLEXRuntimeUtilityTryAddObjectProperty(2, subviews, UIView, NSArray, PropertyKey(ReadOnly));
  123. FLEXRuntimeUtilityTryAddObjectProperty(2, superview, UIView, UIView, PropertyKey(ReadOnly));
  124. // UIButton, private
  125. FLEXRuntimeUtilityTryAddObjectProperty(2, font, UIButton, UIFont, PropertyKey(ReadOnly));
  126. }
  127. @end