Explorar el Código

FLEXArgumentInputView refactor part 2.

Rather than passing string arguments to the method calling utility, pass the actual objects or NSValue boxed primitives.
Ryan Olson hace 12 años
padre
commit
31d9209dd8

+ 13 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputJSONObjectView.h

@@ -0,0 +1,13 @@
+//
+//  FLEXArgumentInputJSONObjectView.h
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/15/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXArgumentInputTextView.h"
+
+@interface FLEXArgumentInputJSONObjectView : FLEXArgumentInputTextView
+
+@end

+ 64 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputJSONObjectView.m

@@ -0,0 +1,64 @@
+//
+//  FLEXArgumentInputJSONObjectView.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/15/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXArgumentInputJSONObjectView.h"
+#import "FLEXRuntimeUtility.h"
+
+@implementation FLEXArgumentInputJSONObjectView
+
+- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding
+{
+    self = [super initWithArgumentTypeEncoding:typeEncoding];
+    if (self) {
+        // Start with the numbers and punctuation keyboard since quotes, curly braces, or
+        // square brackets are likely to be the first characters type for the JSON.
+        self.inputTextView.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
+    }
+    return self;
+}
+
+- (void)setInputOutput:(id)inputOutput
+{
+    self.inputTextView.text = [FLEXRuntimeUtility editableJSONStringForObject:inputOutput];
+}
+
+- (id)inputOutput
+{
+    return [FLEXRuntimeUtility objectValueFromEditableJSONString:self.inputTextView.text];
+}
+
++ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
+{
+    // Must be object type.
+    BOOL supported = type[0] == '@';
+    
+    if (supported) {
+        if (value) {
+            // If there's a current value, it must be serializable to JSON
+            supported = [FLEXRuntimeUtility editableJSONStringForObject:value] != nil;
+        } 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, "@\"NSString\"") == 0;
+                isJSONSerializableType = isJSONSerializableType || strcmp(type, "@\"NSNumber\"") == 0;
+                isJSONSerializableType = isJSONSerializableType || strcmp(type, "@\"NSArray\"") == 0;
+                isJSONSerializableType = isJSONSerializableType || strcmp(type, "@\"NSDictionary\"") == 0;
+                
+                supported = isJSONSerializableType;
+            }
+        }
+    }
+    
+    return supported;
+}
+
+@end

+ 13 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputNumberView.h

@@ -0,0 +1,13 @@
+//
+//  FLEXArgumentInputNumberView.h
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/15/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXArgumentInputTextView.h"
+
+@interface FLEXArgumentInputNumberView : FLEXArgumentInputTextView
+
+@end

+ 56 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputNumberView.m

@@ -0,0 +1,56 @@
+//
+//  FLEXArgumentInputNumberView.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/15/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXArgumentInputNumberView.h"
+#import "FLEXRuntimeUtility.h"
+
+@implementation FLEXArgumentInputNumberView
+
+- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding
+{
+    self = [super initWithArgumentTypeEncoding:typeEncoding];
+    if (self) {
+        self.inputTextView.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
+    }
+    return self;
+}
+
+- (void)setInputOutput:(id)inputOutput
+{
+    if ([inputOutput respondsToSelector:@selector(stringValue)]) {
+        self.inputTextView.text = [inputOutput stringValue];
+    }
+}
+
+- (id)inputOutput
+{
+    return [FLEXRuntimeUtility valueForNumberWithObjCType:self.typeEncoding fromInputString:self.inputTextView.text];
+}
+
++ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
+{
+    static NSArray *primitiveTypes = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        primitiveTypes = @[@(@encode(char)),
+                           @(@encode(int)),
+                           @(@encode(short)),
+                           @(@encode(long)),
+                           @(@encode(long long)),
+                           @(@encode(unsigned char)),
+                           @(@encode(unsigned int)),
+                           @(@encode(unsigned short)),
+                           @(@encode(unsigned long)),
+                           @(@encode(unsigned long long)),
+                           @(@encode(float)),
+                           @(@encode(double))];
+    });
+    return [primitiveTypes containsObject:@(type)];
+}
+
+@end

+ 4 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputTextView.h

@@ -10,4 +10,8 @@
 
 @interface FLEXArgumentInputTextView : FLEXArgumentInputView
 
+// For subclass eyes only
+
+@property (nonatomic, strong, readonly) UITextView *inputTextView;
+
 @end

