FLEXArgumentInputStructView.m 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. //
  2. // FLEXArgumentInputStructView.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/16/14.
  6. // Copyright (c) 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FLEXArgumentInputStructView.h"
  9. #import "FLEXArgumentInputViewFactory.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "FLEXTypeEncodingParser.h"
  12. @interface FLEXArgumentInputStructView ()
  13. @property (nonatomic) NSArray<FLEXArgumentInputView *> *argumentInputViews;
  14. @end
  15. @implementation FLEXArgumentInputStructView
  16. - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding {
  17. self = [super initWithArgumentTypeEncoding:typeEncoding];
  18. if (self) {
  19. NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray new];
  20. NSArray<NSString *> *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
  21. [FLEXRuntimeUtility enumerateTypesInStructEncoding:typeEncoding usingBlock:^(NSString *structName,
  22. const char *fieldTypeEncoding,
  23. NSString *prettyTypeEncoding,
  24. NSUInteger fieldIndex,
  25. NSUInteger fieldOffset) {
  26. FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
  27. inputView.targetSize = FLEXArgumentInputViewSizeSmall;
  28. if (fieldIndex < customTitles.count) {
  29. inputView.title = customTitles[fieldIndex];
  30. } else {
  31. inputView.title = [NSString stringWithFormat:@"%@ field %lu (%@)",
  32. structName, (unsigned long)fieldIndex, prettyTypeEncoding
  33. ];
  34. }
  35. [inputViews addObject:inputView];
  36. [self addSubview:inputView];
  37. }];
  38. self.argumentInputViews = inputViews;
  39. }
  40. return self;
  41. }
  42. #pragma mark - Superclass Overrides
  43. - (void)setBackgroundColor:(UIColor *)backgroundColor {
  44. [super setBackgroundColor:backgroundColor];
  45. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  46. inputView.backgroundColor = backgroundColor;
  47. }
  48. }
  49. - (void)setInputValue:(id)inputValue {
  50. if ([inputValue isKindOfClass:[NSValue class]]) {
  51. const char *structTypeEncoding = [inputValue objCType];
  52. if (strcmp(self.typeEncoding.UTF8String, structTypeEncoding) == 0) {
  53. NSUInteger valueSize = 0;
  54. if (FLEXGetSizeAndAlignment(structTypeEncoding, &valueSize, NULL)) {
  55. void *unboxedValue = malloc(valueSize);
  56. [inputValue getValue:unboxedValue];
  57. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName,
  58. const char *fieldTypeEncoding,
  59. NSString *prettyTypeEncoding,
  60. NSUInteger fieldIndex,
  61. NSUInteger fieldOffset) {
  62. void *fieldPointer = unboxedValue + fieldOffset;
  63. FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
  64. if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) {
  65. inputView.inputValue = (__bridge id)fieldPointer;
  66. } else {
  67. NSValue *boxedField = [FLEXRuntimeUtility valueForPrimitivePointer:fieldPointer objCType:fieldTypeEncoding];
  68. inputView.inputValue = boxedField;
  69. }
  70. }];
  71. free(unboxedValue);
  72. }
  73. }
  74. }
  75. }
  76. - (id)inputValue {
  77. NSValue *boxedStruct = nil;
  78. const char *structTypeEncoding = self.typeEncoding.UTF8String;
  79. NSUInteger structSize = 0;
  80. if (FLEXGetSizeAndAlignment(structTypeEncoding, &structSize, NULL)) {
  81. void *unboxedStruct = malloc(structSize);
  82. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName,
  83. const char *fieldTypeEncoding,
  84. NSString *prettyTypeEncoding,
  85. NSUInteger fieldIndex,
  86. NSUInteger fieldOffset) {
  87. void *fieldPointer = unboxedStruct + fieldOffset;
  88. FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
  89. if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) {
  90. // Object fields
  91. memcpy(fieldPointer, (__bridge void *)inputView.inputValue, sizeof(id));
  92. } else {
  93. // Boxed primitive/struct fields
  94. id inputValue = inputView.inputValue;
  95. if ([inputValue isKindOfClass:[NSValue class]] && strcmp([inputValue objCType], fieldTypeEncoding) == 0) {
  96. [inputValue getValue:fieldPointer];
  97. }
  98. }
  99. }];
  100. boxedStruct = [NSValue value:unboxedStruct withObjCType:structTypeEncoding];
  101. free(unboxedStruct);
  102. }
  103. return boxedStruct;
  104. }
  105. - (BOOL)inputViewIsFirstResponder {
  106. BOOL isFirstResponder = NO;
  107. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  108. if ([inputView inputViewIsFirstResponder]) {
  109. isFirstResponder = YES;
  110. break;
  111. }
  112. }
  113. return isFirstResponder;
  114. }
  115. #pragma mark - Layout and Sizing
  116. - (void)layoutSubviews {
  117. [super layoutSubviews];
  118. CGFloat runningOriginY = self.topInputFieldVerticalLayoutGuide;
  119. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  120. CGSize inputFitSize = [inputView sizeThatFits:self.bounds.size];
  121. inputView.frame = CGRectMake(0, runningOriginY, inputFitSize.width, inputFitSize.height);
  122. runningOriginY = CGRectGetMaxY(inputView.frame) + [[self class] verticalPaddingBetweenFields];
  123. }
  124. }
  125. + (CGFloat)verticalPaddingBetweenFields {
  126. return 10.0;
  127. }
  128. - (CGSize)sizeThatFits:(CGSize)size {
  129. CGSize fitSize = [super sizeThatFits:size];
  130. CGSize constrainSize = CGSizeMake(size.width, CGFLOAT_MAX);
  131. CGFloat height = fitSize.height;
  132. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  133. height += [inputView sizeThatFits:constrainSize].height;
  134. height += [[self class] verticalPaddingBetweenFields];
  135. }
  136. return CGSizeMake(fitSize.width, height);
  137. }
  138. #pragma mark - Class Helpers
  139. + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value {
  140. NSParameterAssert(type);
  141. if (type[0] == FLEXTypeEncodingStructBegin) {
  142. return FLEXGetSizeAndAlignment(type, nil, nil);
  143. }
  144. return NO;
  145. }
  146. + (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding {
  147. NSArray<NSString *> *customTitles = nil;
  148. if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
  149. customTitles = @[@"CGPoint origin", @"CGSize size"];
  150. } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
  151. customTitles = @[@"CGFloat x", @"CGFloat y"];
  152. } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
  153. customTitles = @[@"CGFloat width", @"CGFloat height"];
  154. } else if (strcmp(typeEncoding, @encode(CGVector)) == 0) {
  155. customTitles = @[@"CGFloat dx", @"CGFloat dy"];
  156. } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
  157. customTitles = @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"];
  158. } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
  159. customTitles = @[@"CGFloat horizontal", @"CGFloat vertical"];
  160. } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
  161. customTitles = @[@"NSUInteger location", @"NSUInteger length"];
  162. } else if (strcmp(typeEncoding, @encode(CATransform3D)) == 0) {
  163. customTitles = @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
  164. @"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
  165. @"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
  166. @"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"];
  167. } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
  168. customTitles = @[@"CGFloat a", @"CGFloat b",
  169. @"CGFloat c", @"CGFloat d",
  170. @"CGFloat tx", @"CGFloat ty"];
  171. } else {
  172. if (@available(iOS 11.0, *)) {
  173. if (strcmp(typeEncoding, @encode(NSDirectionalEdgeInsets)) == 0) {
  174. customTitles = @[@"CGFloat top", @"CGFloat leading",
  175. @"CGFloat bottom", @"CGFloat trailing"];
  176. }
  177. }
  178. }
  179. return customTitles;
  180. }
  181. @end