FLEXArgumentInputStructView.m 9.9 KB

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