+ 0 - 13
Classes/Editing/Argument Input Views/FLEXArgumentInputTextView.m

@@ -35,19 +35,6 @@
 }
 
 
-#pragma mark - Input/Output
-
-- (void)setInputOutput:(id)inputOutput
-{
-    self.inputTextView.text = inputOutput;
-}
-
-- (id)inputOutput
-{
-    return [self.inputTextView.text copy];
-}
-
-
 #pragma mark - Layout and Sizing
 
 - (void)layoutSubviews

+ 6 - 1
Classes/Editing/Argument Input Views/FLEXArgumentInputView.h

@@ -23,7 +23,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.
-@property (nonatomic, strong) id inputOutput;
+/// Concrete subclasses *must* override both the setter and getter for this property.
+@property (nonatomic) id inputOutput;
 
 /// Setting this value to large will make some argument input views increase the size of their input field(s).
 /// Useful to increase the use of space if there is only one input view on screen (i.e. for property and ivar editing).
@@ -34,6 +35,10 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
 /// If the input view has one or more text views, returns YES when one of them is focused.
 @property (nonatomic, readonly) BOOL inputViewIsFirstResponder;
 
+/// For subclasses to indicate that they can handle editing a field the give type and value.
+/// Used by FLEXArgumentInputViewFactory to create appropriate input views.
++ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value;
+
 // For subclass eyes only
 
 @property (nonatomic, strong, readonly) UILabel *titleLabel;

+ 16 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputView.m

@@ -73,6 +73,22 @@
     return NO;
 }
 
+- (void)setInputOutput:(id)inputOutput
+{
+    // Subclasses should override.
+}
+
+- (id)inputOutput
+{
+    // Subclasses should override.
+    return nil;
+}
+
++ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
+{
+    return NO;
+}
+
 
 #pragma mark - Class Helpers
 

+ 5 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputViewFactory.h

@@ -12,6 +12,11 @@
 
 @interface FLEXArgumentInputViewFactory : NSObject
 
+/// The main factory method for making argument input view subclasses that are the best fit for the type.
 + (FLEXArgumentInputView *)argumentInputViewForTypeEncoding:(const char *)typeEncoding;
 
+/// A way to check if we should try editing a filed given its type encoding and value.
+/// Useful when deciding whether to edit or explore a property, ivar, or NSUserDefaults value.
++ (BOOL)canEditFieldWithTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue;
+
 @end

+ 30 - 2
Classes/Editing/Argument Input Views/FLEXArgumentInputViewFactory.m

@@ -8,13 +8,41 @@
 
 #import "FLEXArgumentInputViewFactory.h"
 #import "FLEXArgumentInputView.h"
-#import "FLEXArgumentInputTextView.h"
+#import "FLEXArgumentInputJSONObjectView.h"
+#import "FLEXArgumentInputNumberView.h"
 
 @implementation FLEXArgumentInputViewFactory
 
 + (FLEXArgumentInputView *)argumentInputViewForTypeEncoding:(const char *)typeEncoding
 {
-    return [[FLEXArgumentInputTextView alloc] initWithArgumentTypeEncoding:typeEncoding];
+    Class subclass = [self argumentInputViewSubclassForTypeEncoding:typeEncoding currentValue:nil];
+    if (!subclass) {
+        // Fall back to a generic FLEXArgumentInputView if we can't find a subclass that supports the type.
+        // The generic input view does not actually allow input, but it still shows the title of the field.
+        subclass = [FLEXArgumentInputView class];
+    }
+    return [[subclass alloc] initWithArgumentTypeEncoding:typeEncoding];
+}
+
++ (Class)argumentInputViewSubclassForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue
+{
+    Class argumentInputViewSubclass = nil;
+    
+    // Note that order is important here since multiple subclasses may support the same type.
+    // An example is the number subclass and the bool subclass for the type @encode(BOOL).
+    // Both work, but we'd prefer to use the bool subclass.
+    if ([FLEXArgumentInputNumberView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
+        argumentInputViewSubclass = [FLEXArgumentInputNumberView class];
+    } else if ([FLEXArgumentInputJSONObjectView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
+        argumentInputViewSubclass = [FLEXArgumentInputJSONObjectView class];
+    }
+    
+    return argumentInputViewSubclass;
+}
+
++ (BOOL)canEditFieldWithTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue
+{
+    return [self argumentInputViewSubclassForTypeEncoding:typeEncoding currentValue:currentValue] != nil;
 }
 
 @end

+ 9 - 9
Classes/Editing/FLEXDefaultEditorViewController.m

@@ -10,6 +10,7 @@
 #import "FLEXFieldEditorView.h"
 #import "FLEXRuntimeUtility.h"
 #import "FLEXArgumentInputView.h"
+#import "FLEXArgumentInputViewFactory.h"
 
 @interface FLEXDefaultEditorViewController ()
 
@@ -41,32 +42,31 @@
     
     self.fieldEditorView.fieldDescription = self.key;
     
-    [self updateTextFieldString];
+    FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:@encode(id)];
+    inputView.backgroundColor = self.view.backgroundColor;
+    inputView.targetSize = FLEXArgumentInputViewSizeLarge;
+    inputView.inputOutput = [self.defaults objectForKey:self.key];
+    self.fieldEditorView.argumentInputViews = @[inputView];
 }
 
 - (void)actionButtonPressed:(id)sender
 {
     [super actionButtonPressed:sender];
     
-    id value = [FLEXRuntimeUtility objectValueFromEditableString:self.firstInputView.inputOutput];
+    id value = self.firstInputView.inputOutput;
     if (value) {
         [self.defaults setObject:value forKey:self.key];
     } else {
         [self.defaults removeObjectForKey:self.key];
     }
     [self.defaults synchronize];
-    [self updateTextFieldString];
-}
 
