FLEXFieldEditorViewController.m 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. //
  2. // FLEXFieldEditorViewController.m
  3. // FLEX
  4. //
  5. // Created by Tanner on 11/22/18.
  6. // Copyright © 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXFieldEditorViewController.h"
  9. #import "FLEXFieldEditorView.h"
  10. #import "FLEXArgumentInputViewFactory.h"
  11. #import "FLEXPropertyAttributes.h"
  12. #import "FLEXRuntimeUtility.h"
  13. #import "FLEXUtility.h"
  14. #import "FLEXColor.h"
  15. #import "UIBarButtonItem+FLEX.h"
  16. #import <TargetConditionals.h>
  17. #import "FLEXArgumentInputDateView.h"
  18. @interface FLEXFieldEditorViewController () <FLEXArgumentInputViewDelegate>
  19. @property (nonatomic) FLEXProperty *property;
  20. @property (nonatomic) FLEXIvar *ivar;
  21. @property (nonatomic, readonly) id currentValue;
  22. @property (nonatomic, readonly) const FLEXTypeEncoding *typeEncoding;
  23. @property (nonatomic, readonly) NSString *fieldDescription;
  24. @end
  25. @implementation FLEXFieldEditorViewController
  26. #pragma mark - Initialization
  27. + (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)())onCommit {
  28. id value = [property getValue:target];
  29. if (![self canEditProperty:property onObject:target currentValue:value]) {
  30. return nil;
  31. }
  32. FLEXFieldEditorViewController *editor = [self target:target data:property commitHandler:onCommit];
  33. editor.title = [@"Property: " stringByAppendingString:property.name];
  34. editor.property = property;
  35. return editor;
  36. }
  37. + (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)())onCommit {
  38. FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit];
  39. editor.title = [@"Ivar: " stringByAppendingString:ivar.name];
  40. editor.ivar = ivar;
  41. return editor;
  42. }
  43. #pragma mark - Overrides
  44. - (void)viewDidLoad {
  45. [super viewDidLoad];
  46. self.view.backgroundColor = FLEXColor.groupedBackgroundColor;
  47. #if !TARGET_OS_TV
  48. // Create getter button
  49. _getterButton = [[UIBarButtonItem alloc]
  50. initWithTitle:@"Get"
  51. style:UIBarButtonItemStyleDone
  52. target:self
  53. action:@selector(getterButtonPressed:)
  54. ];
  55. self.toolbarItems = @[
  56. UIBarButtonItem.flex_flexibleSpace, self.getterButton, self.actionButton
  57. ];
  58. #else
  59. _getterButton = [UIButton buttonWithType:UIButtonTypeSystem];
  60. [_getterButton setTitle:@"Get" forState:UIControlStateNormal];
  61. [_getterButton addTarget:self action:@selector(getterButtonPressed:) forControlEvents:UIControlEventPrimaryActionTriggered];
  62. _getterButton.frame = CGRectMake(100, 600, 200, 70);
  63. [self.view addSubview:_getterButton];
  64. UIFocusGuide *focusGuide = [[UIFocusGuide alloc] init];
  65. [self.view addLayoutGuide:focusGuide];
  66. [focusGuide.topAnchor constraintEqualToAnchor:self.actionButton.topAnchor].active = true;
  67. [focusGuide.bottomAnchor constraintEqualToAnchor:self.getterButton.bottomAnchor].active = true;
  68. focusGuide.preferredFocusEnvironments = self.preferredFocusEnvironments;
  69. #endif
  70. // Configure input view
  71. self.fieldEditorView.fieldDescription = self.fieldDescription;
  72. FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:self.typeEncoding];
  73. inputView.inputValue = self.currentValue;
  74. inputView.delegate = self;
  75. self.fieldEditorView.argumentInputViews = @[inputView];
  76. // Don't show a "set" button for switches; we mutate when the switch is flipped
  77. if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
  78. self.actionButton.enabled = NO;
  79. #if !TARGET_OS_TV
  80. self.actionButton.title = @"Flip the switch to call the setter";
  81. // Put getter button before setter button
  82. self.toolbarItems = @[
  83. UIBarButtonItem.flex_flexibleSpace, self.actionButton, self.getterButton
  84. ];
  85. #endif
  86. }
  87. }
  88. - (NSArray *)preferredFocusEnvironments {
  89. if ([self actionButton] && _getterButton){
  90. return @[[self actionButton],_getterButton];
  91. } else {
  92. if ([self actionButton]){
  93. return @[[self actionButton]];
  94. } else if (_getterButton){
  95. return @[_getterButton];
  96. }
  97. }
  98. return nil;
  99. }
  100. - (void)actionButtonPressed:(id)sender {
  101. if (self.property) {
  102. id userInputObject = self.firstInputView.inputValue;
  103. NSArray *arguments = userInputObject ? @[userInputObject] : nil;
  104. SEL setterSelector = self.property.likelySetter;
  105. NSError *error = nil;
  106. [FLEXRuntimeUtility performSelector:setterSelector onObject:self.target withArguments:arguments error:&error];
  107. if (error) {
  108. [FLEXAlert showAlert:@"Property Setter Failed" message:error.localizedDescription from:self];
  109. sender = nil; // Don't pop back
  110. }
  111. } else {
  112. // TODO: check mutability and use mutableCopy if necessary;
  113. // this currently could and would assign NSArray to NSMutableArray
  114. [self.ivar setValue:self.firstInputView.inputValue onObject:self.target];
  115. }
  116. // Dismiss keyboard and handle committed changes
  117. [super actionButtonPressed:sender];
  118. // Go back after setting, but not for switches.
  119. if (sender) {
  120. [self.navigationController popViewControllerAnimated:YES];
  121. } else {
  122. self.firstInputView.inputValue = self.currentValue;
  123. }
  124. }
  125. - (void)getterButtonPressed:(id)sender {
  126. [self.fieldEditorView endEditing:YES];
  127. [self exploreObjectOrPopViewController:self.currentValue];
  128. }
  129. - (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView {
  130. if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
  131. [self actionButtonPressed:nil];
  132. }
  133. }
  134. - (void)viewWillLayoutSubviews {
  135. [super viewWillLayoutSubviews];
  136. #if TARGET_OS_TV
  137. FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:self.typeEncoding];
  138. if ([inputView isKindOfClass:[FLEXArgumentInputDateView class]]){
  139. [self actionButton].frame = CGRectMake(100, 350, 200, 60);
  140. [self getterButton].frame = CGRectMake(100, 550, 200, 60);
  141. return;
  142. }
  143. CGRect getterFrame = _getterButton.frame;
  144. CGFloat actionOffset = [[self actionButton] frame].origin.y;
  145. //CGRect fieldEditorFrame = self.fieldEditorView.frame;
  146. //CGFloat buttonOffset = (fieldEditorFrame.origin.y + fieldEditorFrame.size.height) + (130 + 67);
  147. getterFrame.origin.y = actionOffset;
  148. _getterButton.frame = getterFrame;
  149. #endif
  150. }
  151. #pragma mark - Private
  152. - (id)currentValue {
  153. if (self.property) {
  154. return [self.property getValue:self.target];
  155. } else {
  156. return [self.ivar getValue:self.target];
  157. }
  158. }
  159. - (const FLEXTypeEncoding *)typeEncoding {
  160. if (self.property) {
  161. return self.property.attributes.typeEncoding.UTF8String;
  162. } else {
  163. return self.ivar.typeEncoding.UTF8String;
  164. }
  165. }
  166. - (NSString *)fieldDescription {
  167. if (self.property) {
  168. return self.property.fullDescription;
  169. } else {
  170. return self.ivar.description;
  171. }
  172. }
  173. + (BOOL)canEditProperty:(FLEXProperty *)property onObject:(id)object currentValue:(id)value {
  174. const FLEXTypeEncoding *typeEncoding = property.attributes.typeEncoding.UTF8String;
  175. BOOL canEditType = [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:value];
  176. return canEditType && [object respondsToSelector:property.likelySetter];
  177. }
  178. + (BOOL)canEditIvar:(Ivar)ivar currentValue:(id)value {
  179. return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:ivar_getTypeEncoding(ivar) currentValue:value];
  180. }
  181. @end