Просмотр исходного кода

Allow passing addresses as params for id args

Allow passing addresses as params for id args
Tanner Bennett лет назад: 7
Родитель
Сommit
e98abb1e23

+ 161 - 24
Classes/Editing/ArgumentInputViews/FLEXArgumentInputObjectView.m

@@ -1,5 +1,5 @@
 //
-//  FLEXArgumentInputObjectView.m
+//  FLEXArgumentInputJSONObjectView.m
 //  Flipboard
 //
 //  Created by Ryan Olson on 6/15/14.
@@ -9,6 +9,20 @@
 #import "FLEXArgumentInputObjectView.h"
 #import "FLEXRuntimeUtility.h"
 
+static const CGFloat kSegmentInputMargin = 4;
+
+typedef NS_ENUM(NSUInteger, FLEXArgInputObjectType) {
+    FLEXArgInputObjectTypeJSON,
+    FLEXArgInputObjectTypeAddress
+};
+
+@interface FLEXArgumentInputObjectView ()
+
+@property (nonatomic) UISegmentedControl *objectTypeSegmentControl;
+@property (nonatomic) FLEXArgInputObjectType inputType;
+
+@end
+
 @implementation FLEXArgumentInputObjectView
 
 - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding
@@ -19,47 +33,170 @@
         // square brackets are likely to be the first characters type for the JSON.
         self.inputTextView.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
         self.targetSize = FLEXArgumentInputViewSizeLarge;
+
+        self.objectTypeSegmentControl = [[UISegmentedControl alloc] initWithItems:@[@"Value", @"Address"]];
+        [self.objectTypeSegmentControl addTarget:self action:@selector(didChangeType) forControlEvents:UIControlEventValueChanged];
+        self.objectTypeSegmentControl.selectedSegmentIndex = 0;
+        [self addSubview:self.objectTypeSegmentControl];
+
+        self.inputType = [[self class] preferredDefaultTypeForObjCType:typeEncoding withCurrentValue:nil];
+        self.objectTypeSegmentControl.selectedSegmentIndex = self.inputType;
     }
+
     return self;
 }
 