-- (void)updateTextFieldString
-{
-    id defaultsValue = [self.defaults objectForKey:self.key];
-    self.firstInputView.inputOutput = [FLEXRuntimeUtility editiableDescriptionForObject:defaultsValue];
+    self.firstInputView.inputOutput = [self.defaults objectForKey:self.key];
 }
 
 + (BOOL)canEditDefaultWithValue:(id)currentValue
 {
-    return !currentValue || [FLEXRuntimeUtility editiableDescriptionForObject:currentValue] != nil;
+    return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:@encode(id) currentValue:currentValue];
 }
 
 @end

+ 0 - 3
Classes/Editing/FLEXFieldEditorViewController.h

@@ -15,9 +15,6 @@
 
 - (id)initWithTarget:(id)target;
 
-+ (BOOL)canEditType:(NSString *)typeEncoding currentObjectValue:(id)value;
-+ (NSString *)stringTypeEncoding;
-
 // Convenience accessor since many subclasses only use one input view
 @property (nonatomic, readonly) FLEXArgumentInputView *firstInputView;
 

+ 0 - 74
Classes/Editing/FLEXFieldEditorViewController.m

@@ -89,12 +89,6 @@
     self.fieldEditorView.targetDescription = [NSString stringWithFormat:@"%@ %p", [self.target class], self.target];
     [self.scrollView addSubview:self.fieldEditorView];
     
-    // One argument input view by default. Subclasses can configure the field editor view with more/different argument input views if needed.
-    FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:NULL];
-    inputView.backgroundColor = self.view.backgroundColor;
-    inputView.targetSize = FLEXArgumentInputViewSizeLarge;
-    self.fieldEditorView.argumentInputViews = @[inputView];
-    
     self.setterButton = [[UIBarButtonItem alloc] initWithTitle:[self titleForActionButton] style:UIBarButtonItemStyleDone target:self action:@selector(actionButtonPressed:)];
     self.navigationItem.rightBarButtonItem = self.setterButton;
 }
@@ -124,72 +118,4 @@
     return @"Set";
 }
 
