Pārlūkot izejas kodu

Add FLEXArgumentInputStructView

The view is composed of argument input views for each of the fields in the struct.
Ryan Olson 12 gadi atpakaļ
vecāks
revīzija
e7eabaed08

+ 13 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputStructView.h

@@ -0,0 +1,13 @@
+//
+//  FLEXArgumentInputStructView.h
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/16/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXArgumentInputView.h"
+
+@interface FLEXArgumentInputStructView : FLEXArgumentInputView
+
+@end

+ 198 - 0
Classes/Editing/Argument Input Views/FLEXArgumentInputStructView.m

@@ -0,0 +1,198 @@
+//
+//  FLEXArgumentInputStructView.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 6/16/14.
+//  Copyright (c) 2014 Flipboard. All rights reserved.
+//
+
+#import "FLEXArgumentInputStructView.h"
+#import "FLEXArgumentInputViewFactory.h"
+#import "FLEXRuntimeUtility.h"
+
+@interface FLEXArgumentInputStructView ()
+
+@property (nonatomic, strong) NSArray *argumentInputViews;
+
+@end
+
+@implementation FLEXArgumentInputStructView
+
+- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding
+{
+    self = [super initWithArgumentTypeEncoding:typeEncoding];
+    if (self) {
+        NSMutableArray *inputViews = [NSMutableArray array];
+        NSArray *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
+        [FLEXRuntimeUtility enumerateTypesInStructEncoding:typeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
+            
+            FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
+            inputView.backgroundColor = self.backgroundColor;
+            inputView.targetSize = FLEXArgumentInputViewSizeSmall;
+            
+            if (fieldIndex < [customTitles count]) {
+                inputView.title = [customTitles objectAtIndex:fieldIndex];
+            } else {
+                inputView.title = [NSString stringWithFormat:@"%@ field %d (%@)", structName, fieldIndex, prettyTypeEncoding];
+            }
+
+            [inputViews addObject:inputView];
+            [self addSubview:inputView];
+        }];
+        self.argumentInputViews = inputViews;
+    }
+    return self;
+}
+
+
+#pragma mark - Superclass Overrides
+
+- (void)setBackgroundColor:(UIColor *)backgroundColor
+{
+    [super setBackgroundColor:backgroundColor];
+    for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
+        inputView.backgroundColor = backgroundColor;
+    }
+}
+
+- (void)setInputValue:(id)inputValue
+{
+    if ([inputValue isKindOfClass:[NSValue class]]) {
+        const char *structTypeEncoding = [inputValue objCType];
+        if (strcmp([self.typeEncoding UTF8String], structTypeEncoding) == 0) {
+            NSUInteger valueSize = 0;
+            NSGetSizeAndAlignment(structTypeEncoding, &valueSize, NULL);
+            void *unboxedValue = malloc(valueSize);
+            [inputValue getValue:unboxedValue];
+            [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
+                
+                void *fieldPointer = unboxedValue + fieldOffset;
+                FLEXArgumentInputView *inputView = [self.argumentInputViews objectAtIndex:fieldIndex];
+                
+                if (fieldTypeEncoding[0] == @encode(id)[0] || fieldTypeEncoding[0] == @encode(Class)[0]) {
+                    inputView.inputValue = (__bridge id)fieldPointer;
+                } else {
+                    NSValue *boxedField = [FLEXRuntimeUtility valueForPrimitivePointer:fieldPointer objCType:fieldTypeEncoding];
+                    inputView.inputValue = boxedField;
+                }
+            }];
+            free(unboxedValue);
+        }
+    }
+}
+
+- (id)inputValue
+{
+    const char *structTypeEncoding = [self.typeEncoding UTF8String];
+    NSUInteger structSize = 0;
+    NSGetSizeAndAlignment(structTypeEncoding, &structSize, NULL);
+    void *unboxedStruct = malloc(structSize);
+    
+    [FLEXRuntimeUtility enumerateTypesInStructEncoding:structTypeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
+        
+        void *fieldPointer = unboxedStruct + fieldOffset;
+        FLEXArgumentInputView *inputView = [self.argumentInputViews objectAtIndex:fieldIndex];
+        
+        if (fieldTypeEncoding[0] == @encode(id)[0] || fieldTypeEncoding[0] == @encode(Class)[0]) {
+            // Object fields
+            memcpy(fieldPointer, (__bridge void *)inputView.inputValue, sizeof(id));
+        } else {
+            // Boxed primitive/struct fields
+            id inputValue = inputView.inputValue;
+            if ([inputValue isKindOfClass:[NSValue class]] && strcmp([inputValue objCType], fieldTypeEncoding) == 0) {
+                [inputValue getValue:fieldPointer];
+            }
+        }
+    }];
+    
+    NSValue *boxedStruct = [NSValue value:unboxedStruct withObjCType:structTypeEncoding];
+    free(unboxedStruct);
+    
+    return boxedStruct;
+}
+
+- (BOOL)inputViewIsFirstResponder
+{
+    BOOL isFirstResponder = NO;
+    for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
+        if ([inputView inputViewIsFirstResponder]) {
+            isFirstResponder = YES;
+            break;
+        }
+    }
+    return isFirstResponder;
+}
+
+
+#pragma mark - Layout and Sizing
+
+- (void)layoutSubviews
+{
+    [super layoutSubviews];
+    
+    CGFloat runningOriginY = self.topInputFieldVerticalLayoutGuide;
+    
+    for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
+        CGSize inputFitSize = [inputView sizeThatFits:self.bounds.size];
+        inputView.frame = CGRectMake(0, runningOriginY, inputFitSize.width, inputFitSize.height);
+        runningOriginY = CGRectGetMaxY(inputView.frame) + [[self class] verticalPaddingBetweenFields];
+    }
+}
+
++ (CGFloat)verticalPaddingBetweenFields
+{
+    return 10.0;
+}
+
+- (CGSize)sizeThatFits:(CGSize)size
+{
+    CGSize fitSize = [super sizeThatFits:size];
+    
+    CGSize constrainSize = CGSizeMake(size.width, CGFLOAT_MAX);
+    CGFloat height = fitSize.height;
+    
+    for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
+        height += [inputView sizeThatFits:constrainSize].height;
+        height += [[self class] verticalPaddingBetweenFields];
+    }
+    
+    return CGSizeMake(fitSize.width, height);
+}
+
+
+#pragma mark - Class Helpers
+
++ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
+{
+    return type[0] == '{';
+}
+
++ (NSArray *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
+{
+    NSArray *customTitles = nil;
+    if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
+        customTitles = @[@"CGPoint origin", @"CGSize size"];
+    } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
+        customTitles = @[@"CGFloat x", @"CGFloat y"];
+    } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
+        customTitles = @[@"CGFloat width", @"CGFloat height"];
+    } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
+        customTitles = @[@"CGFloat top", @"CGFloat left", @"CGFloat bottom", @"CGFloat right"];
+    } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
+        customTitles = @[@"CGFloat horizontal", @"CGFloat vertical"];
+    } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
+        customTitles = @[@"NSUInteger location", @"NSUInteger length"];
+    } else if (strcmp(typeEncoding, @encode(CATransform3D)) == 0) {
+        customTitles = @[@"CGFloat m11", @"CGFloat m12", @"CGFloat m13", @"CGFloat m14",
+                         @"CGFloat m21", @"CGFloat m22", @"CGFloat m23", @"CGFloat m24",
+                         @"CGFloat m31", @"CGFloat m32", @"CGFloat m33", @"CGFloat m34",
+                         @"CGFloat m41", @"CGFloat m42", @"CGFloat m43", @"CGFloat m44"];
+    } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
+        customTitles = @[@"CGFloat a", @"CGFloat b",
+                         @"CGFloat c", @"CGFloat d",
+                         @"CGFloat tx", @"CGFloat ty"];
+    }
+    return customTitles;
+}
+
+@end

+ 4 - 1
Classes/Editing/Argument Input Views/FLEXArgumentInputViewFactory.m

@@ -11,6 +11,7 @@
 #import "FLEXArgumentInputJSONObjectView.h"
 #import "FLEXArgumentInputNumberView.h"
 #import "FLEXArgumentInputSwitchView.h"
+#import "FLEXArgumentInputStructView.h"
 
 @implementation FLEXArgumentInputViewFactory
 
@@ -32,7 +33,9 @@
     // Note that order is important here since multiple subclasses may support the same type.
     // An example is the number subclass and the bool subclass for the type @encode(BOOL).
     // Both work, but we'd prefer to use the bool subclass.
-    if ([FLEXArgumentInputSwitchView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
+    if ([FLEXArgumentInputStructView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
+        argumentInputViewSubclass = [FLEXArgumentInputStructView class];
+    } else if ([FLEXArgumentInputSwitchView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
         argumentInputViewSubclass = [FLEXArgumentInputSwitchView class];
     } else if ([FLEXArgumentInputNumberView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
         argumentInputViewSubclass = [FLEXArgumentInputNumberView class];