Bläddra i källkod

FLEX*EditorViewController overhaul

There was a lot of code duplication between the property and ivar editor classes. This fixes that.

- Renamed: FLEXFieldEditorViewController → FLEXVariableEditorViewController
- Renamed: FLEXMutableFieldEditorViewController → FLEXFieldEditorViewController
- Collapsed FLEXPropertyEditorViewController and FLEXIvarEditorViewController into their parent class, FLEXFieldEditorViewController
- Property/ivar editor now takes a FLEXProperty/FLEXIvar
- Property/ivar editor initializer is failable based on editability of property/ivar
- FLEXMethodCallingViewController now takes a FLEXMethod
- Argument input views will now generally allow editing of a nil value
Tanner Bennett 6 år sedan
förälder
incheckning
d31e1aeb4e
24 ändrade filer med 415 tillägg och 476 borttagningar
  1. 6 1
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputColorView.m
  2. 2 1
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputDateView.m
  3. 2 3
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputFontView.m
  4. 2 0
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputNumberView.m
  5. 9 4
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputStringView.m
  6. 14 1
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputStructView.m
  7. 2 1
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputSwitchView.m
  8. 1 2
      Classes/Editing/ArgumentInputViews/FLEXArgumentInputViewFactory.h
  9. 2 2
      Classes/Editing/FLEXDefaultEditorViewController.h
  10. 1 1
      Classes/Editing/FLEXFieldEditorView.h
  11. 17 20
      Classes/Editing/FLEXFieldEditorViewController.h
  12. 113 92
      Classes/Editing/FLEXFieldEditorViewController.m
  13. 0 18
      Classes/Editing/FLEXIvarEditorViewController.h
  14. 0 86
      Classes/Editing/FLEXIvarEditorViewController.m
  15. 4 4
      Classes/Editing/FLEXMethodCallingViewController.h
  16. 49 49
      Classes/Editing/FLEXMethodCallingViewController.m
  17. 0 18
      Classes/Editing/FLEXMutableFieldEditorViewController.h
  18. 0 36
      Classes/Editing/FLEXMutableFieldEditorViewController.m
  19. 0 18
      Classes/Editing/FLEXPropertyEditorViewController.h
  20. 0 100
      Classes/Editing/FLEXPropertyEditorViewController.m
  21. 35 0
      Classes/Editing/FLEXVariableEditorViewController.h
  22. 147 0
      Classes/Editing/FLEXVariableEditorViewController.m
  23. 1 1
      Classes/ObjectExplorers/Controllers/FLEXViewExplorerViewController.m
  24. 8 18
      FLEX.xcodeproj/project.pbxproj

+ 6 - 1
Classes/Editing/ArgumentInputViews/FLEXArgumentInputColorView.m

@@ -248,6 +248,8 @@
             UIColor *color = [[UIColor alloc] initWithCGColor:colorRef];
             [self updateWithColor:color];
         }
+    } else {
+        [self updateWithColor:[UIColor clearColor]];
     }
 }
 
@@ -323,7 +325,10 @@
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
-    return (type && (strcmp(type, @encode(CGColorRef)) == 0 || strcmp(type, FLEXEncodeClass(UIColor)) == 0)) || [value isKindOfClass:[UIColor class]];
+    NSParameterAssert(type);
+
+    // We don't care if currentValue is a color or not; we will default to +clearColor
+    return (strcmp(type, @encode(CGColorRef)) == 0) || (strcmp(type, FLEXEncodeClass(UIColor)) == 0);
 }
 
 @end

+ 2 - 1
Classes/Editing/ArgumentInputViews/FLEXArgumentInputDateView.m

@@ -57,7 +57,8 @@
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
-    return (type && (strcmp(type, FLEXEncodeClass(NSDate)) == 0)) || [value isKindOfClass:[NSDate class]];
+    NSParameterAssert(type);
+    return strcmp(type, FLEXEncodeClass(NSDate)) == 0;
 }
 
 @end

+ 2 - 3
Classes/Editing/ArgumentInputViews/FLEXArgumentInputFontView.m

@@ -113,9 +113,8 @@
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
-    BOOL supported = type && strcmp(type, FLEXEncodeClass(UIFont)) == 0;
-    supported = supported || (value && [value isKindOfClass:[UIFont class]]);
-    return supported;
+    NSParameterAssert(type);
+    return strcmp(type, FLEXEncodeClass(UIFont)) == 0;
 }
 
 @end

+ 2 - 0
Classes/Editing/ArgumentInputViews/FLEXArgumentInputNumberView.m

@@ -35,6 +35,8 @@
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
+    NSParameterAssert(type);
+    
     static NSArray<NSString *> *primitiveTypes = nil;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{

+ 9 - 4
Classes/Editing/ArgumentInputViews/FLEXArgumentInputStringView.m

@@ -22,7 +22,11 @@
 
 - (void)setInputValue:(id)inputValue
 {
-    self.inputTextView.text = inputValue;
+    if ([inputValue isKindOfClass:[NSString class]]) {
+        self.inputTextView.text = inputValue;
+    }
+
+    // TODO: support NSValue coontaining char *
 }
 
 - (id)inputValue
@@ -38,9 +42,10 @@
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
-    BOOL supported = type && strcmp(type, FLEXEncodeClass(NSString)) == 0;
-    supported = supported || (value && [value isKindOfClass:[NSString class]]);
-    return supported;
+    NSParameterAssert(type);
+    BOOL typeIsString = strcmp(type, FLEXEncodeClass(NSString)) == 0;
+    BOOL valueIsNilOrString = (!value || [value isKindOfClass:[NSString class]]);
+    return typeIsString && valueIsNilOrString;
 }
 
 @end

+ 14 - 1
Classes/Editing/ArgumentInputViews/FLEXArgumentInputStructView.m

