Explorar el Código

Add useful UIView properties to the shortcuts section of the view explorer.

We have to do some runtime hacking to make use of our property editor since some of the UIView “properties” are not actually properties.
Ryan Olson hace 12 años
padre
commit
6395bac5c5

+ 4 - 0
Classes/Object Explorers/FLEXViewExplorerViewController.h

@@ -10,4 +10,8 @@
 
 @interface FLEXViewExplorerViewController : FLEXObjectExplorerViewController
 
+/// Array of NSStrings that match property names on the view we're exploring.
+/// Subclasses can override to modify or append properties that should show up in the custom "shortcuts" section.
+- (NSArray *)shortcutPropertyNames;
+
 @end

+ 117 - 35
Classes/Object Explorers/FLEXViewExplorerViewController.m

@@ -15,7 +15,6 @@
 
 typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
     FLEXViewExplorerRowViewController,
-    FLEXViewExplorerRowFrame,
     FLEXViewExplorerRowPreview
 };
 
@@ -43,40 +42,80 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
 
 - (NSArray *)customSectionRowCookies
 {
-    NSArray *rowCookies = @[@(FLEXViewExplorerRowPreview),
-                            @(FLEXViewExplorerRowFrame)];
+    NSMutableArray *rowCookies = [NSMutableArray array];
     
     if ([FLEXUtility viewControllerForView:self.viewToExplore]) {
-        rowCookies = [@[@(FLEXViewExplorerRowViewController)] arrayByAddingObjectsFromArray:rowCookies];
+        [rowCookies addObject:@(FLEXViewExplorerRowViewController)];
     }
     
+    [rowCookies addObject:@(FLEXViewExplorerRowPreview)];
+    [rowCookies addObjectsFromArray:[self shortcutPropertyNames]];
+    
     return rowCookies;
 }
 
+- (NSArray *)shortcutPropertyNames
+{
+    return @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled"];
+}
+
 - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
 {
     NSString *title = nil;
-    if ([rowCookie isEqual:@(FLEXViewExplorerRowViewController)]) {
-        title = @"View Controller";
-    } else if ([rowCookie isEqual:@(FLEXViewExplorerRowFrame)]) {
-        title = @"@property CGRect frame";
-    } else if ([rowCookie isEqual:@(FLEXViewExplorerRowPreview)]) {
-        title = @"Image Preview";
+    
+    if ([rowCookie isKindOfClass:[NSNumber class]]) {
+        FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
+        switch (row) {
+            case FLEXViewExplorerRowViewController:
+                title = @"View Controller";
+                break;
+                
+            case FLEXViewExplorerRowPreview:
+                title = @"Preview Image";
+                break;
+        }
+    } else if ([rowCookie isKindOfClass:[NSString class]]) {
+        objc_property_t property = [self viewPropertyForName:rowCookie];
+        if (property) {
+            NSString *prettyPropertyName = [FLEXRuntimeUtility prettyNameForProperty:property];
+            // Since we're outside of the "properties" section, prepend @property for clarity.
+            title = [NSString stringWithFormat:@"@property %@", prettyPropertyName];
+        }
     }
+    
     return title;
 }
 
 - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
 {
     NSString *subtitle = nil;
-    if ([rowCookie isEqual:@(FLEXViewExplorerRowViewController)]) {
-        subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[FLEXUtility viewControllerForView:self.viewToExplore]];
-    } else if ([rowCookie isEqual:@(FLEXViewExplorerRowFrame)]) {
-        subtitle = [FLEXUtility stringForCGRect:self.viewToExplore.frame];
+    
+    if ([rowCookie isKindOfClass:[NSNumber class]]) {
+        FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
+        switch (row) {
+            case FLEXViewExplorerRowViewController:
+                subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[FLEXUtility viewControllerForView:self.viewToExplore]];
+                break;
+                
+            case FLEXViewExplorerRowPreview:
+                break;
+        }
+    } else if ([rowCookie isKindOfClass:[NSString class]]) {
+        objc_property_t property = [self viewPropertyForName:rowCookie];
+        if (property) {
+            id value = [FLEXRuntimeUtility valueForProperty:property onObject:self.viewToExplore];
+            subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:value];
+        }
     }
+    
     return subtitle;
 }
 
