FLEXArgumentInputStructView.m 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. //
  2. // FLEXArgumentInputStructView.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/16/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXArgumentInputStructView.h"
  9. #import "FLEXArgumentInputViewFactory.h"
  10. #import "FLEXRuntimeUtility.h"
  11. @interface FLEXArgumentInputStructView ()
  12. @property (nonatomic) NSArray<FLEXArgumentInputView *> *argumentInputViews;
  13. @end
  14. @implementation FLEXArgumentInputStructView
  15. - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding
  16. {
  17. self = [super initWithArgumentTypeEncoding:typeEncoding];
  18. if (self) {
  19. NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray array];
  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. {
  45. [super setBackgroundColor:backgroundColor];
  46. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  47. inputView.backgroundColor = backgroundColor;
  48. }
  49. }
  50. - (void)setInputValue:(id)inputValue
  51. {
  52. if ([inputValue isKindOfClass:[NSValue class]]) {
  53. const char *structTypeEncoding = [inputValue objCType];
  54. if (strcmp(self.typeEncoding.UTF8String, structTypeEncoding) == 0) {
  55. NSUInteger valueSize = 0;
  56. @try {
  57. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  58. NSGetSizeAndAlignment(structTypeEncoding, &valueSize, NULL);
  59. } @catch (NSException *exception) { }
  60. if (valueSize > 0) {
  61. void *unboxedValue = malloc(valueSize);
  62. [inputValue getValue:unboxedValue];
  63. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName,
  64. const char *fieldTypeEncoding,
  65. NSString *prettyTypeEncoding,
  66. NSUInteger fieldIndex,
  67. NSUInteger fieldOffset) {
  68. void *fieldPointer = unboxedValue + fieldOffset;
  69. FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
  70. if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) {
  71. inputView.inputValue = (__bridge id)fieldPointer;
  72. } else {
  73. NSValue *boxedField = [FLEXRuntimeUtility valueForPrimitivePointer:fieldPointer objCType:fieldTypeEncoding];
  74. inputView.inputValue = boxedField;
  75. }
  76. }];
  77. free(unboxedValue);
  78. }
  79. }
  80. }
  81. }
  82. - (id)inputValue
  83. {
  84. NSValue *boxedStruct = nil;
  85. const char *structTypeEncoding = self.typeEncoding.UTF8String;
  86. NSUInteger structSize = 0;
  87. @try {
  88. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  89. NSGetSizeAndAlignment(structTypeEncoding, &structSize, NULL);
  90. } @catch (NSException *exception) { }
  91. if (structSize > 0) {
  92. void *unboxedStruct = malloc(structSize);
  93. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName,
  94. const char *fieldTypeEncoding,
  95. NSString *prettyTypeEncoding,
  96. NSUInteger fieldIndex,
  97. NSUInteger fieldOffset) {
  98. void *fieldPointer = unboxedStruct + fieldOffset;
  99. FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
  100. if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) {
  101. // Object fields
  102. memcpy(fieldPointer, (__bridge void *)inputView.inputValue, sizeof(id));
  103. } else {
  104. // Boxed primitive/struct fields
  105. id inputValue = inputView.inputValue;
  106. if ([inputValue isKindOfClass:[NSValue class]] && strcmp([inputValue objCType], fieldTypeEncoding) == 0) {
  107. [inputValue getValue:fieldPointer];
  108. }
  109. }
  110. }];
  111. boxedStruct = [NSValue value:unboxedStruct withObjCType:structTypeEncoding];
  112. free(unboxedStruct);
  113. }
  114. return boxedStruct;
  115. }
  116. - (BOOL)inputViewIsFirstResponder
  117. {
  118. BOOL isFirstResponder = NO;
  119. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  120. if ([inputView inputViewIsFirstResponder]) {
  121. isFirstResponder = YES;
  122. break;
  123. }
  124. }
  125. return isFirstResponder;
  126. }
  127. #pragma mark - Layout and Sizing
  128. - (void)layoutSubviews
  129. {
  130. [super layoutSubviews];
  131. CGFloat runningOriginY = self.topInputFieldVerticalLayoutGuide;
  132. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  133. CGSize inputFitSize = [inputView sizeThatFits:self.bounds.size];
  134. inputView.frame = CGRectMake(0, runningOriginY, inputFitSize.width, inputFitSize.height);
  135. runningOriginY = CGRectGetMaxY(inputView.frame) + [[self class] verticalPaddingBetweenFields];
  136. }
  137. }
  138. + (CGFloat)verticalPaddingBetweenFields
  139. {
  140. return 10.0;
  141. }
  142. - (CGSize)sizeThatFits:(CGSize)size
  143. {
  144. CGSize fitSize = [super sizeThatFits:size];
  145. CGSize constrainSize = CGSizeMake(size.width, CGFLOAT_MAX);
  146. CGFloat height = fitSize.height;
  147. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  148. height += [inputView sizeThatFits:constrainSize].height;
  149. height += [[self class] verticalPaddingBetweenFields];
  150. }
  151. return CGSizeMake(fitSize.width, height);
  152. }
  153. #pragma mark - Class Helpers
  154. + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
  155. {
  156. NSParameterAssert(type);
  157. if (type[0] == FLEXTypeEncodingStructBegin) {
  158. // We cannot support anything with bitfields or structs,
  159. // and this will throw an exception if it does
  160. @try {
  161. NSGetSizeAndAlignment(type, nil, nil);
  162. } @catch (NSException *exception) {
  163. return NO;
  164. }
  165. return YES;
  166. }
  167. return NO;
  168. }
  169. + (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
  170. {
  171. NSArray<NSString *> *customTitles = nil;
  172. if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
  173. customTitles = @[@"CGPoint origin", @"CGSize size"];
  174. } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
  175. customTitles = @[@"CGFloat x", @"CGFloat y"];
  176. } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
  177. customTitles = @[@"CGFloat width", @"CGFloat height"];
  178. } else if (strcmp(typeEncoding, @encode(CGVector)) == 0) {
  179. customTitles = @[@"CGFloat dx", @"CGFloat dy"];
  180. } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
  181. customTitles = @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"];
  182. } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
  183. customTitles = @[@"CGFloat horizontal", @"CGFloat vertical"];
  184. } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
  185. customTitles = @[@"NSUInteger location", @"NSUInteger length"];
  186. } else if (strcmp(typeEncoding, @encode(CATransform3D)) == 0) {
  187. customTitles = @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
  188. @"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
  189. @"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
  190. @"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"];
  191. } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
  192. customTitles = @[@"CGFloat a", @"CGFloat b",
  193. @"CGFloat c", @"CGFloat d",
  194. @"CGFloat tx", @"CGFloat ty"];
  195. } else {
  196. if (@available(iOS 11.0, *)) {
  197. if (strcmp(typeEncoding, @encode(NSDirectionalEdgeInsets)) == 0) {
  198. customTitles = @[@"CGFloat top", @"CGFloat leading",
  199. @"CGFloat bottom", @"CGFloat trailing"];
  200. }
  201. }
  202. }
  203. return customTitles;
  204. }
  205. @end