@@ -176,7 +176,20 @@
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
-    return type && type[0] == FLEXTypeEncodingStructBegin;
+    NSParameterAssert(type);
+    if (type[0] == FLEXTypeEncodingStructBegin) {
+        // We cannot support anything with bitfields or structs,
+        // and this will throw an exception if it does
+        @try {
+            NSGetSizeAndAlignment(type, nil, nil);
+        } @catch (NSException *exception) {
+            return NO;
+        }
+
+        return YES;
+    }
+
+    return NO;
 }
 
 + (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding

+ 2 - 1
Classes/Editing/ArgumentInputViews/FLEXArgumentInputSwitchView.m

@@ -80,8 +80,9 @@
 
 + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
 {
+    NSParameterAssert(type);
     // Only BOOLs. Current value is irrelevant.
-    return type && strcmp(type, @encode(BOOL)) == 0;
+    return strcmp(type, @encode(BOOL)) == 0;
 }
 
 @end

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

@@ -7,8 +7,7 @@
 //
 
 #import <Foundation/Foundation.h>
-
-@class FLEXArgumentInputView;
+#import "FLEXArgumentInputSwitchView.h"
 
 @interface FLEXArgumentInputViewFactory : NSObject
 

+ 2 - 2
Classes/Editing/FLEXDefaultEditorViewController.h

@@ -6,9 +6,9 @@
 //  Copyright (c) 2014 Flipboard. All rights reserved.
 //
 
-#import "FLEXMutableFieldEditorViewController.h"
+#import "FLEXFieldEditorViewController.h"
 
-@interface FLEXDefaultEditorViewController : FLEXMutableFieldEditorViewController
+@interface FLEXDefaultEditorViewController : FLEXFieldEditorViewController
 
 - (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
 

+ 1 - 1
Classes/Editing/FLEXFieldEditorView.h

@@ -15,6 +15,6 @@
 @property (nonatomic, copy) NSString *targetDescription;
 @property (nonatomic, copy) NSString *fieldDescription;
 
-@property (nonatomic) NSArray<FLEXArgumentInputView *> *argumentInputViews;
+@property (nonatomic, copy) NSArray<FLEXArgumentInputView *> *argumentInputViews;
 
 @end

+ 17 - 20
Classes/Editing/FLEXFieldEditorViewController.h

@@ -1,32 +1,29 @@
 //
 //  FLEXFieldEditorViewController.h
-//  Flipboard
+//  FLEX
 //
-//  Created by Ryan Olson on 5/16/14.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
+//  Created by Tanner on 11/22/18.
+//  Copyright © 2018 Flipboard. All rights reserved.
 //
 
-#import <UIKit/UIKit.h>
+#import "FLEXVariableEditorViewController.h"
+#import "FLEXProperty.h"
+#import "FLEXIvar.h"
 
-@class FLEXFieldEditorView;
-@class FLEXArgumentInputView;
+NS_ASSUME_NONNULL_BEGIN
 
-@interface FLEXFieldEditorViewController : UIViewController
+@interface FLEXFieldEditorViewController : FLEXVariableEditorViewController
 
-- (id)initWithTarget:(id)target;
+/// @return nil if the property is readonly or if the type is unsupported
++ (nullable instancetype)target:(id)target property:(FLEXProperty *)property;
+/// @return nil if the ivar type is unsupported
++ (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar;
 
-// Convenience accessor since many subclasses only use one input view
-@property (nonatomic, readonly) FLEXArgumentInputView *firstInputView;
+/// Subclasses can change the button title via the \c title property
+@property (nonatomic, readonly) UIBarButtonItem *getterButton;
 
-// For subclass use only.
-@property (nonatomic, readonly) id target;
-@property (nonatomic, readonly) FLEXFieldEditorView *fieldEditorView;
-@property (nonatomic, readonly) UIBarButtonItem *setterButton;
-
-- (void)actionButtonPressed:(id)sender;
-- (NSString *)titleForActionButton;
-/// Pushes an explorer view controller for the given object
-/// or pops the current view controller.
-- (void)exploreObjectOrPopViewController:(id)objectOrNil;
+- (void)getterButtonPressed:(id)sender;
 
 @end
+
+NS_ASSUME_NONNULL_END

+ 113 - 92
Classes/Editing/FLEXFieldEditorViewController.m

@@ -1,132 +1,153 @@
 //
 //  FLEXFieldEditorViewController.m
-//  Flipboard
+//  FLEX
 //
-//  Created by Ryan Olson on 5/16/14.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
+//  Created by Tanner on 11/22/18.
+//  Copyright © 2018 Flipboard. All rights reserved.
 //
 
-#import "FLEXColor.h"
 #import "FLEXFieldEditorViewController.h"
 #import "FLEXFieldEditorView.h"
-#import "FLEXRuntimeUtility.h"
-#import "FLEXUtility.h"
-#import "FLEXObjectExplorerFactory.h"
-#import "FLEXArgumentInputView.h"
 #import "FLEXArgumentInputViewFactory.h"
-#import "FLEXObjectExplorerViewController.h"
+#import "FLEXPropertyAttributes.h"
+#import "FLEXUtility.h"
 
-@interface FLEXFieldEditorViewController () <UIScrollViewDelegate>
+@interface FLEXFieldEditorViewController () <FLEXArgumentInputViewDelegate>
 
-@property (nonatomic) UIScrollView *scrollView;
+@property (nonatomic) FLEXProperty *property;
+@property (nonatomic) FLEXIvar *ivar;
 
-@property (nonatomic, readwrite) id target;
-@property (nonatomic, readwrite) FLEXFieldEditorView *fieldEditorView;
-@property (nonatomic, readwrite) UIBarButtonItem *setterButton;
+@property (nonatomic, readonly) id currentValue;
+@property (nonatomic, readonly) const FLEXTypeEncoding *typeEncoding;
+@property (nonatomic, readonly) NSString *fieldDescription;
 
 @end
 
 @implementation FLEXFieldEditorViewController
 
-- (id)initWithTarget:(id)target
-{
-    self = [super initWithNibName:nil bundle:nil];
-    if (self) {
-        self.target = target;
-        [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
-        [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
+#pragma mark - Initialization
+
++ (instancetype)target:(id)target property:(FLEXProperty *)property {
+    id value = [FLEXRuntimeUtility valueForProperty:property.objc_property onObject:target];
+    if (![self canEditProperty:property onObject:target currentValue:value]) {
+        return nil;
     }
-    return self;
+
+    FLEXFieldEditorViewController *editor = [self target:target];
+    editor.title = @"Property";
+    editor.property = property;
+    return editor;
 }
 
-- (void)dealloc
-{
-    [NSNotificationCenter.defaultCenter removeObserver:self];
++ (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar {
+    FLEXFieldEditorViewController *editor = [self target:target];
+    editor.title = @"Instance Variable";
+    editor.ivar = ivar;
+    return editor;
 }
 
-- (void)keyboardDidShow:(NSNotification *)notification
-{
-    CGRect keyboardRectInWindow = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
-    CGSize keyboardSize = [self.view convertRect:keyboardRectInWindow fromView:nil].size;
-    UIEdgeInsets scrollInsets = self.scrollView.contentInset;
-    scrollInsets.bottom = keyboardSize.height;
-    self.scrollView.contentInset = scrollInsets;
-    self.scrollView.scrollIndicatorInsets = scrollInsets;
-    
-    // Find the active input view and scroll to make sure it's visible.
-    for (FLEXArgumentInputView *argumentInputView in self.fieldEditorView.argumentInputViews) {
-        if (argumentInputView.inputViewIsFirstResponder) {
-            CGRect scrollToVisibleRect = [self.scrollView convertRect:argumentInputView.bounds fromView:argumentInputView];
-            [self.scrollView scrollRectToVisible:scrollToVisibleRect animated:YES];
-            break;
-        }
+#pragma mark - Overrides
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+
+    // Create getter button
+    _getterButton = [[UIBarButtonItem alloc]
+        initWithTitle:@"Get"
+        style:UIBarButtonItemStyleDone
+        target:self
+        action:@selector(getterButtonPressed:)
+    ];
+    self.navigationItem.rightBarButtonItems = @[self.setterButton, self.getterButton];
+
+    // Configure input view
+    self.fieldEditorView.fieldDescription = self.fieldDescription;
+    FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:self.typeEncoding];
+    inputView.inputValue = self.currentValue;
+    inputView.backgroundColor = self.view.backgroundColor;
+    inputView.delegate = self;
+    self.fieldEditorView.argumentInputViews = @[inputView];
+
+    // Don't show a "set" button for switches; we mutate when the switch is flipped
+    if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
+        self.navigationItem.rightBarButtonItem = nil;
     }
 }
 
-- (void)keyboardWillHide:(NSNotification *)notification
-{
-    UIEdgeInsets scrollInsets = self.scrollView.contentInset;
-    scrollInsets.bottom = 0.0;
-    self.scrollView.contentInset = scrollInsets;
-    self.scrollView.scrollIndicatorInsets = scrollInsets;
-}
+- (void)actionButtonPressed:(id)sender {
+    [super actionButtonPressed:sender];
+
+    if (self.property) {
+        id userInputObject = self.firstInputView.inputValue;
+        NSArray *arguments = userInputObject ? @[userInputObject] : nil;
+        SEL setterSelector = self.property.likelySetter;
+        NSError *error = nil;
+        [FLEXRuntimeUtility performSelector:setterSelector onObject:self.target withArguments:arguments error:&error];
+        if (error) {
+            [FLEXAlert showAlert:@"Property Setter Failed" message:error.localizedDescription from:self];
+            sender = nil; // Don't pop back
+        }
+    } else {
+        // TODO: check mutability and use mutableCopy if necessary;
+        // this currently could and would assign NSArray to NSMutableArray
 
-- (void)viewDidLoad
-{
-    [super viewDidLoad];
-    
-    self.view.backgroundColor = [FLEXColor scrollViewBackgroundColor];
-    
-    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
-    self.scrollView.backgroundColor = self.view.backgroundColor;
-    self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
-    self.scrollView.delegate = self;
-    [self.view addSubview:self.scrollView];
-    
-    self.fieldEditorView = [FLEXFieldEditorView new];
-    self.fieldEditorView.backgroundColor = self.view.backgroundColor;
-    self.fieldEditorView.targetDescription = [NSString stringWithFormat:@"%@ %p", [self.target class], self.target];
-    [self.scrollView addSubview:self.fieldEditorView];
-    
-    self.setterButton = [[UIBarButtonItem alloc] initWithTitle:[self titleForActionButton] style:UIBarButtonItemStyleDone target:self action:@selector(actionButtonPressed:)];
-    self.navigationItem.rightBarButtonItem = self.setterButton;
+        [FLEXRuntimeUtility setValue:self.firstInputView.inputValue forIvar:self.ivar.objc_ivar onObject:self.target];
+    }
+
+    // Go back after setting, but not for switches.
+    if (sender) {
+        [self.navigationController popViewControllerAnimated:YES];
+    } else {
+        self.firstInputView.inputValue = self.currentValue;
+    }
 }
 
-- (void)viewWillLayoutSubviews
-{
-    CGSize constrainSize = CGSizeMake(self.scrollView.bounds.size.width, CGFLOAT_MAX);
-    CGSize fieldEditorSize = [self.fieldEditorView sizeThatFits:constrainSize];
-    self.fieldEditorView.frame = CGRectMake(0, 0, fieldEditorSize.width, fieldEditorSize.height);
-    self.scrollView.contentSize = fieldEditorSize;
+- (void)getterButtonPressed:(id)sender {
+    [self.fieldEditorView endEditing:YES];
+
+    [self exploreObjectOrPopViewController:self.currentValue];
 }
 
-- (FLEXArgumentInputView *)firstInputView
-{
-    return [self.fieldEditorView argumentInputViews].firstObject;
+- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView {
+    if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
+        [self actionButtonPressed:nil];
+    }
 }
 
-- (void)actionButtonPressed:(id)sender
-{
-    // Subclasses can override
-    [self.fieldEditorView endEditing:YES];
+#pragma mark - Private
+
+- (id)currentValue {
+    if (self.property) {
+        return [FLEXRuntimeUtility valueForProperty:self.property.objc_property onObject:self.target];
+    } else {
+        return [FLEXRuntimeUtility valueForIvar:self.ivar.objc_ivar onObject:self.target];
+    }
 }
 
-- (NSString *)titleForActionButton
-{
-    // Subclasses can override.
-    return @"Set";
+- (const FLEXTypeEncoding *)typeEncoding {
+    if (self.property) {
+        return self.property.attributes.typeEncoding.UTF8String;
+    } else {
+        return self.ivar.typeEncoding.UTF8String;
+    }
 }
 
-- (void)exploreObjectOrPopViewController:(id)objectOrNil {
-    if (objectOrNil) {
-        // For non-nil (or void) return types, push an explorer view controller to display the object
-        FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:objectOrNil];
-        [self.navigationController pushViewController:explorerViewController animated:YES];
+- (NSString *)fieldDescription {
+    if (self.property) {
+        return [FLEXRuntimeUtility fullDescriptionForProperty:self.property.objc_property];
     } else {
-        // If we didn't get a returned object but the method call succeeded,
-        // pop this view controller off the stack to indicate that the call went through.
-        [self.navigationController popViewControllerAnimated:YES];
+        return [FLEXRuntimeUtility prettyNameForIvar:self.ivar.objc_ivar];
     }
 }
 
++ (BOOL)canEditProperty:(FLEXProperty *)property onObject:(id)object currentValue:(id)value {
+    const FLEXTypeEncoding *typeEncoding = property.attributes.typeEncoding.UTF8String;
+    BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
+    return canEditType && [object respondsToSelector:property.likelySetter];
+}
+
++ (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value {
+    return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
+}
+
 @end

+ 0 - 18
Classes/Editing/FLEXIvarEditorViewController.h

@@ -1,18 +0,0 @@
-//
-//  FLEXIvarEditorViewController.h
-//  Flipboard
-//
-//  Created by Ryan Olson on 5/23/14.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXMutableFieldEditorViewController.h"
-#import <objc/runtime.h>
-
-@interface FLEXIvarEditorViewController : FLEXMutableFieldEditorViewController
-
-- (id)initWithTarget:(id)target ivar:(Ivar)ivar;
-
-+ (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value;
-
-@end

+ 0 - 86
Classes/Editing/FLEXIvarEditorViewController.m

@@ -1,86 +0,0 @@
-//
-//  FLEXIvarEditorViewController.m
-//  Flipboard
-//
-//  Created by Ryan Olson on 5/23/14.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXIvarEditorViewController.h"
-#import "FLEXFieldEditorView.h"
-#import "FLEXRuntimeUtility.h"
-#import "FLEXArgumentInputView.h"
-#import "FLEXArgumentInputViewFactory.h"
-#import "FLEXArgumentInputSwitchView.h"
-
-@interface FLEXIvarEditorViewController () <FLEXArgumentInputViewDelegate>
-
-@property (nonatomic) Ivar ivar;
-
-@end
-
-@implementation FLEXIvarEditorViewController
-
-- (id)initWithTarget:(id)target ivar:(Ivar)ivar
-{
-    self = [super initWithTarget:target];
-    if (self) {
-        self.ivar = ivar;
-        self.title = @"Instance Variable";
-    }
-    return self;
-}
-
-- (void)viewDidLoad
-{
-    [super viewDidLoad];
-    
-    self.fieldEditorView.fieldDescription = [FLEXRuntimeUtility prettyNameForIvar:self.ivar];
-    
-    FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:ivar_getTypeEncoding(self.ivar)];
-    inputView.backgroundColor = self.view.backgroundColor;
-    inputView.inputValue = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
-    inputView.delegate = self;
-    self.fieldEditorView.argumentInputViews = @[inputView];
-    
-    // Don't show a "set" button for switches. Set the ivar when the switch toggles.
-    if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
-        self.navigationItem.rightBarButtonItem = nil;
-    }
-}
-
-- (void)actionButtonPressed:(id)sender
-{
-    [super actionButtonPressed:sender];
-
-    // TODO: check mutability and use mutableCopy if necessary;
-    // this currently could and would assign NSArray to NSMutableArray
-    
-    [FLEXRuntimeUtility setValue:self.firstInputView.inputValue forIvar:self.ivar onObject:self.target];
-    self.firstInputView.inputValue = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
-    
-    // Pop view controller for consistency;
-    // property setters and method calls also pop on success.
-    [self.navigationController popViewControllerAnimated:YES];
-}
-
-- (void)getterButtonPressed:(id)sender
-{
-    [super getterButtonPressed:sender];
-    id returnedObject = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
-    [self exploreObjectOrPopViewController:returnedObject];
-}
-
-- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
-{
-    if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
-        [self actionButtonPressed:nil];
-    }
-}
-
-+ (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value
-{
-    return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
-}
-
-@end

+ 4 - 4
Classes/Editing/FLEXMethodCallingViewController.h

@@ -6,11 +6,11 @@
 //  Copyright (c) 2014 Flipboard. All rights reserved.
 //
 
-#import "FLEXFieldEditorViewController.h"
-#import <objc/runtime.h>
+#import "FLEXVariableEditorViewController.h"
+#import "FLEXMethod.h"
 
-@interface FLEXMethodCallingViewController : FLEXFieldEditorViewController
+@interface FLEXMethodCallingViewController : FLEXVariableEditorViewController
 
-- (id)initWithTarget:(id)target method:(Method)method;
++ (instancetype)target:(id)target method:(FLEXMethod *)method;
 
 @end

+ 49 - 49
Classes/Editing/FLEXMethodCallingViewController.m

@@ -16,93 +16,93 @@
 #import "FLEXUtility.h"
 
 @interface FLEXMethodCallingViewController ()
-
-@property (nonatomic) Method method;
-@property (nonatomic) FLEXTypeEncoding *returnType;
-
+@property (nonatomic) FLEXMethod *method;
 @end
 
 @implementation FLEXMethodCallingViewController
 
-- (id)initWithTarget:(id)target method:(Method)method
++ (instancetype)target:(id)target method:(FLEXMethod *)method
+{
+    return [[self alloc] initWithTarget:target method:method];
+}
+
+- (id)initWithTarget:(id)target method:(FLEXMethod *)method
 {
+    NSParameterAssert(method.isInstanceMethod == !object_isClass(target));
+
     self = [super initWithTarget:target];
     if (self) {
         self.method = method;
-        self.returnType = [FLEXRuntimeUtility returnTypeForMethod:method];
-        self.title = [self isClassMethod] ? @"Class Method" : @"Method";
+        self.title = method.isInstanceMethod ? @"Method" : @"Class Method";
     }
+
     return self;
 }
 
 - (void)viewDidLoad
 {
     [super viewDidLoad];
-    
-    NSString *returnType = @((const char *)self.returnType);
-    NSString *methodDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
-    NSString *format = @"Signature:\n%@\n\nReturn Type:\n%@";
-    NSString *info = [NSString stringWithFormat:format, methodDescription, returnType];
-    self.fieldEditorView.fieldDescription = info;
-    
-    NSArray<NSString *> *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
+
+    self.setterButton.title = @"Call";
+
+    // Configure field editor view
+    self.fieldEditorView.argumentInputViews = [self argumentInputViews];
+    self.fieldEditorView.fieldDescription = [NSString stringWithFormat:
+        @"Signature:\n%@\n\nReturn Type:\n%s",
+        self.method.description, (char *)self.method.returnType
+    ];
+}
+
+- (NSArray<FLEXArgumentInputView *> *)argumentInputViews {
+    Method method = self.method.objc_method;
+    NSArray *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:method];
     NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray array];
     unsigned int argumentIndex = kFLEXNumberOfImplicitArgs;
+
     for (NSString *methodComponent in methodComponents) {
-        char *argumentTypeEncoding = method_copyArgumentType(self.method, argumentIndex);
+        char *argumentTypeEncoding = method_copyArgumentType(method, argumentIndex);
         FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:argumentTypeEncoding];
         free(argumentTypeEncoding);
-        
+
         inputView.backgroundColor = self.view.backgroundColor;
         inputView.title = methodComponent;
         [argumentInputViews addObject:inputView];
         argumentIndex++;
     }
-    self.fieldEditorView.argumentInputViews = argumentInputViews;
-}
-
-- (void)dealloc
-{
-    free(self.returnType);
-    self.returnType = NULL;
-}
-
-- (BOOL)isClassMethod
-{
-    return self.target && self.target == [self.target class];
-}
 
-- (NSString *)titleForActionButton
-{
-    return @"Call";
+    return argumentInputViews;
 }
 
 - (void)actionButtonPressed:(id)sender
 {
     [super actionButtonPressed:sender];
-    
+
+    // Gather arguments
     NSMutableArray *arguments = [NSMutableArray array];
     for (FLEXArgumentInputView *inputView in self.fieldEditorView.argumentInputViews) {
-        id argumentValue = inputView.inputValue;
-        if (!argumentValue) {
-            // Use NSNulls as placeholders in the array. They will be interpreted as nil arguments.
-            argumentValue = [NSNull null];
-        }
-        [arguments addObject:argumentValue];
+        // Use NSNull as a nil placeholder; it will be interpreted as nil
+        [arguments addObject:inputView.inputValue ?: [NSNull null]];
     }
-    
+
+    // Call method
     NSError *error = nil;
-    id returnedObject = [FLEXRuntimeUtility performSelector:method_getName(self.method) onObject:self.target withArguments:arguments error:&error];
-    
+    id returnValue = [FLEXRuntimeUtility
+        performSelector:self.method.selector
+        onObject:self.target
+        withArguments:arguments
+        error:&error
+    ];
+
+    // Display return value or error
     if (error) {
-        [FLEXAlert showAlert:@"Method Call Failed" message:[error localizedDescription] from:self];
-    } else if (returnedObject) {
+        [FLEXAlert showAlert:@"Method Call Failed" message:error.localizedDescription from:self];
+    } else if (returnValue) {
         // For non-nil (or void) return types, push an explorer view controller to display the returned object
-        returnedObject = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:returnedObject type:self.returnType];
-        FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:returnedObject];
-        [self.navigationController pushViewController:explorerViewController animated:YES];
+        returnValue = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:returnValue type:self.method.returnType];
+        FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:returnValue];
+        [self.navigationController pushViewController:explorer animated:YES];
     } else {
-        [self exploreObjectOrPopViewController:returnedObject];
+        [self exploreObjectOrPopViewController:returnValue];
     }
 }
 

+ 0 - 18
Classes/Editing/FLEXMutableFieldEditorViewController.h

@@ -1,18 +0,0 @@
-//
-//  FLEXMutableFieldEditorViewController.h
-//  FLEX
-//
-//  Created by Tanner on 11/22/18.
-//  Copyright © 2018 Flipboard. All rights reserved.
-//
-
-#import "FLEXFieldEditorViewController.h"
-
-@interface FLEXMutableFieldEditorViewController : FLEXFieldEditorViewController
-
-@property (nonatomic, readonly) UIBarButtonItem *getterButton;
-
-- (void)getterButtonPressed:(id)sender;
-- (NSString *)titleForGetterButton;
-
-@end

+ 0 - 36
Classes/Editing/FLEXMutableFieldEditorViewController.m

@@ -1,36 +0,0 @@
-//
-//  FLEXMutableFieldEditorViewController.m
-//  FLEX
-//
-//  Created by Tanner on 11/22/18.
-//  Copyright © 2018 Flipboard. All rights reserved.
-//
-
-#import "FLEXMutableFieldEditorViewController.h"
-#import "FLEXFieldEditorView.h"
-
-@interface FLEXMutableFieldEditorViewController ()
-
-@property (nonatomic, readwrite) UIBarButtonItem *getterButton;
-
-@end
-
-@implementation FLEXMutableFieldEditorViewController
-
-- (void)viewDidLoad {
-    [super viewDidLoad];
-    
-    self.getterButton = [[UIBarButtonItem alloc] initWithTitle:[self titleForGetterButton] style:UIBarButtonItemStyleDone target:self action:@selector(getterButtonPressed:)];
-    self.navigationItem.rightBarButtonItems = @[self.setterButton, self.getterButton];
-}
-
-- (void)getterButtonPressed:(id)sender {
-    // Subclasses can override
-    [self.fieldEditorView endEditing:YES];
-}
-
-- (NSString *)titleForGetterButton {
-    return @"Get";
-}
-
-@end

+ 0 - 18
Classes/Editing/FLEXPropertyEditorViewController.h

@@ -1,18 +0,0 @@
-//
-//  FLEXPropertyEditorViewController.h
-//  Flipboard
-//
-//  Created by Ryan Olson on 5/20/14.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXMutableFieldEditorViewController.h"
-#import <objc/runtime.h>
-
-@interface FLEXPropertyEditorViewController : FLEXMutableFieldEditorViewController
-
-- (id)initWithTarget:(id)target property:(objc_property_t)property;
-
-+ (BOOL)canEditProperty:(objc_property_t)property onObject:(id)object currentValue:(id)value;
-
-@end

+ 0 - 100
Classes/Editing/FLEXPropertyEditorViewController.m

@@ -1,100 +0,0 @@
-//
-//  FLEXPropertyEditorViewController.m
-//  Flipboard
-//
-//  Created by Ryan Olson on 5/20/14.
-//  Copyright (c) 2014 Flipboard. All rights reserved.
-//
-
-#import "FLEXPropertyEditorViewController.h"
-#import "FLEXRuntimeUtility.h"
-#import "FLEXFieldEditorView.h"
-#import "FLEXArgumentInputView.h"
-#import "FLEXArgumentInputViewFactory.h"
-#import "FLEXArgumentInputSwitchView.h"
-#import "FLEXUtility.h"
-
-@interface FLEXPropertyEditorViewController () <FLEXArgumentInputViewDelegate>
-
-@property (nonatomic) objc_property_t property;
-
-@end
-
-@implementation FLEXPropertyEditorViewController
-
-- (id)initWithTarget:(id)target property:(objc_property_t)property
-{
-    self = [super initWithTarget:target];
-    if (self) {
-        self.property = property;
-        self.title = @"Property";
-    }
-    return self;
-}
-
-- (void)viewDidLoad
-{
-    [super viewDidLoad];
-    
-    self.fieldEditorView.fieldDescription = [FLEXRuntimeUtility fullDescriptionForProperty:self.property];
-    id currentValue = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
-    self.setterButton.enabled = [[self class] canEditProperty:self.property onObject:self.target currentValue:currentValue];
-    
-    const char *typeEncoding = [FLEXRuntimeUtility typeEncodingForProperty:self.property].UTF8String;
-    FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:typeEncoding];
-    inputView.backgroundColor = self.view.backgroundColor;
-    inputView.inputValue = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
-    inputView.delegate = self;
-    self.fieldEditorView.argumentInputViews = @[inputView];
-    
-    // Don't show a "set" button for switches - just call the setter immediately after the switch toggles.
-    if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
-        self.navigationItem.rightBarButtonItem = nil;
-    }
-}
-
-- (void)actionButtonPressed:(id)sender
-{
-    [super actionButtonPressed:sender];
-    
-    id userInputObject = self.firstInputView.inputValue;
-    NSArray *arguments = userInputObject ? @[userInputObject] : nil;
-    SEL setterSelector = [FLEXRuntimeUtility setterSelectorForProperty:self.property];
-    NSError *error = nil;
-    [FLEXRuntimeUtility performSelector:setterSelector onObject:self.target withArguments:arguments error:&error];
-    if (error) {
-        [FLEXAlert showAlert:@"Property Setter Failed" message:[error localizedDescription] from:self];
-        self.firstInputView.inputValue = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
-    } else {
-        // If the setter was called without error, pop the view controller to indicate that and make the user's life easier.
-        // Don't do this for simulated taps on the action button (i.e. from switch/BOOL editors). The experience is weird there.
-        if (sender) {
-            [self.navigationController popViewControllerAnimated:YES];
-        }
-    }
-}
-
-- (void)getterButtonPressed:(id)sender
-{
-    [super getterButtonPressed:sender];
-    id returnedObject = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
-    [self exploreObjectOrPopViewController:returnedObject];
-}
-
-- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
-{
-    if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
-        [self actionButtonPressed:nil];
-    }
-}
-
-+ (BOOL)canEditProperty:(objc_property_t)property onObject:(id)object currentValue:(id)value
-{
-    const char *typeEncoding = [FLEXRuntimeUtility typeEncodingForProperty:property].UTF8String;
-    BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
-    SEL setterSelector = [FLEXRuntimeUtility setterSelectorForProperty:property];
-    BOOL isReadonly = [FLEXRuntimeUtility isReadonlyProperty:property] && (!setterSelector || ![object respondsToSelector:setterSelector]);
-    return canEditType && !isReadonly;
-}
-
-@end

+ 35 - 0
Classes/Editing/FLEXVariableEditorViewController.h

@@ -0,0 +1,35 @@
+//
+//  FLEXVariableEditorViewController.h
+//  Flipboard
+//
+//  Created by Ryan Olson on 5/16/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class FLEXFieldEditorView;
+@class FLEXArgumentInputView;
+
+/// Provides a screen for editing or configuring one or more variables.
+@interface FLEXVariableEditorViewController : UIViewController
+
++ (instancetype)target:(id)target;
+- (id)initWithTarget:(id)target;
+
+// Convenience accessor since many subclasses only use one input view
+@property (nonatomic, readonly) FLEXArgumentInputView *firstInputView;
+
+// For subclass use only.
+@property (nonatomic, readonly) id target;
+@property (nonatomic, readonly) FLEXFieldEditorView *fieldEditorView;
+/// Subclasses can change the button title via the \c title property
+@property (nonatomic, readonly) UIBarButtonItem *setterButton;
+
+- (void)actionButtonPressed:(id)sender;
+
+/// Pushes an explorer view controller for the given object
+/// or pops the current view controller.
+- (void)exploreObjectOrPopViewController:(id)objectOrNil;
+
+@end

+ 147 - 0
Classes/Editing/FLEXVariableEditorViewController.m

@@ -0,0 +1,147 @@
+//
+//  FLEXVariableEditorViewController.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 5/16/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXColor.h"
+#import "FLEXVariableEditorViewController.h"
+#import "FLEXFieldEditorView.h"
+#import "FLEXRuntimeUtility.h"
+#import "FLEXUtility.h"
+#import "FLEXObjectExplorerFactory.h"
+#import "FLEXArgumentInputView.h"
+#import "FLEXArgumentInputViewFactory.h"
+#import "FLEXObjectExplorerViewController.h"
+
+@interface FLEXVariableEditorViewController () <UIScrollViewDelegate>
+
+@property (nonatomic) UIScrollView *scrollView;
+
+@property (nonatomic, readwrite) id target;
+
+@end
+
+@implementation FLEXVariableEditorViewController
+
+#pragma mark - Initialization
+
++ (instancetype)target:(id)target
+{
+    return [[self alloc] initWithTarget:target];
+}
+
+- (id)initWithTarget:(id)target
+{
+    self = [super init];
+    if (self) {
+        self.target = target;
+        [NSNotificationCenter.defaultCenter
+            addObserver:self selector:@selector(keyboardDidShow:)
+            name:UIKeyboardDidShowNotification object:nil
+        ];
+        [NSNotificationCenter.defaultCenter
+            addObserver:self selector:@selector(keyboardWillHide:)
+            name:UIKeyboardWillHideNotification object:nil
+        ];
+    }
+    
+    return self;
+}
+
+- (void)dealloc
+{
+    [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+#pragma mark - UIViewController methods
+
+- (void)keyboardDidShow:(NSNotification *)notification
+{
+    CGRect keyboardRectInWindow = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
+    CGSize keyboardSize = [self.view convertRect:keyboardRectInWindow fromView:nil].size;
+    UIEdgeInsets scrollInsets = self.scrollView.contentInset;
+    scrollInsets.bottom = keyboardSize.height;
+    self.scrollView.contentInset = scrollInsets;
+    self.scrollView.scrollIndicatorInsets = scrollInsets;
+    
+    // Find the active input view and scroll to make sure it's visible.
+    for (FLEXArgumentInputView *argumentInputView in self.fieldEditorView.argumentInputViews) {
+        if (argumentInputView.inputViewIsFirstResponder) {
+            CGRect scrollToVisibleRect = [self.scrollView convertRect:argumentInputView.bounds fromView:argumentInputView];
+            [self.scrollView scrollRectToVisible:scrollToVisibleRect animated:YES];
+            break;
+        }
+    }
+}
+
+- (void)keyboardWillHide:(NSNotification *)notification
+{
+    UIEdgeInsets scrollInsets = self.scrollView.contentInset;
+    scrollInsets.bottom = 0.0;
+    self.scrollView.contentInset = scrollInsets;
+    self.scrollView.scrollIndicatorInsets = scrollInsets;
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+    
+    self.view.backgroundColor = [FLEXColor scrollViewBackgroundColor];
+    
+    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
+    self.scrollView.backgroundColor = self.view.backgroundColor;
+    self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    self.scrollView.delegate = self;
+    [self.view addSubview:self.scrollView];
+    
+    _fieldEditorView = [FLEXFieldEditorView new];
+    self.fieldEditorView.backgroundColor = self.view.backgroundColor;
+    self.fieldEditorView.targetDescription = [NSString stringWithFormat:@"%@ %p", [self.target class], self.target];
+    [self.scrollView addSubview:self.fieldEditorView];
+    
+    _setterButton = [[UIBarButtonItem alloc]
+        initWithTitle:@"Set"
+        style:UIBarButtonItemStyleDone
+        target:self
+        action:@selector(actionButtonPressed:)
+    ];
+    self.navigationItem.rightBarButtonItem = self.setterButton;
+}
+
+- (void)viewWillLayoutSubviews
+{
+    CGSize constrainSize = CGSizeMake(self.scrollView.bounds.size.width, CGFLOAT_MAX);
+    CGSize fieldEditorSize = [self.fieldEditorView sizeThatFits:constrainSize];
+    self.fieldEditorView.frame = CGRectMake(0, 0, fieldEditorSize.width, fieldEditorSize.height);
+    self.scrollView.contentSize = fieldEditorSize;
+}
+
+#pragma mark - Public
+
+- (FLEXArgumentInputView *)firstInputView
+{
+    return [self.fieldEditorView argumentInputViews].firstObject;
+}
+
+- (void)actionButtonPressed:(id)sender
+{
+    // Subclasses can override
+    [self.fieldEditorView endEditing:YES];
+}
+
+- (void)exploreObjectOrPopViewController:(id)objectOrNil {
+    if (objectOrNil) {
+        // For non-nil (or void) return types, push an explorer view controller to display the object
+        FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:objectOrNil];
+        [self.navigationController pushViewController:explorerViewController animated:YES];
+    } else {
+        // If we didn't get a returned object but the method call succeeded,
+        // pop this view controller off the stack to indicate that the call went through.
+        [self.navigationController popViewControllerAnimated:YES];
+    }
+}
+
+@end

+ 1 - 1
Classes/ObjectExplorers/Controllers/FLEXViewExplorerViewController.m

@@ -11,7 +11,7 @@
 #import "FLEXUtility.h"
 #import "FLEXObjectExplorerFactory.h"
 #import "FLEXImagePreviewViewController.h"
-#import "FLEXPropertyEditorViewController.h"
+#import "FLEXFieldEditorViewController.h"
 
 typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
     FLEXViewExplorerRowViewController,

+ 8 - 18
FLEX.xcodeproj/project.pbxproj

@@ -88,12 +88,10 @@
 		3A4C950A1B5B21410088C3F2 /* FLEXFieldEditorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94851B5B21410088C3F2 /* FLEXFieldEditorView.m */; };
 		3A4C950B1B5B21410088C3F2 /* FLEXFieldEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94861B5B21410088C3F2 /* FLEXFieldEditorViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C950C1B5B21410088C3F2 /* FLEXFieldEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94871B5B21410088C3F2 /* FLEXFieldEditorViewController.m */; };
-		3A4C950D1B5B21410088C3F2 /* FLEXIvarEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94881B5B21410088C3F2 /* FLEXIvarEditorViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		3A4C950E1B5B21410088C3F2 /* FLEXIvarEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94891B5B21410088C3F2 /* FLEXIvarEditorViewController.m */; };
+		3A4C950B1B5B21410088C3F2 /* FLEXVariableEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94861B5B21410088C3F2 /* FLEXVariableEditorViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		3A4C950C1B5B21410088C3F2 /* FLEXVariableEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94871B5B21410088C3F2 /* FLEXVariableEditorViewController.m */; };
 		3A4C950F1B5B21410088C3F2 /* FLEXMethodCallingViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C948A1B5B21410088C3F2 /* FLEXMethodCallingViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C95101B5B21410088C3F2 /* FLEXMethodCallingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C948B1B5B21410088C3F2 /* FLEXMethodCallingViewController.m */; };