-+ (BOOL)canEditType:(NSString *)typeEncoding currentObjectValue:(id)value
-{
-    // Many primitive types can always be edited (numbers and supported structs).
-    static NSArray *primitiveTypes = nil;
-    static dispatch_once_t onceToken;
-    dispatch_once(&onceToken, ^{
-        primitiveTypes = @[@(@encode(char)),
-                           @(@encode(int)),
-                           @(@encode(short)),
-                           @(@encode(long)),
-                           @(@encode(long long)),
-                           @(@encode(unsigned char)),
-                           @(@encode(unsigned int)),
-                           @(@encode(unsigned short)),
-                           @(@encode(unsigned long)),
-                           @(@encode(unsigned long long)),
-                           @(@encode(float)),
-                           @(@encode(double)),
-                           @(@encode(CGRect)),
-                           @(@encode(CGSize)),
-                           @(@encode(CGPoint)),
-                           @(@encode(CGAffineTransform)),
-                           @(@encode(NSRange)),
-                           @(@encode(UIEdgeInsets)),
-                           @(@encode(UIOffset))];
-    });
-    
-    BOOL canEdit = [primitiveTypes containsObject:typeEncoding];
-    
-    // Object types may be
-    if (!canEdit) {
-        if (value) {
-            // If the current value is non-nil and we can represent it with an editable string, then we can suppor editing.
-            canEdit = canEdit || [FLEXRuntimeUtility editiableDescriptionForObject:value] != nil;
-        } else {
-            // Also always edit types that are explicitly typed NSString, NSNumber, NSArray, or NSDictionary and are nil.
-            // This kind of type encoding is only kept by ivars and properties. Method agruments and return types drop the class from the type encoding.
-            // The editor supports populating these types through NSJSONSerilization parsing of the input string.
-            canEdit = canEdit || [typeEncoding isEqual:[self stringTypeEncoding]];
-            canEdit = canEdit || [typeEncoding isEqual:[self numberTypeEncoding]];
-            canEdit = canEdit || [typeEncoding isEqual:[self arrayTypeEncoding]];
-            canEdit = canEdit || [typeEncoding isEqual:[self dictionaryTypeEncoding]];
-        }
-    }
-    
-    return canEdit;
-}
-
-+ (NSString *)stringTypeEncoding
-{
-    return @"@\"NSString\"";
-}
-
-+ (NSString *)numberTypeEncoding
-{
-    return @"@\"NSNumber\"";
-}
-
-+ (NSString *)arrayTypeEncoding
-{
-    return @"@\"NSArray\"";
-}
-
-+ (NSString *)dictionaryTypeEncoding
-{
-    return @"@\"NSDictionary\"";
-}
-
 @end

+ 9 - 15
Classes/Editing/FLEXIvarEditorViewController.m

@@ -10,6 +10,7 @@
 #import "FLEXFieldEditorView.h"
 #import "FLEXRuntimeUtility.h"
 #import "FLEXArgumentInputView.h"
+#import "FLEXArgumentInputViewFactory.h"
 
 @interface FLEXIvarEditorViewController ()
 
@@ -35,31 +36,24 @@
     
     self.fieldEditorView.fieldDescription = [FLEXRuntimeUtility prettyNameForIvar:self.ivar];
     
-    [self updateTextFieldString];
+    FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:ivar_getTypeEncoding(self.ivar)];
+    inputView.backgroundColor = self.view.backgroundColor;
+    inputView.targetSize = FLEXArgumentInputViewSizeLarge;
+    inputView.inputOutput = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
+    self.fieldEditorView.argumentInputViews = @[inputView];
 }
 
 - (void)actionButtonPressed:(id)sender
 {
     [super actionButtonPressed:sender];
     
-    [self updateIvarFromString:self.firstInputView.inputOutput];
-    [self updateTextFieldString];
-}
-
-- (void)updateTextFieldString
-{
-    id ivarValue = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
-    self.firstInputView.inputOutput = [FLEXRuntimeUtility editiableDescriptionForObject:ivarValue];
-}
-
-- (void)updateIvarFromString:(NSString *)string
-{
-    [FLEXRuntimeUtility setIvar:self.ivar onObject:self.target withInputString:self.firstInputView.inputOutput];
+    [FLEXRuntimeUtility setValue:self.firstInputView.inputOutput forIvar:self.ivar onObject:self.target];
+    self.firstInputView.inputOutput = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
 }
 
 + (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value
 {
-    return [self canEditType:@(ivar_getTypeEncoding(ivar)) currentObjectValue:value];
+    return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
 }
 
 @end

+ 8 - 38
Classes/Editing/FLEXMethodCallingViewController.m

@@ -42,19 +42,12 @@
     NSMutableArray *argumentInputViews = [NSMutableArray array];
     unsigned int argumentIndex = kFLEXNumberOfImplicitArgs;
     for (NSString *methodComponent in methodComponents) {
-        FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:NULL];
-        inputView.backgroundColor = self.view.backgroundColor;
-        inputView.title = methodComponent;
-        
-        // Prepopulate the structs that we parse from strings with the default values.
-        // This shows the intended formatting which would be less clear otherwise.
         char *argumentTypeEncoding = method_copyArgumentType(self.method, argumentIndex);
-        NSValue *defaultValue = [[self class] defaultValueForEncoding:argumentTypeEncoding];
-        if (defaultValue) {
-            inputView.inputOutput = [FLEXRuntimeUtility editiableDescriptionForObject:defaultValue];
-        }
+        FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:argumentTypeEncoding];
         free(argumentTypeEncoding);
         
