FLEXArgumentInputObjectView.m 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. //
  2. // FLEXArgumentInputJSONObjectView.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/15/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXArgumentInputObjectView.h"
  9. #import "FLEXRuntimeUtility.h"
  10. static const CGFloat kSegmentInputMargin = 4;
  11. typedef NS_ENUM(NSUInteger, FLEXArgInputObjectType) {
  12. FLEXArgInputObjectTypeJSON,
  13. FLEXArgInputObjectTypeAddress
  14. };
  15. @interface FLEXArgumentInputObjectView ()
  16. @property (nonatomic) UISegmentedControl *objectTypeSegmentControl;
  17. @property (nonatomic) FLEXArgInputObjectType inputType;
  18. @end
  19. @implementation FLEXArgumentInputObjectView
  20. - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding
  21. {
  22. self = [super initWithArgumentTypeEncoding:typeEncoding];
  23. if (self) {
  24. // Start with the numbers and punctuation keyboard since quotes, curly braces, or
  25. // square brackets are likely to be the first characters type for the JSON.
  26. self.inputTextView.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
  27. self.targetSize = FLEXArgumentInputViewSizeLarge;
  28. self.objectTypeSegmentControl = [[UISegmentedControl alloc] initWithItems:@[@"Value", @"Address"]];
  29. [self.objectTypeSegmentControl addTarget:self action:@selector(didChangeType) forControlEvents:UIControlEventValueChanged];
  30. self.objectTypeSegmentControl.selectedSegmentIndex = 0;
  31. [self addSubview:self.objectTypeSegmentControl];
  32. self.inputType = [[self class] preferredDefaultTypeForObjCType:typeEncoding withCurrentValue:nil];
  33. self.objectTypeSegmentControl.selectedSegmentIndex = self.inputType;
  34. }
  35. return self;
  36. }
  37. - (void)didChangeType
  38. {
  39. self.inputType = self.objectTypeSegmentControl.selectedSegmentIndex;
  40. if (super.inputValue) {
  41. // Trigger an update to the text field to show
  42. // the address of the stored object we were given,
  43. // or to show a JSON representation of the object
  44. [self populateTextAreaFromValue:super.inputValue];
  45. } else {
  46. // Clear the text field
  47. self.inputValue = nil;
  48. }
  49. }
  50. - (void)setInputType:(FLEXArgInputObjectType)inputType {
  51. if (_inputType == inputType) return;
  52. _inputType = inputType;
  53. // Resize input view
  54. switch (inputType) {
  55. case FLEXArgInputObjectTypeJSON:
  56. self.targetSize = FLEXArgumentInputViewSizeLarge;
  57. break;
  58. case FLEXArgInputObjectTypeAddress:
  59. self.targetSize = FLEXArgumentInputViewSizeSmall;
  60. break;
  61. }
  62. [self setNeedsLayout];
  63. [self.superview setNeedsLayout];
  64. }
  65. - (void)setInputValue:(id)inputValue
  66. {
  67. super.inputValue = inputValue;
  68. [self populateTextAreaFromValue:inputValue];
  69. }
  70. - (id)inputValue
  71. {
  72. switch (self.inputType) {
  73. case FLEXArgInputObjectTypeJSON:
  74. return [FLEXRuntimeUtility objectValueFromEditableJSONString:self.inputTextView.text];
  75. case FLEXArgInputObjectTypeAddress: {
  76. NSScanner *scanner = [NSScanner scannerWithString:self.inputTextView.text];
  77. unsigned long long objectPointerValue;
  78. if ([scanner scanHexLongLong:&objectPointerValue]) {
  79. return (__bridge id)(void *)objectPointerValue;
  80. }
  81. return nil;
  82. }
  83. }
  84. }
  85. - (void)populateTextAreaFromValue:(id)value
  86. {
  87. if (self.inputType == FLEXArgInputObjectTypeJSON) {
  88. self.inputTextView.text = [FLEXRuntimeUtility editableJSONStringForObject:value];
  89. } else if (self.inputType == FLEXArgInputObjectTypeAddress) {
  90. self.inputTextView.text = [NSString stringWithFormat:@"%p", value];
  91. }
  92. }
  93. - (CGSize)sizeThatFits:(CGSize)size
  94. {
  95. CGSize fitSize = [super sizeThatFits:size];
  96. fitSize.height += [self.objectTypeSegmentControl sizeThatFits:size].height + kSegmentInputMargin;
  97. return fitSize;
  98. }
  99. - (void)layoutSubviews {
  100. // Must be called first since we are overriding self.inputTextView's position
  101. [super layoutSubviews];
  102. CGFloat segmentHeight = [self.objectTypeSegmentControl sizeThatFits:self.frame.size].height;
  103. self.inputTextView.frame = CGRectMake(
  104. 0.0,
  105. segmentHeight + self.topInputFieldVerticalLayoutGuide + kSegmentInputMargin,
  106. self.inputTextView.frame.size.width,
  107. self.inputTextView.frame.size.height
  108. );
  109. self.objectTypeSegmentControl.frame = CGRectMake(
  110. 0.0,
  111. self.topInputFieldVerticalLayoutGuide,
  112. self.frame.size.width,
  113. segmentHeight
  114. );
  115. }
  116. + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
  117. {
  118. NSParameterAssert(type);
  119. // Must be object type
  120. return type[0] == '@';
  121. }
  122. + (FLEXArgInputObjectType)preferredDefaultTypeForObjCType:(const char *)type withCurrentValue:(id)value
  123. {
  124. NSParameterAssert(type[0] == '@');
  125. if (value) {
  126. // If there's a current value, it must be serializable to JSON
  127. // to display the JSON editor. Otherwise display the address field.
  128. if ([FLEXRuntimeUtility editableJSONStringForObject:value]) {
  129. return FLEXArgInputObjectTypeJSON;
  130. } else {
  131. return FLEXArgInputObjectTypeAddress;
  132. }
  133. } else {
  134. // Otherwise, see if we have more type information than just 'id'.
  135. // If we do, make sure the encoding is something serializable to JSON.
  136. // Properties and ivars keep more detailed type encoding information than method arguments.
  137. if (strcmp(type, @encode(id)) != 0) {
  138. BOOL isJSONSerializableType = NO;
  139. // Note: we can't use @encode(NSString) here because that drops
  140. // the class information and just goes to @encode(id).
  141. NSArray *jsonTypes = @[
  142. @(FLEXEncodeClass(NSString)),
  143. @(FLEXEncodeClass(NSNumber)),
  144. @(FLEXEncodeClass(NSArray)),
  145. @(FLEXEncodeClass(NSDictionary)),
  146. @(FLEXEncodeClass(NSMutableString)),
  147. @(FLEXEncodeClass(NSMutableArray)),
  148. @(FLEXEncodeClass(NSMutableDictionary)),
  149. ];
  150. // Look for matching types
  151. NSString *typeStr = @(type);
  152. for (NSString *encodedClass in jsonTypes) {
  153. if ([typeStr isEqualToString:encodedClass]) {
  154. isJSONSerializableType = YES;
  155. break;
  156. }
  157. }
  158. if (isJSONSerializableType) {
  159. return FLEXArgInputObjectTypeJSON;
  160. } else {
  161. return FLEXArgInputObjectTypeAddress;
  162. }
  163. } else {
  164. return FLEXArgInputObjectTypeAddress;
  165. }
  166. }
  167. }
  168. @end