You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
8.5 KiB

  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, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
  22. FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
  23. inputView.backgroundColor = self.backgroundColor;
  24. inputView.targetSize = FLEXArgumentInputViewSizeSmall;
  25. if (fieldIndex < customTitles.count) {
  26. inputView.title = customTitles[fieldIndex];
  27. } else {
  28. inputView.title = [NSString stringWithFormat:@"%@ field %lu (%@)", structName, (unsigned long)fieldIndex, prettyTypeEncoding];
  29. }
  30. [inputViews addObject:inputView];
  31. [self addSubview:inputView];
  32. }];
  33. self.argumentInputViews = inputViews;
  34. }
  35. return self;
  36. }
  37. #pragma mark - Superclass Overrides
  38. - (void)setBackgroundColor:(UIColor *)backgroundColor
  39. {
  40. [super setBackgroundColor:backgroundColor];
  41. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  42. inputView.backgroundColor = backgroundColor;
  43. }
  44. }
  45. - (void)setInputValue:(id)inputValue
  46. {
  47. if ([inputValue isKindOfClass:[NSValue class]]) {
  48. const char *structTypeEncoding = [inputValue objCType];
  49. if (strcmp(self.typeEncoding.UTF8String, structTypeEncoding) == 0) {
  50. NSUInteger valueSize = 0;
  51. @try {
  52. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  53. NSGetSizeAndAlignment(structTypeEncoding, &valueSize, NULL);
  54. } @catch (NSException *exception) { }
  55. if (valueSize > 0) {
  56. void *unboxedValue = malloc(valueSize);
  57. [inputValue getValue:unboxedValue];
  58. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
  59. void *fieldPointer = unboxedValue + fieldOffset;
  60. FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
  61. if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) {
  62. inputView.inputValue = (__bridge id)fieldPointer;
  63. } else {
  64. NSValue *boxedField = [FLEXRuntimeUtility valueForPrimitivePointer:fieldPointer objCType:fieldTypeEncoding];
  65. inputView.inputValue = boxedField;
  66. }
  67. }];
  68. free(unboxedValue);
  69. }
  70. }
  71. }
  72. }
  73. - (id)inputValue
  74. {
  75. NSValue *boxedStruct = nil;
  76. const char *structTypeEncoding = self.typeEncoding.UTF8String;
  77. NSUInteger structSize = 0;
  78. @try {
  79. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  80. NSGetSizeAndAlignment(structTypeEncoding, &structSize, NULL);
  81. } @catch (NSException *exception) { }
  82. if (structSize > 0) {
  83. void *unboxedStruct = malloc(structSize);
  84. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
  85. void *fieldPointer = unboxedStruct + fieldOffset;
  86. FLEXArgumentInputView *inputView = self.argumentInputViews[fieldIndex];
  87. if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) {
  88. // Object fields
  89. memcpy(fieldPointer, (__bridge void *)inputView.inputValue, sizeof(id));
  90. } else {
  91. // Boxed primitive/struct fields
  92. id inputValue = inputView.inputValue;
  93. if ([inputValue isKindOfClass:[NSValue class]] && strcmp([inputValue objCType], fieldTypeEncoding) == 0) {
  94. [inputValue getValue:fieldPointer];
  95. }
  96. }
  97. }];
  98. boxedStruct = [NSValue value:unboxedStruct withObjCType:structTypeEncoding];
  99. free(unboxedStruct);
  100. }
  101. return boxedStruct;
  102. }
  103. - (BOOL)inputViewIsFirstResponder
  104. {
  105. BOOL isFirstResponder = NO;
  106. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  107. if ([inputView inputViewIsFirstResponder]) {
  108. isFirstResponder = YES;
  109. break;
  110. }
  111. }
  112. return isFirstResponder;
  113. }
  114. #pragma mark - Layout and Sizing
  115. - (void)layoutSubviews
  116. {
  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. {
  127. return 10.0;
  128. }
  129. - (CGSize)sizeThatFits:(CGSize)size
  130. {
  131. CGSize fitSize = [super sizeThatFits:size];
  132. CGSize constrainSize = CGSizeMake(size.width, CGFLOAT_MAX);
  133. CGFloat height = fitSize.height;
  134. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  135. height += [inputView sizeThatFits:constrainSize].height;
  136. height += [[self class] verticalPaddingBetweenFields];
  137. }
  138. return CGSizeMake(fitSize.width, height);
  139. }
  140. #pragma mark - Class Helpers
  141. + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
  142. {
  143. return type && type[0] == FLEXTypeEncodingStructBegin;
  144. }
  145. + (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
  146. {
  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