|
|
@@ -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
|