-		3A4C95111B5B21410088C3F2 /* FLEXPropertyEditorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C948C1B5B21410088C3F2 /* FLEXPropertyEditorViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
-		3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C948D1B5B21410088C3F2 /* FLEXPropertyEditorViewController.m */; };
 		3A4C951E1B5B21410088C3F2 /* FLEXClassesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C949B1B5B21410088C3F2 /* FLEXClassesTableViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C949C1B5B21410088C3F2 /* FLEXClassesTableViewController.m */; };
 		3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C949F1B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -366,14 +364,12 @@
 		3A4C94831B5B21410088C3F2 /* FLEXDefaultEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXDefaultEditorViewController.m; sourceTree = "<group>"; };
 		3A4C94841B5B21410088C3F2 /* FLEXFieldEditorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFieldEditorView.h; sourceTree = "<group>"; };
 		3A4C94851B5B21410088C3F2 /* FLEXFieldEditorView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFieldEditorView.m; sourceTree = "<group>"; };
+		3A4C94861B5B21410088C3F2 /* FLEXVariableEditorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXVariableEditorViewController.h; sourceTree = "<group>"; };
+		3A4C94871B5B21410088C3F2 /* FLEXVariableEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXVariableEditorViewController.m; sourceTree = "<group>"; };
 		3A4C94861B5B21410088C3F2 /* FLEXFieldEditorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFieldEditorViewController.h; sourceTree = "<group>"; };
 		3A4C94871B5B21410088C3F2 /* FLEXFieldEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFieldEditorViewController.m; sourceTree = "<group>"; };
-		3A4C94881B5B21410088C3F2 /* FLEXIvarEditorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXIvarEditorViewController.h; sourceTree = "<group>"; };
-		3A4C94891B5B21410088C3F2 /* FLEXIvarEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXIvarEditorViewController.m; sourceTree = "<group>"; };
 		3A4C948A1B5B21410088C3F2 /* FLEXMethodCallingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMethodCallingViewController.h; sourceTree = "<group>"; };
 		3A4C948B1B5B21410088C3F2 /* FLEXMethodCallingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMethodCallingViewController.m; sourceTree = "<group>"; };
-		3A4C948C1B5B21410088C3F2 /* FLEXPropertyEditorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXPropertyEditorViewController.h; sourceTree = "<group>"; };
-		3A4C948D1B5B21410088C3F2 /* FLEXPropertyEditorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXPropertyEditorViewController.m; sourceTree = "<group>"; };
 		3A4C949B1B5B21410088C3F2 /* FLEXClassesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXClassesTableViewController.h; sourceTree = "<group>"; };
 		3A4C949C1B5B21410088C3F2 /* FLEXClassesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXClassesTableViewController.m; sourceTree = "<group>"; };
 		3A4C949F1B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXFileBrowserSearchOperation.h; sourceTree = "<group>"; };
@@ -689,18 +685,14 @@
 				3A4C94671B5B21410088C3F2 /* ArgumentInputViews */,
 				3A4C94841B5B21410088C3F2 /* FLEXFieldEditorView.h */,
 				3A4C94851B5B21410088C3F2 /* FLEXFieldEditorView.m */,
