FLEXArgumentInputStructView.m 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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, strong) NSArray *argumentInputViews;
  13. @end
  14. @implementation FLEXArgumentInputStructView
  15. - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding
  16. {
  17. self = [super initWithArgumentTypeEncoding:typeEncoding];
  18. if (self) {
  19. NSMutableArray *inputViews = [NSMutableArray array];
  20. NSArray *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 objectAtIndex: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. NSGetSizeAndAlignment(structTypeEncoding, &valueSize, NULL);
  52. void *unboxedValue = malloc(valueSize);
  53. [inputValue getValue:unboxedValue];
  54. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
  55. void *fieldPointer = unboxedValue + fieldOffset;
  56. FLEXArgumentInputView *inputView = [self.argumentInputViews objectAtIndex:fieldIndex];
  57. if (fieldTypeEncoding[0] == @encode(id)[0] || fieldTypeEncoding[0] == @encode(Class)[0]) {
  58. inputView.inputValue = (__bridge id)fieldPointer;
  59. } else {
  60. NSValue *boxedField = [FLEXRuntimeUtility valueForPrimitivePointer:fieldPointer objCType:fieldTypeEncoding];
  61. inputView.inputValue = boxedField;
  62. }
  63. }];
  64. free(unboxedValue);
  65. }
  66. }
  67. }
  68. - (id)inputValue
  69. {
  70. const char *structTypeEncoding = [self.typeEncoding UTF8String];
  71. NSUInteger structSize = 0;
  72. NSGetSizeAndAlignment(structTypeEncoding, &structSize, NULL);
  73. void *unboxedStruct = malloc(structSize);
  74. [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
  75. void *fieldPointer = unboxedStruct + fieldOffset;
  76. FLEXArgumentInputView *inputView = [self.argumentInputViews objectAtIndex:fieldIndex];
  77. if (fieldTypeEncoding[0] == @encode(id)[0] || fieldTypeEncoding[0] == @encode(Class)[0]) {
  78. // Object fields
  79. memcpy(fieldPointer, (__bridge void *)inputView.inputValue, sizeof(id));
  80. } else {
  81. // Boxed primitive/struct fields
  82. id inputValue = inputView.inputValue;
  83. if ([inputValue isKindOfClass:[NSValue class]] && strcmp([inputValue objCType], fieldTypeEncoding) == 0) {
  84. [inputValue getValue:fieldPointer];
  85. }
  86. }
  87. }];
  88. NSValue *boxedStruct = [NSValue value:unboxedStruct withObjCType:structTypeEncoding];
  89. free(unboxedStruct);
  90. return boxedStruct;
  91. }
  92. - (BOOL)inputViewIsFirstResponder
  93. {
  94. BOOL isFirstResponder = NO;
  95. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  96. if ([inputView inputViewIsFirstResponder]) {
  97. isFirstResponder = YES;
  98. break;
  99. }
  100. }
  101. return isFirstResponder;
  102. }
  103. #pragma mark - Layout and Sizing
  104. - (void)layoutSubviews
  105. {
  106. [super layoutSubviews];
  107. CGFloat runningOriginY = self.topInputFieldVerticalLayoutGuide;
  108. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  109. CGSize inputFitSize = [inputView sizeThatFits:self.bounds.size];
  110. inputView.frame = CGRectMake(0, runningOriginY, inputFitSize.width, inputFitSize.height);
  111. runningOriginY = CGRectGetMaxY(inputView.frame) + [[self class] verticalPaddingBetweenFields];
  112. }
  113. }
  114. + (CGFloat)verticalPaddingBetweenFields
  115. {
  116. return 10.0;
  117. }
  118. - (CGSize)sizeThatFits:(CGSize)size
  119. {
  120. CGSize fitSize = [super sizeThatFits:size];
  121. CGSize constrainSize = CGSizeMake(size.width, CGFLOAT_MAX);
  122. CGFloat height = fitSize.height;
  123. for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
  124. height += [inputView sizeThatFits:constrainSize].height;
  125. height += [[self class] verticalPaddingBetweenFields];
  126. }
  127. return CGSizeMake(fitSize.width, height);
  128. }
  129. #pragma mark - Class Helpers
  130. + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
  131. {
  132. return type[0] == '{';
  133. }
  134. + (NSArray *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
  135. {
  136. NSArray *customTitles = nil;
  137. if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
  138. customTitles = @[@"CGPoint origin", @"CGSize size"];
  139. } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
  140. customTitles = @[@"CGFloat x", @"CGFloat y"];
  141. } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
  142. customTitles = @[@"CGFloat width", @"CGFloat height"];
  143. } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
  144. customTitles = @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"];
  145. } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
  146. customTitles = @[@"CGFloat horizontal", @"CGFloat vertical"];
  147. } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
  148. customTitles = @[@"NSUInteger location", @"NSUInteger length"];
  149. } else if (strcmp(typeEncoding, @encode(CATransform3D)) == 0) {
  150. customTitles = @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
  151. @"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
  152. @"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
  153. @"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"];
  154. } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
  155. customTitles = @[@"CGFloat a", @"CGFloat b",
  156. @"CGFloat c", @"CGFloat d",
  157. @"CGFloat tx", @"CGFloat ty"];
  158. }
  159. return customTitles;
  160. }
  161. @end