+- (objc_property_t)viewPropertyForName:(NSString *)propertyName
+{
+    return class_getProperty([self.viewToExplore class], [propertyName UTF8String]);
+}
+
 - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
 {
     return YES;
@@ -85,30 +124,73 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
 - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
 {
     UIViewController *drillInViewController = nil;
-    if ([rowCookie isEqual:@(FLEXViewExplorerRowViewController)]) {
-        drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:[FLEXUtility viewControllerForView:self.viewToExplore]];
-    } else if ([rowCookie isEqual:@(FLEXViewExplorerRowFrame)]) {
-        // A quirk of UIView: frame is not actually a property from the perspective of the runtime.
-        // We add the property to the class at runtime if it hasn't been added yet.
-        [FLEXRuntimeUtility addFramePropertyToUIViewIfNeeded];
-        objc_property_t frameProperty = class_getProperty([UIView class], "frame");
-        drillInViewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.viewToExplore property:frameProperty];
-    } else if ([rowCookie isEqual:@(FLEXViewExplorerRowPreview)]) {
-        if (!CGRectIsEmpty(self.viewToExplore.bounds)) {
-            CGSize viewSize = self.viewToExplore.bounds.size;
-            UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
-            if ([self.viewToExplore respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
-                [self.viewToExplore drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
-            } else {
-                CGContextRef imageContext = UIGraphicsGetCurrentContext();
-                [self.viewToExplore.layer renderInContext:imageContext];
-            }
-            UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
-            UIGraphicsEndImageContext();
-            drillInViewController = [[FLEXViewSnapshotViewController alloc] initWithImage:previewImage];
+    
+    if ([rowCookie isKindOfClass:[NSNumber class]]) {
+        FLEXViewExplorerRow row = [rowCookie unsignedIntegerValue];
+        switch (row) {
+            case FLEXViewExplorerRowViewController:
+                drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:[FLEXUtility viewControllerForView:self.viewToExplore]];
+                break;
+                
+            case FLEXViewExplorerRowPreview:
+                drillInViewController = [[self class] imagePreviewViewControllerForView:self.viewToExplore];
+                break;
+        }
+    } else if ([rowCookie isKindOfClass:[NSString class]]) {
+        objc_property_t property = [self viewPropertyForName:rowCookie];
+        if (property) {
+            drillInViewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.viewToExplore property:property];
         }
     }
+
     return drillInViewController;
 }
 
++ (UIViewController *)imagePreviewViewControllerForView:(UIView *)view
+{
+    UIViewController *imagePreviewViewController = nil;
+    if (!CGRectIsEmpty(view.bounds)) {
+        CGSize viewSize = view.bounds.size;
+        UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
+        if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
+            [view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
+        } else {
+            CGContextRef imageContext = UIGraphicsGetCurrentContext();
+            [view.layer renderInContext:imageContext];
+        }
+        UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
+        UIGraphicsEndImageContext();
+        imagePreviewViewController = [[FLEXViewSnapshotViewController alloc] initWithImage:previewImage];
+    }
+    return imagePreviewViewController;
+}
+
+
+#pragma mark - Runtime Adjustment
+
++ (void)initialize
+{
+    // A quirk of UIView: a lot of the "@property"s are not actually properties from the perspective of the runtime.
+    // We add these properties to the class at runtime if they haven't been added yet.
+    // This way, we can use our property editor to access and change them.
+    // The property attributes match the declared attributes in UIView.h
+    NSDictionary *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
+    [FLEXRuntimeUtility tryAddPropertyWithName:"frame" attributes:frameAttributes toClass:[UIView class]];
+    
+    NSDictionary *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
+    [FLEXRuntimeUtility tryAddPropertyWithName:"alpha" attributes:alphaAttributes toClass:[UIView class]];
+    
+    NSDictionary *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
+    [FLEXRuntimeUtility tryAddPropertyWithName:"clipsToBounds" attributes:clipsAttributes toClass:[UIView class]];
+    
+    NSDictionary *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
+    [FLEXRuntimeUtility tryAddPropertyWithName:"opaque" attributes:opaqueAttributes toClass:[UIView class]];
+    
+    NSDictionary *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
+    [FLEXRuntimeUtility tryAddPropertyWithName:"hidden" attributes:hiddenAttributes toClass:[UIView class]];
+    
+    NSDictionary *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @"@\"UIColor\"", kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
+    [FLEXRuntimeUtility tryAddPropertyWithName:"backgroundColor" attributes:backgroundColorAttributes toClass:[UIView class]];
+}
+
 @end