+				3A4C94861B5B21410088C3F2 /* FLEXVariableEditorViewController.h */,
+				3A4C94871B5B21410088C3F2 /* FLEXVariableEditorViewController.m */,
 				3A4C94861B5B21410088C3F2 /* FLEXFieldEditorViewController.h */,
 				3A4C94871B5B21410088C3F2 /* FLEXFieldEditorViewController.m */,
-				C3DA55FC21A76406005DDA60 /* FLEXMutableFieldEditorViewController.h */,
-				C3DA55FD21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m */,
 				3A4C94821B5B21410088C3F2 /* FLEXDefaultEditorViewController.h */,
 				3A4C94831B5B21410088C3F2 /* FLEXDefaultEditorViewController.m */,
-				3A4C94881B5B21410088C3F2 /* FLEXIvarEditorViewController.h */,
-				3A4C94891B5B21410088C3F2 /* FLEXIvarEditorViewController.m */,
 				3A4C948A1B5B21410088C3F2 /* FLEXMethodCallingViewController.h */,
 				3A4C948B1B5B21410088C3F2 /* FLEXMethodCallingViewController.m */,
-				3A4C948C1B5B21410088C3F2 /* FLEXPropertyEditorViewController.h */,
-				3A4C948D1B5B21410088C3F2 /* FLEXPropertyEditorViewController.m */,
 			);
 			path = Editing;
 			sourceTree = "<group>";