+        inputView.backgroundColor = self.view.backgroundColor;
+        inputView.title = methodComponent;
         [argumentInputViews addObject:inputView];
         argumentIndex++;
     }
@@ -77,12 +70,12 @@
     
     NSMutableArray *arguments = [NSMutableArray array];
     for (FLEXArgumentInputView *inputView in self.fieldEditorView.argumentInputViews) {
-        NSString *argumentString = inputView.inputOutput;
-        if (!argumentString) {
-            // Use empty string as a placeholder in the array. It will be interpreted as nil.
-            argumentString = @"";
+        id argumentValue = inputView.inputOutput;
+        if (!argumentValue) {
+            // Use NSNulls as placeholders in the array. They will be interpreted as nil arguments.
+            argumentValue = [NSNull null];
         }
-        [arguments addObject:argumentString];
+        [arguments addObject:argumentValue];
     }
     
     id returnedObject = [FLEXRuntimeUtility performSelector:method_getName(self.method) onObject:self.target withArguments:arguments error:NULL];
@@ -94,27 +87,4 @@
     }
 }
 
-+ (NSValue *)defaultValueForEncoding:(const char *)typeEncoding
-{
-    NSValue *value = nil;
-    
-    if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
-        value = [NSValue valueWithCGRect:CGRectZero];
-    } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
-        value = [NSValue valueWithCGPoint:CGPointZero];
-    } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
-        value = [NSValue valueWithCGSize:CGSizeZero];
-    } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
-        value = [NSValue valueWithCGAffineTransform:CGAffineTransformIdentity];
-    } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
-        value = [NSValue valueWithRange:NSMakeRange(0, 0)];
-    } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
-        value = [NSValue valueWithUIEdgeInsets:UIEdgeInsetsZero];
-    } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
-        value = [NSValue valueWithUIOffset:UIOffsetZero];
-    }
-    
-    return value;
-}
-
 @end

+ 13 - 15
Classes/Editing/FLEXPropertyEditorViewController.m

@@ -10,6 +10,7 @@
 #import "FLEXRuntimeUtility.h"
 #import "FLEXFieldEditorView.h"
 #import "FLEXArgumentInputView.h"
+#import "FLEXArgumentInputViewFactory.h"
 
 @interface FLEXPropertyEditorViewController ()
 
@@ -37,33 +38,30 @@
     id currentValue = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
     self.setterButton.enabled = [[self class] canEditProperty:self.property currentValue:currentValue];
     
-    [self updateArgumentInputView];
+    const char *typeEncoding = [[FLEXRuntimeUtility typeEncodingForProperty:self.property] UTF8String];
+    FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:typeEncoding];
+    inputView.backgroundColor = self.view.backgroundColor;
+    inputView.targetSize = FLEXArgumentInputViewSizeLarge;
+    inputView.inputOutput = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
+    self.fieldEditorView.argumentInputViews = @[inputView];
 }
 
 - (void)actionButtonPressed:(id)sender
 {
     [super actionButtonPressed:sender];
     
-    [self updatePropertyFromString:self.firstInputView.inputOutput];
-    [self updateArgumentInputView];
-}
-
-- (void)updateArgumentInputView
-{
-    id propertyValue = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
-    self.firstInputView.inputOutput = [FLEXRuntimeUtility editiableDescriptionForObject:propertyValue];
-}
-
-- (void)updatePropertyFromString:(NSString *)string
-{
+    id userInputObject = self.firstInputView.inputOutput;
+    NSArray *arguments = userInputObject ? @[userInputObject] : nil;
     SEL setterSelector = [FLEXRuntimeUtility setterSelectorForProperty:self.property];
-    NSArray *arguments = string ? @[string] : nil;
     [FLEXRuntimeUtility performSelector:setterSelector onObject:self.target withArguments:arguments error:NULL];
+    
+    self.firstInputView.inputOutput = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
 }
 
 + (BOOL)canEditProperty:(objc_property_t)property currentValue:(id)value
 {
-    BOOL canEditType = [self canEditType:[FLEXRuntimeUtility typeEncodingForProperty:property] currentObjectValue:value];
+    const char *typeEncoding = [[FLEXRuntimeUtility typeEncodingForProperty:property] UTF8String];
+    BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
     BOOL isReadonly = [FLEXRuntimeUtility isReadonlyProperty:property];
     return canEditType && !isReadonly;
 }