+- (void)didChangeType
+{
+    self.inputType = self.objectTypeSegmentControl.selectedSegmentIndex;
+
+    if (super.inputValue) {
+        // Trigger an update to the text field to show
+        // the address of the stored object we were given,
+        // or to show a JSON representation of the object
+        [self populateTextAreaFromValue:super.inputValue];
+    } else {
+        // Clear the text field
+        self.inputValue = nil;
+    }
+}
+
+- (void)setInputType:(FLEXArgInputObjectType)inputType {
+    if (_inputType == inputType) return;
+
+    _inputType = inputType;
+
+    // Resize input view
+    switch (inputType) {
+        case FLEXArgInputObjectTypeJSON:
+            self.targetSize = FLEXArgumentInputViewSizeLarge;
+            break;
+        case FLEXArgInputObjectTypeAddress:
+            self.targetSize = FLEXArgumentInputViewSizeSmall;
+            break;
+    }
+
+    [self setNeedsLayout];
+    [self.superview setNeedsLayout];
+}
+
 - (void)setInputValue:(id)inputValue
 {
-    self.inputTextView.text = [FLEXRuntimeUtility editableJSONStringForObject:inputValue];
+    super.inputValue = inputValue;
+    [self populateTextAreaFromValue:inputValue];
 }
 
 - (id)inputValue
 {
-    return [FLEXRuntimeUtility objectValueFromEditableJSONString:self.inputTextView.text];
+    switch (self.inputType) {
+        case FLEXArgInputObjectTypeJSON:
+            return [FLEXRuntimeUtility objectValueFromEditableJSONString:self.inputTextView.text];
+        case FLEXArgInputObjectTypeAddress: {
+            NSScanner *scanner = [NSScanner scannerWithString:self.inputTextView.text];
+
+            unsigned long long objectPointerValue;
+            if ([scanner scanHexLongLong:&objectPointerValue]) {
+                return (__bridge id)(void *)objectPointerValue;
+            }
+
+            return nil;
+        }
+    }
+}
+
+- (void)populateTextAreaFromValue:(id)value
+{
+    if (self.inputType == FLEXArgInputObjectTypeJSON) {
+        self.inputTextView.text = [FLEXRuntimeUtility editableJSONStringForObject:value];
+    } else if (self.inputType == FLEXArgInputObjectTypeAddress) {
+        self.inputTextView.text = [NSString stringWithFormat:@"%p", value];
+    }
+}
+
+- (CGSize)sizeThatFits:(CGSize)size
+{
+    CGSize fitSize = [super sizeThatFits:size];
+    fitSize.height += [self.objectTypeSegmentControl sizeThatFits:size].height + kSegmentInputMargin;
+
+    return fitSize;
+}
+
+- (void)layoutSubviews {
+    // Must be called first since we are overriding self.inputTextView's position
+    [super layoutSubviews];
+
+    CGFloat segmentHeight = [self.objectTypeSegmentControl sizeThatFits:self.frame.size].height;
+
+    self.inputTextView.frame = CGRectMake(
+        0.0,
+        segmentHeight + self.topInputFieldVerticalLayoutGuide + kSegmentInputMargin,
+        self.inputTextView.frame.size.width,
+        self.inputTextView.frame.size.height
+    );
+    self.objectTypeSegmentControl.frame = CGRectMake(
+        0.0,
+        self.topInputFieldVerticalLayoutGuide,
+        self.frame.size.width,
+        segmentHeight
+    );
 }
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
-    // Must be object type.
-    BOOL supported = type && type[0] == '@';
-    
-    if (supported) {
-        if (value) {
-            // If there's a current value, it must be serializable to JSON
-            supported = [FLEXRuntimeUtility editableJSONStringForObject:value] != nil;
+    NSParameterAssert(type);
+    // Must be object type
+    return type[0] == '@';
+}
+
++ (FLEXArgInputObjectType)preferredDefaultTypeForObjCType:(const char *)type withCurrentValue:(id)value
+{
+    NSParameterAssert(type[0] == '@');
+
+    if (value) {
+        // If there's a current value, it must be serializable to JSON
+        // to display the JSON editor. Otherwise display the address field.
+        if ([FLEXRuntimeUtility editableJSONStringForObject:value]) {
+            return FLEXArgInputObjectTypeJSON;
         } else {
-            // Otherwise, see if we have more type information than just 'id'.
-            // If we do, make sure the encoding is something serializable to JSON.
-            // Properties and ivars keep more detailed type encoding information than method arguments.
-            if (strcmp(type, @encode(id)) != 0) {
-                BOOL isJSONSerializableType = NO;
-                // Note: we can't use @encode(NSString) here because that drops the string information and just goes to @encode(id).
-                isJSONSerializableType = isJSONSerializableType || strcmp(type, FLEXEncodeClass(NSString)) == 0;
-                isJSONSerializableType = isJSONSerializableType || strcmp(type, FLEXEncodeClass(NSNumber)) == 0;
-                isJSONSerializableType = isJSONSerializableType || strcmp(type, FLEXEncodeClass(NSArray)) == 0;
-                isJSONSerializableType = isJSONSerializableType || strcmp(type, FLEXEncodeClass(NSDictionary)) == 0;
-                
-                supported = isJSONSerializableType;
+            return FLEXArgInputObjectTypeAddress;
+        }
+    } else {
+        // Otherwise, see if we have more type information than just 'id'.
+        // If we do, make sure the encoding is something serializable to JSON.
+        // Properties and ivars keep more detailed type encoding information than method arguments.
+        if (strcmp(type, @encode(id)) != 0) {
+            BOOL isJSONSerializableType = NO;
+
+            // Note: we can't use @encode(NSString) here because that drops
+            // the class information and just goes to @encode(id).
+            NSArray *jsonTypes = @[
+                @(FLEXEncodeClass(NSString)),
+                @(FLEXEncodeClass(NSNumber)),
+                @(FLEXEncodeClass(NSArray)),
+                @(FLEXEncodeClass(NSDictionary)),
+                @(FLEXEncodeClass(NSMutableString)),
+                @(FLEXEncodeClass(NSMutableArray)),
+                @(FLEXEncodeClass(NSMutableDictionary)),
+            ];
+
+            // Look for matching types
+            NSString *typeStr = @(type);
+            for (NSString *encodedClass in jsonTypes) {
+                if ([typeStr isEqualToString:encodedClass]) {
+                    isJSONSerializableType = YES;
+                    break;
+                }
             }
+
+            if (isJSONSerializableType) {
+                return FLEXArgInputObjectTypeJSON;
+            } else {
+                return FLEXArgInputObjectTypeAddress;
+            }
+        } else {
+            return FLEXArgInputObjectTypeAddress;
         }
     }
-    
-    return supported;
 }
 
 @end

+ 3 - 10
Classes/Editing/ArgumentInputViews/FLEXArgumentInputTextView.m

@@ -82,21 +82,14 @@
 
 - (NSUInteger)numberOfInputLines
 {
-    NSUInteger numberOfInputLines = 0;
     switch (self.targetSize) {
         case FLEXArgumentInputViewSizeDefault:
-            numberOfInputLines = 2;
-            break;
-            
+            return 2;
         case FLEXArgumentInputViewSizeSmall:
-            numberOfInputLines = 1;
-            break;
-            
+            return 1;
         case FLEXArgumentInputViewSizeLarge:
-            numberOfInputLines = 8;
-            break;
+            return 8;
     }
-    return numberOfInputLines;
 }
 
 - (CGFloat)inputTextViewHeight

+ 2 - 1
Classes/Editing/ArgumentInputViews/FLEXArgumentInputView.h

@@ -26,7 +26,8 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
 /// To populate the filed with an initial value, set this property.
 /// To reteive the value input by the user, access the property.
 /// Primitive types and structs should/will be boxed in NSValue containers.
-/// Concrete subclasses *must* override both the setter and getter for this property.
+/// Concrete subclasses should override both the setter and getter for this property.
+/// Subclasses can call super.inputValue to access a backing store for the value.
 @property (nonatomic) id inputValue;
 
 /// Setting this value to large will make some argument input views increase the size of their input field(s).

+ 0 - 11
Classes/Editing/ArgumentInputViews/FLEXArgumentInputView.m

@@ -89,17 +89,6 @@
     return NO;
 }
 
-- (void)setInputValue:(id)inputValue
-{
-    // Subclasses should override.
-}
-
-- (id)inputValue
-{
-    // Subclasses should override.
-    return nil;
-}
-
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
     return NO;