@@ -1126,7 +1118,6 @@
 				3A4C95341B5B21410088C3F2 /* FLEXSystemLogTableViewController.h in Headers */,
 				C34C9BDD23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h in Headers */,
 				3A4C95091B5B21410088C3F2 /* FLEXFieldEditorView.h in Headers */,
-				3A4C950D1B5B21410088C3F2 /* FLEXIvarEditorViewController.h in Headers */,
 				C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */,
 				C3490E1F233BDD73002AE200 /* FLEXSingleRowSection.h in Headers */,
 				3A4C95281B5B21410088C3F2 /* FLEXInstancesTableViewController.h in Headers */,
@@ -1175,6 +1166,7 @@
 				3A4C94CF1B5B21410088C3F2 /* FLEXImageExplorerViewController.h in Headers */,
 				2EF6B04E1D494BE50006BDA5 /* FLEXNetworkCurlLogger.h in Headers */,
 				3A4C950B1B5B21410088C3F2 /* FLEXFieldEditorViewController.h in Headers */,
+				3A4C950B1B5B21410088C3F2 /* FLEXVariableEditorViewController.h in Headers */,
 				C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */,
 				94A515251C4CA2080063292F /* FLEXExplorerToolbar.h in Headers */,
 				C387C88322E0D24A00750E58 /* UIView+FLEX_Layout.h in Headers */,