+ 4 - 3
Classes/Utility/FLEXRuntimeUtility.h

@@ -26,7 +26,7 @@ extern const unsigned int kFLEXNumberOfImplicitArgs;
 // Ivar Helpers
 + (NSString *)prettyNameForIvar:(Ivar)ivar;
 + (id)valueForIvar:(Ivar)ivar onObject:(id)object;
-+ (void)setIvar:(Ivar)ivar onObject:(id)object withInputString:(NSString *)inputString;
++ (void)setValue:(id)value forIvar:(Ivar)ivar onObject:(id)object;
 
 // Method Helpers
 + (NSString *)prettyNameForMethod:(Method)method isClassMethod:(BOOL)isClassMethod;
@@ -34,7 +34,8 @@ extern const unsigned int kFLEXNumberOfImplicitArgs;
 
 // Method Calling/Field Editing
 + (id)performSelector:(SEL)selector onObject:(id)object withArguments:(NSArray *)arguments error:(NSError * __autoreleasing *)error;
-+ (NSString *)editiableDescriptionForObject:(id)object;
-+ (id)objectValueFromEditableString:(NSString *)string;
++ (NSString *)editableJSONStringForObject:(id)object;
++ (id)objectValueFromEditableJSONString:(NSString *)string;
++ (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString;
 
 @end

+ 97 - 113
Classes/Utility/FLEXRuntimeUtility.m

@@ -25,7 +25,8 @@ static NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding = @"t";
 static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
 typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
     FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
-    FLEXRuntimeUtilityErrorCodeInvocationFailed = 1
+    FLEXRuntimeUtilityErrorCodeInvocationFailed = 1,
+    FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch = 2
 };
 
 // Arguments 0 and 1 are self and _cmd always
@@ -199,27 +200,31 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     } else {
         ptrdiff_t offset = ivar_getOffset(ivar);
         void *pointer = (__bridge void *)object + offset;
-        value = [self valueForPrimitivePointer:pointer objcType:type];
+        value = [self valueForPrimitivePointer:pointer objCType:type];
     }
     return value;
 }
 
-+ (void)setIvar:(Ivar)ivar onObject:(id)object withInputString:(NSString *)inputString
++ (void)setValue:(id)value forIvar:(Ivar)ivar onObject:(id)object
 {
     const char *typeEncodingCString = ivar_getTypeEncoding(ivar);
     if (typeEncodingCString[0] == '@') {
-        // Object - use NSJSONSerialization
-        id ivarValue = [self objectValueFromEditableString:inputString];
-        object_setIvar(object, ivar, ivarValue);
-    } else {
-        // Primitive - try to parse a number or struct from the string based on the type encoding
+        object_setIvar(object, ivar, value);
+    } else if ([value isKindOfClass:[NSValue class]]) {
+        // Primitive - unbox the NSValue.
+        NSValue *valueValue = (NSValue *)value;
+        
+        // Make sure that the box contained the correct type.
+        NSAssert(strcmp([valueValue objCType], typeEncodingCString) == 0, @"Type encoding mismatch (value: %s; ivar: %s) in setting ivar named: %s on object: %@", [valueValue objCType], typeEncodingCString, ivar_getName(ivar), object);
+        
         NSUInteger bufferSize = 0;
         NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
-        void *buffer = malloc(bufferSize);
-        [self getPrimitiveValue:buffer withObjCType:typeEncodingCString fromInputString:inputString];
+        void *buffer = calloc(bufferSize, 1);
+        [valueValue getValue:buffer];
         ptrdiff_t offset = ivar_getOffset(ivar);
         void *pointer = (__bridge void *)object + offset;
         memcpy(pointer, buffer, bufferSize);
+        free(buffer);
     }
 }
 
@@ -288,24 +293,34 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     NSUInteger numberOfArguments = [methodSignature numberOfArguments];
     for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
         NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
-        NSString *argumentString = [arguments count] > argumentsArrayIndex ? [arguments objectAtIndex:argumentsArrayIndex] : nil;
-        const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
-        if (typeEncodingCString[0] == @encode(id)[0] || typeEncodingCString[0] == @encode(Class)[0]) {
-            // Object
-            id argumentObject = [self objectValueFromEditableString:argumentString];
-            [invocation setArgument:&argumentObject atIndex:argumentIndex];
-        } else if (strcmp(typeEncodingCString, @encode(const char *)) == 0 || strcmp(typeEncodingCString, @encode(char *)) == 0) {
-            // C string
-            const char *argumentCString = [argumentString UTF8String];
-            [invocation setArgument:&argumentCString atIndex:argumentIndex];
-        } else {
-            // Primitive number or struct
-            NSUInteger bufferSize = 0;
-            NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
-            void *buffer = malloc(bufferSize);
-            [self getPrimitiveValue:buffer withObjCType:typeEncodingCString fromInputString:argumentString];
-            [invocation setArgument:buffer atIndex:argumentIndex];
-            free(buffer);
+        id argumentObject = [arguments count] > argumentsArrayIndex ? [arguments objectAtIndex:argumentsArrayIndex] : nil;
+        
+        // NSNull in the arguments array can be passed as a placeholder to indicate nil. We only need to set the argument if it will be non-nil.
+        if (argumentObject && ![argumentObject isKindOfClass:[NSNull class]]) {
+            const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
+            if (typeEncodingCString[0] == @encode(id)[0] || typeEncodingCString[0] == @encode(Class)[0]) {
+                // Object
+                [invocation setArgument:&argumentObject atIndex:argumentIndex];
+            } else if ([argumentObject isKindOfClass:[NSValue class]]){
+                // Primitive boxed in NSValue
+                NSValue *argumentValue = (NSValue *)argumentObject;
+                
+                // Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature
+                if (strcmp([argumentValue objCType], typeEncodingCString) != 0) {
+                    if (error) {
+                        NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Type encoding mismatch for agrument at index %lu. Value type: %s; Method argument type: %s.", (unsigned long)argumentsArrayIndex, [argumentValue objCType], typeEncodingCString]};
+                        *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch userInfo:userInfo];
+                    }
+                    return nil;
+                }
+                
+                NSUInteger bufferSize = 0;
+                NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
+                void *buffer = calloc(bufferSize, 1);
+                [argumentValue getValue:buffer];
+                [invocation setArgument:buffer atIndex:argumentIndex];
+                free(buffer);
+            }
         }
     }
     
@@ -336,7 +351,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
             void *returnValue = malloc([methodSignature methodReturnLength]);
             if (returnValue) {
                 [invocation getReturnValue:returnValue];
-                returnObject = [self valueForPrimitivePointer:returnValue objcType:returnType];
+                returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
                 free(returnValue);
             }
         }
@@ -345,31 +360,11 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return returnObject;
 }
 