@@ -1182,7 +1174,6 @@
 				3A4C94D71B5B21410088C3F2 /* FLEXSetExplorerViewController.h in Headers */,
 				3A4C94D91B5B21410088C3F2 /* FLEXViewControllerExplorerViewController.h in Headers */,
 				3A4C952E1B5B21410088C3F2 /* FLEXWebViewController.h in Headers */,
-				3A4C95111B5B21410088C3F2 /* FLEXPropertyEditorViewController.h in Headers */,
 				C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */,
 				3A4C94E91B5B21410088C3F2 /* FLEXHierarchyTableViewController.h in Headers */,
 				3A4C94F31B5B21410088C3F2 /* FLEXArgumentInputFontView.h in Headers */,
@@ -1335,7 +1326,6 @@
 				C34C9BDE23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m in Sources */,
 				2EF6B04D1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m in Sources */,
 				94A515201C4CA1F10063292F /* FLEXWindow.m in Sources */,
-				3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */,
 				C3F527C6231891F6009CBA07 /* FLEXViewShortcuts.m in Sources */,
 				3A4C95391B5B21410088C3F2 /* FLEXNetworkRecorder.m in Sources */,
 				3A4C950E1B5B21410088C3F2 /* FLEXIvarEditorViewController.m in Sources */,
@@ -1357,7 +1347,6 @@
 				C36FBFD4230F3B98008D95D5 /* FLEXProtocol.m in Sources */,
 				C34D4EB123A2ABD900C1F903 /* FLEXLayerShortcuts.m in Sources */,
 				C38F3F32230C958F004E3731 /* FLEXAlert.m in Sources */,
-				C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */,
 				3A4C95081B5B21410088C3F2 /* FLEXDefaultEditorViewController.m in Sources */,
 				779B1ED11C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m in Sources */,
 				C36FBFD0230F3B98008D95D5 /* FLEXProperty.m in Sources */,
@@ -1442,6 +1431,7 @@
 				C3490E20233BDD73002AE200 /* FLEXSingleRowSection.m in Sources */,
 				3A4C950C1B5B21410088C3F2 /* FLEXFieldEditorViewController.m in Sources */,
 				C34D4EB923A2B17900C1F903 /* FLEXBundleShortcuts.m in Sources */,
+				3A4C950C1B5B21410088C3F2 /* FLEXVariableEditorViewController.m in Sources */,
 				3A4C952D1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.m in Sources */,
 				C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */,
 				3A4C953D1B5B21410088C3F2 /* FLEXNetworkTransaction.m in Sources */,