-+ (NSString *)editiableDescriptionForObject:(id)object
++ (NSString *)editableJSONStringForObject:(id)object
 {
     NSString *editableDescription = nil;
     
-    if ([object isKindOfClass:[NSValue class]]) {
-        NSValue *value = (NSValue *)object;
-        const char *typeEncoding = [value objCType];
-        if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
-            editableDescription = NSStringFromCGRect([value CGRectValue]);
-        } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
-            editableDescription = NSStringFromCGSize([value CGSizeValue]);
-        } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
-            editableDescription = NSStringFromCGPoint([value CGPointValue]);
-        } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
-            editableDescription = NSStringFromCGAffineTransform([value CGAffineTransformValue]);
-        } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
-            editableDescription = NSStringFromRange([value rangeValue]);
-        } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
-            editableDescription = NSStringFromUIEdgeInsets([value UIEdgeInsetsValue]);
-        } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
-            editableDescription = NSStringFromUIOffset([value UIOffsetValue]);
-        }
-    }
-    
-    if (object && !editableDescription) {
+    if (object) {
         // This is a hack to use JSON serialzation for our editable objects.
         // NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary.
         // We always wrap the object inside an array and then strip the outter square braces off the final string.
@@ -383,7 +378,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return editableDescription;
 }
 
-+ (id)objectValueFromEditableString:(NSString *)string
++ (id)objectValueFromEditableJSONString:(NSString *)string
 {
     id value = nil;
     // nil for empty string/whitespace
@@ -393,6 +388,55 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return value;
 }
 
++ (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString
+{
+    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
+    [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
+    NSNumber *number = [formatter numberFromString:inputString];
+    
+    // Make sure we box the number with the correct type encoding so it can be propperly unboxed later via getValue:
+    NSValue *value = nil;
+    if (strcmp(typeEncoding, @encode(char)) == 0) {
+        char primitiveValue = [number charValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(int)) == 0) {
+        int primitiveValue = [number intValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(short)) == 0) {
+        short primitiveValue = [number shortValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(long)) == 0) {
+        long primitiveValue = [number longValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(long long)) == 0) {
+        long long primitiveValue = [number longLongValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(unsigned char)) == 0) {
+        unsigned char primitiveValue = [number unsignedCharValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(unsigned int)) == 0) {
+        unsigned int primitiveValue = [number unsignedIntValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(unsigned short)) == 0) {
+        unsigned short primitiveValue = [number unsignedShortValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(unsigned long)) == 0) {
+        unsigned long primitiveValue = [number unsignedLongValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(unsigned long long)) == 0) {
+        unsigned long long primitiveValue = [number unsignedLongValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(float)) == 0) {
+        float primitiveValue = [number floatValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    } else if (strcmp(typeEncoding, @encode(double)) == 0) {
+        double primitiveValue = [number doubleValue];
+        value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
+    }
+    
+    return value;
+}
+
 
 #pragma mark - Internal Helpers
 
@@ -510,7 +554,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return encodingString;
 }
 
-+ (NSValue *)valueForPrimitivePointer:(void *)pointer objcType:(const char *)type
++ (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type
 {
     // CASE marcro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
 #define CASE(ctype, selectorpart) \
@@ -543,64 +587,4 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return value;
 }
 
-+ (void)getPrimitiveValue:(void *)value withObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString
-{
-    if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
-        *(CGRect *)value = CGRectFromString(inputString);
-    } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
-        *(CGSize *)value = CGSizeFromString(inputString);
-    } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
-        *(CGPoint *)value = CGPointFromString(inputString);
-    } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
-        *(CGAffineTransform *)value = CGAffineTransformFromString(inputString);
-    } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
-        *(NSRange *)value = NSRangeFromString(inputString);
-    } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
-        *(UIEdgeInsets *)value = UIEdgeInsetsFromString(inputString);
-    } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
-        *(UIOffset *)value = UIOffsetFromString(inputString);
-    } else {
-        NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
-        [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
-        NSNumber *number = [formatter numberFromString:inputString];
-        
-        if (strcmp(typeEncoding, @encode(char)) == 0) {
-            // Special case - ask for the string's BOOL value if we couldn't parse a number.
-            // This allows us to take YES/NO/true/false input.
-            if (number) {
-                *(char *)value = [number charValue];
-            } else {
-                *(char *)value = [inputString boolValue];
-            }
-        } else if (strcmp(typeEncoding, @encode(int)) == 0) {
-            *(int *)value = [number intValue];
-        } else if (strcmp(typeEncoding, @encode(short)) == 0) {
-            *(short *)value = [number shortValue];
-        } else if (strcmp(typeEncoding, @encode(long)) == 0) {
-            *(long *)value = [number longValue];
-        } else if (strcmp(typeEncoding, @encode(long long)) == 0) {
-            *(long long *)value = [number longLongValue];
-        } else if (strcmp(typeEncoding, @encode(unsigned char)) == 0) {
-            *(unsigned char *)value = [number unsignedCharValue];
-        } else if (strcmp(typeEncoding, @encode(unsigned int)) == 0) {
-            *(unsigned int *)value = [number unsignedIntValue];
-        } else if (strcmp(typeEncoding, @encode(unsigned short)) == 0) {
-            *(unsigned short *)value = [number unsignedShortValue];
-        } else if (strcmp(typeEncoding, @encode(unsigned long)) == 0) {
-            *(unsigned long *)value = [number unsignedLongValue];
-        } else if (strcmp(typeEncoding, @encode(unsigned long long)) == 0) {
-            *(unsigned long long *)value = [number unsignedLongLongValue];
-        } else if (strcmp(typeEncoding, @encode(float)) == 0) {
-            *(float *)value = [number floatValue];
-        } else if (strcmp(typeEncoding, @encode(double)) == 0) {
-            *(double *)value = [number doubleValue];
-        } else {
-            // If we didn't match one of the supported types, fill the buffer with zeros.
-            NSUInteger bytes = 0;
-            NSGetSizeAndAlignment(typeEncoding, &bytes, NULL);
-            memset(value, 0, bytes);
-        }
-    }
-}
-
 @end