mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 04:44:14 -04:00 
			
		
		
		
	added files via upload
This commit is contained in:
		| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputColorView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/30/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputColorView : FLEXArgumentInputView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,311 @@ | ||||
| // | ||||
| //  FLEXArgumentInputColorView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/30/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputColorView.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| @protocol FLEXColorComponentInputViewDelegate; | ||||
|  | ||||
| @interface FLEXColorComponentInputView : UIView | ||||
|  | ||||
| @property (nonatomic) UISlider *slider; | ||||
| @property (nonatomic) UILabel *valueLabel; | ||||
|  | ||||
| @property (nonatomic, weak) id <FLEXColorComponentInputViewDelegate> delegate; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @protocol FLEXColorComponentInputViewDelegate <NSObject> | ||||
|  | ||||
| - (void)colorComponentInputViewValueDidChange:(FLEXColorComponentInputView *)colorComponentInputView; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| @implementation FLEXColorComponentInputView | ||||
|  | ||||
| - (id)initWithFrame:(CGRect)frame { | ||||
|     self = [super initWithFrame:frame]; | ||||
|     if (self) { | ||||
|         self.slider = [UISlider new]; | ||||
|         [self.slider addTarget:self action:@selector(sliderChanged:) forControlEvents:UIControlEventValueChanged]; | ||||
|         [self addSubview:self.slider]; | ||||
|          | ||||
|         self.valueLabel = [UILabel new]; | ||||
|         self.valueLabel.backgroundColor = self.backgroundColor; | ||||
|         self.valueLabel.font = [UIFont systemFontOfSize:14.0]; | ||||
|         self.valueLabel.textAlignment = NSTextAlignmentRight; | ||||
|         [self addSubview:self.valueLabel]; | ||||
|          | ||||
|         [self updateValueLabel]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setBackgroundColor:(UIColor *)backgroundColor { | ||||
|     [super setBackgroundColor:backgroundColor]; | ||||
|     self.slider.backgroundColor = backgroundColor; | ||||
|     self.valueLabel.backgroundColor = backgroundColor; | ||||
| } | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|      | ||||
|     const CGFloat kValueLabelWidth = 50.0; | ||||
|      | ||||
|     [self.slider sizeToFit]; | ||||
|     CGFloat sliderWidth = self.bounds.size.width - kValueLabelWidth; | ||||
|     self.slider.frame = CGRectMake(0, 0, sliderWidth, self.slider.frame.size.height); | ||||
|      | ||||
|     [self.valueLabel sizeToFit]; | ||||
|     CGFloat valueLabelOriginX = CGRectGetMaxX(self.slider.frame); | ||||
|     CGFloat valueLabelOriginY = FLEXFloor((self.slider.frame.size.height - self.valueLabel.frame.size.height) / 2.0); | ||||
|     self.valueLabel.frame = CGRectMake(valueLabelOriginX, valueLabelOriginY, kValueLabelWidth, self.valueLabel.frame.size.height); | ||||
| } | ||||
|  | ||||
| - (void)sliderChanged:(id)sender { | ||||
|     [self.delegate colorComponentInputViewValueDidChange:self]; | ||||
|     [self updateValueLabel]; | ||||
| } | ||||
|  | ||||
| - (void)updateValueLabel { | ||||
|     self.valueLabel.text = [NSString stringWithFormat:@"%.3f", self.slider.value]; | ||||
| } | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGFloat height = [self.slider sizeThatFits:size].height; | ||||
|     return CGSizeMake(size.width, height); | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
| @interface FLEXColorPreviewBox : UIView | ||||
|  | ||||
| @property (nonatomic) UIColor *color; | ||||
|  | ||||
| @property (nonatomic) UIView *colorOverlayView; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXColorPreviewBox | ||||
|  | ||||
| - (id)initWithFrame:(CGRect)frame { | ||||
|     self = [super initWithFrame:frame]; | ||||
|     if (self) { | ||||
|         self.layer.borderWidth = 1.0; | ||||
|         self.layer.borderColor = UIColor.blackColor.CGColor; | ||||
|         self.backgroundColor = [UIColor colorWithPatternImage:[[self class] backgroundPatternImage]]; | ||||
|          | ||||
|         self.colorOverlayView = [[UIView alloc] initWithFrame:self.bounds]; | ||||
|         self.colorOverlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | ||||
|         self.colorOverlayView.backgroundColor = UIColor.clearColor; | ||||
|         [self addSubview:self.colorOverlayView]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setColor:(UIColor *)color { | ||||
|     self.colorOverlayView.backgroundColor = color; | ||||
| } | ||||
|  | ||||
| - (UIColor *)color { | ||||
|     return self.colorOverlayView.backgroundColor; | ||||
| } | ||||
|  | ||||
| + (UIImage *)backgroundPatternImage { | ||||
|     const CGFloat kSquareDimension = 5.0; | ||||
|     CGSize squareSize = CGSizeMake(kSquareDimension, kSquareDimension); | ||||
|     CGSize imageSize = CGSizeMake(2.0 * kSquareDimension, 2.0 * kSquareDimension); | ||||
|      | ||||
|     UIGraphicsBeginImageContextWithOptions(imageSize, YES, UIScreen.mainScreen.scale); | ||||
|      | ||||
|     [UIColor.whiteColor setFill]; | ||||
|     UIRectFill(CGRectMake(0, 0, imageSize.width, imageSize.height)); | ||||
|      | ||||
|     [UIColor.grayColor setFill]; | ||||
|     UIRectFill(CGRectMake(squareSize.width, 0, squareSize.width, squareSize.height)); | ||||
|     UIRectFill(CGRectMake(0, squareSize.height, squareSize.width, squareSize.height)); | ||||
|      | ||||
|     UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | ||||
|     UIGraphicsEndImageContext(); | ||||
|      | ||||
|     return image; | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
| @interface FLEXArgumentInputColorView () <FLEXColorComponentInputViewDelegate> | ||||
|  | ||||
| @property (nonatomic) FLEXColorPreviewBox *colorPreviewBox; | ||||
| @property (nonatomic) UILabel *hexLabel; | ||||
| @property (nonatomic) FLEXColorComponentInputView *alphaInput; | ||||
| @property (nonatomic) FLEXColorComponentInputView *redInput; | ||||
| @property (nonatomic) FLEXColorComponentInputView *greenInput; | ||||
| @property (nonatomic) FLEXColorComponentInputView *blueInput; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputColorView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.colorPreviewBox = [FLEXColorPreviewBox new]; | ||||
|         [self addSubview:self.colorPreviewBox]; | ||||
|          | ||||
|         self.hexLabel = [UILabel new]; | ||||
|         self.hexLabel.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.9]; | ||||
|         self.hexLabel.textAlignment = NSTextAlignmentCenter; | ||||
|         self.hexLabel.font = [UIFont systemFontOfSize:12.0]; | ||||
|         [self addSubview:self.hexLabel]; | ||||
|          | ||||
|         self.alphaInput = [FLEXColorComponentInputView new]; | ||||
|         self.alphaInput.slider.minimumTrackTintColor = UIColor.blackColor; | ||||
|         self.alphaInput.delegate = self; | ||||
|         [self addSubview:self.alphaInput]; | ||||
|          | ||||
|         self.redInput = [FLEXColorComponentInputView new]; | ||||
|         self.redInput.slider.minimumTrackTintColor = UIColor.redColor; | ||||
|         self.redInput.delegate = self; | ||||
|         [self addSubview:self.redInput]; | ||||
|          | ||||
|         self.greenInput = [FLEXColorComponentInputView new]; | ||||
|         self.greenInput.slider.minimumTrackTintColor = UIColor.greenColor; | ||||
|         self.greenInput.delegate = self; | ||||
|         [self addSubview:self.greenInput]; | ||||
|          | ||||
|         self.blueInput = [FLEXColorComponentInputView new]; | ||||
|         self.blueInput.slider.minimumTrackTintColor = UIColor.blueColor; | ||||
|         self.blueInput.delegate = self; | ||||
|         [self addSubview:self.blueInput]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setBackgroundColor:(UIColor *)backgroundColor { | ||||
|     [super setBackgroundColor:backgroundColor]; | ||||
|     self.alphaInput.backgroundColor = backgroundColor; | ||||
|     self.redInput.backgroundColor = backgroundColor; | ||||
|     self.greenInput.backgroundColor = backgroundColor; | ||||
|     self.blueInput.backgroundColor = backgroundColor; | ||||
| } | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|      | ||||
|     CGFloat runningOriginY = 0; | ||||
|     CGSize constrainSize = CGSizeMake(self.bounds.size.width, CGFLOAT_MAX); | ||||
|      | ||||
|     self.colorPreviewBox.frame = CGRectMake(0, runningOriginY, self.bounds.size.width, [[self class] colorPreviewBoxHeight]); | ||||
|     runningOriginY = CGRectGetMaxY(self.colorPreviewBox.frame) + [[self class] inputViewVerticalPadding]; | ||||
|      | ||||
|     [self.hexLabel sizeToFit]; | ||||
|     const CGFloat kLabelVerticalOutsetAmount = 0.0; | ||||
|     const CGFloat kLabelHorizontalOutsetAmount = 2.0; | ||||
|     UIEdgeInsets labelOutset = UIEdgeInsetsMake(-kLabelVerticalOutsetAmount, -kLabelHorizontalOutsetAmount, -kLabelVerticalOutsetAmount, -kLabelHorizontalOutsetAmount); | ||||
|     self.hexLabel.frame = UIEdgeInsetsInsetRect(self.hexLabel.frame, labelOutset); | ||||
|     CGFloat hexLabelOriginX = self.colorPreviewBox.layer.borderWidth; | ||||
|     CGFloat hexLabelOriginY = CGRectGetMaxY(self.colorPreviewBox.frame) - self.colorPreviewBox.layer.borderWidth - self.hexLabel.frame.size.height; | ||||
|     self.hexLabel.frame = CGRectMake(hexLabelOriginX, hexLabelOriginY, self.hexLabel.frame.size.width, self.hexLabel.frame.size.height); | ||||
|      | ||||
|     NSArray<FLEXColorComponentInputView *> *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput]; | ||||
|     for (FLEXColorComponentInputView *inputView in colorComponentInputViews) { | ||||
|         CGSize fitSize = [inputView sizeThatFits:constrainSize]; | ||||
|         inputView.frame = CGRectMake(0, runningOriginY, fitSize.width, fitSize.height); | ||||
|         runningOriginY = CGRectGetMaxY(inputView.frame) + [[self class] inputViewVerticalPadding]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     if ([inputValue isKindOfClass:[UIColor class]]) { | ||||
|         [self updateWithColor:inputValue]; | ||||
|     } else if ([inputValue isKindOfClass:[NSValue class]]) { | ||||
|         const char *type = [inputValue objCType]; | ||||
|         if (strcmp(type, @encode(CGColorRef)) == 0) { | ||||
|             CGColorRef colorRef; | ||||
|             [inputValue getValue:&colorRef]; | ||||
|             UIColor *color = [[UIColor alloc] initWithCGColor:colorRef]; | ||||
|             [self updateWithColor:color]; | ||||
|         } | ||||
|     } else { | ||||
|         [self updateWithColor:UIColor.clearColor]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     return [UIColor colorWithRed:self.redInput.slider.value green:self.greenInput.slider.value blue:self.blueInput.slider.value alpha:self.alphaInput.slider.value]; | ||||
| } | ||||
|  | ||||
| - (void)colorComponentInputViewValueDidChange:(FLEXColorComponentInputView *)colorComponentInputView { | ||||
|     [self updateColorPreview]; | ||||
| } | ||||
|  | ||||
| - (void)updateWithColor:(UIColor *)color { | ||||
|     CGFloat red, green, blue, white, alpha; | ||||
|     if ([color getRed:&red green:&green blue:&blue alpha:&alpha]) { | ||||
|         self.alphaInput.slider.value = alpha; | ||||
|         [self.alphaInput updateValueLabel]; | ||||
|         self.redInput.slider.value = red; | ||||
|         [self.redInput updateValueLabel]; | ||||
|         self.greenInput.slider.value = green; | ||||
|         [self.greenInput updateValueLabel]; | ||||
|         self.blueInput.slider.value = blue; | ||||
|         [self.blueInput updateValueLabel]; | ||||
|     } else if ([color getWhite:&white alpha:&alpha]) { | ||||
|         self.alphaInput.slider.value = alpha; | ||||
|         [self.alphaInput updateValueLabel]; | ||||
|         self.redInput.slider.value = white; | ||||
|         [self.redInput updateValueLabel]; | ||||
|         self.greenInput.slider.value = white; | ||||
|         [self.greenInput updateValueLabel]; | ||||
|         self.blueInput.slider.value = white; | ||||
|         [self.blueInput updateValueLabel]; | ||||
|     } | ||||
|     [self updateColorPreview]; | ||||
| } | ||||
|  | ||||
| - (void)updateColorPreview { | ||||
|     self.colorPreviewBox.color = self.inputValue; | ||||
|     unsigned char redByte = self.redInput.slider.value * 255; | ||||
|     unsigned char greenByte = self.greenInput.slider.value * 255; | ||||
|     unsigned char blueByte = self.blueInput.slider.value * 255; | ||||
|     self.hexLabel.text = [NSString stringWithFormat:@"#%02X%02X%02X", redByte, greenByte, blueByte]; | ||||
|     [self setNeedsLayout]; | ||||
| } | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGFloat height = 0; | ||||
|     height += [[self class] colorPreviewBoxHeight]; | ||||
|     height += [[self class] inputViewVerticalPadding]; | ||||
|     height += [self.alphaInput sizeThatFits:size].height; | ||||
|     height += [[self class] inputViewVerticalPadding]; | ||||
|     height += [self.redInput sizeThatFits:size].height; | ||||
|     height += [[self class] inputViewVerticalPadding]; | ||||
|     height += [self.greenInput sizeThatFits:size].height; | ||||
|     height += [[self class] inputViewVerticalPadding]; | ||||
|     height += [self.blueInput sizeThatFits:size].height; | ||||
|     return CGSizeMake(size.width, height); | ||||
| } | ||||
|  | ||||
| + (CGFloat)inputViewVerticalPadding { | ||||
|     return 10.0; | ||||
| } | ||||
|  | ||||
| + (CGFloat)colorPreviewBoxHeight { | ||||
|     return 40.0; | ||||
| } | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type); | ||||
|  | ||||
|     // We don't care if currentValue is a color or not; we will default to +clearColor | ||||
|     return (strcmp(type, @encode(CGColorRef)) == 0) || (strcmp(type, FLEXEncodeClass(UIColor)) == 0); | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputDataView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Daniel Rodriguez Troitino on 2/14/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputDateView : FLEXArgumentInputView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,58 @@ | ||||
| // | ||||
| //  FLEXArgumentInputDataView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Daniel Rodriguez Troitino on 2/14/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputDateView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| @interface FLEXArgumentInputDateView () | ||||
|  | ||||
| @property (nonatomic) UIDatePicker *datePicker; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputDateView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.datePicker = [UIDatePicker new]; | ||||
|         self.datePicker.datePickerMode = UIDatePickerModeDateAndTime; | ||||
|         // Using UTC, because that's what the NSDate description prints | ||||
|         self.datePicker.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; | ||||
|         self.datePicker.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; | ||||
|         [self addSubview:self.datePicker]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     if ([inputValue isKindOfClass:[NSDate class]]) { | ||||
|         self.datePicker.date = inputValue; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     return self.datePicker.date; | ||||
| } | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|     self.datePicker.frame = self.bounds; | ||||
| } | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGFloat height = [self.datePicker sizeThatFits:size].height; | ||||
|     return CGSizeMake(size.width, height); | ||||
| } | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type); | ||||
|     return strcmp(type, FLEXEncodeClass(NSDate)) == 0; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputFontView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/28/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputFontView : FLEXArgumentInputView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,109 @@ | ||||
| // | ||||
| //  FLEXArgumentInputFontView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/28/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputFontView.h" | ||||
| #import "FLEXArgumentInputViewFactory.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXArgumentInputFontsPickerView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputFontView () | ||||
|  | ||||
| @property (nonatomic) FLEXArgumentInputView *fontNameInput; | ||||
| @property (nonatomic) FLEXArgumentInputView *pointSizeInput; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputFontView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.fontNameInput = [[FLEXArgumentInputFontsPickerView alloc] initWithArgumentTypeEncoding:FLEXEncodeClass(NSString)]; | ||||
|         self.fontNameInput.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|         self.fontNameInput.title = @"Font Name:"; | ||||
|         [self addSubview:self.fontNameInput]; | ||||
|          | ||||
|         self.pointSizeInput = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:@encode(CGFloat)]; | ||||
|         self.pointSizeInput.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|         self.pointSizeInput.title = @"Point Size:"; | ||||
|         [self addSubview:self.pointSizeInput]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setBackgroundColor:(UIColor *)backgroundColor { | ||||
|     [super setBackgroundColor:backgroundColor]; | ||||
|     self.fontNameInput.backgroundColor = backgroundColor; | ||||
|     self.pointSizeInput.backgroundColor = backgroundColor; | ||||
| } | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     if ([inputValue isKindOfClass:[UIFont class]]) { | ||||
|         UIFont *font = (UIFont *)inputValue; | ||||
|         self.fontNameInput.inputValue = font.fontName; | ||||
|         self.pointSizeInput.inputValue = @(font.pointSize); | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     CGFloat pointSize = 0; | ||||
|     if ([self.pointSizeInput.inputValue isKindOfClass:[NSValue class]]) { | ||||
|         NSValue *pointSizeValue = (NSValue *)self.pointSizeInput.inputValue; | ||||
|         if (strcmp([pointSizeValue objCType], @encode(CGFloat)) == 0) { | ||||
|             [pointSizeValue getValue:&pointSize]; | ||||
|         } | ||||
|     } | ||||
|     return [UIFont fontWithName:self.fontNameInput.inputValue size:pointSize]; | ||||
| } | ||||
|  | ||||
| - (BOOL)inputViewIsFirstResponder { | ||||
|     return [self.fontNameInput inputViewIsFirstResponder] || [self.pointSizeInput inputViewIsFirstResponder]; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Layout and Sizing | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|      | ||||
|     CGFloat runningOriginY = self.topInputFieldVerticalLayoutGuide; | ||||
|      | ||||
|     CGSize fontNameFitSize = [self.fontNameInput sizeThatFits:self.bounds.size]; | ||||
|     self.fontNameInput.frame = CGRectMake(0, runningOriginY, fontNameFitSize.width, fontNameFitSize.height); | ||||
|     runningOriginY = CGRectGetMaxY(self.fontNameInput.frame) + [[self class] verticalPaddingBetweenFields]; | ||||
|      | ||||
|     CGSize pointSizeFitSize = [self.pointSizeInput sizeThatFits:self.bounds.size]; | ||||
|     self.pointSizeInput.frame = CGRectMake(0, runningOriginY, pointSizeFitSize.width, pointSizeFitSize.height); | ||||
| } | ||||
|  | ||||
| + (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; | ||||
|     height += [self.fontNameInput sizeThatFits:constrainSize].height; | ||||
|     height += [[self class] verticalPaddingBetweenFields]; | ||||
|     height += [self.pointSizeInput sizeThatFits:constrainSize].height; | ||||
|      | ||||
|     return CGSizeMake(fitSize.width, height); | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type); | ||||
|     return strcmp(type, FLEXEncodeClass(UIFont)) == 0; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,12 @@ | ||||
| // | ||||
| //  FLEXArgumentInputFontsPickerView.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by 啟倫 陳 on 2014/7/27. | ||||
| //  Copyright (c) 2014年 f. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputTextView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputFontsPickerView : FLEXArgumentInputTextView <UIPickerViewDataSource, UIPickerViewDelegate> | ||||
| @end | ||||
| @@ -0,0 +1,96 @@ | ||||
| // | ||||
| //  FLEXArgumentInputFontsPickerView.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by 啟倫 陳 on 2014/7/27. | ||||
| //  Copyright (c) 2014年 f. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputFontsPickerView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| @interface FLEXArgumentInputFontsPickerView () | ||||
|  | ||||
| @property (nonatomic) NSMutableArray<NSString *> *availableFonts; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| @implementation FLEXArgumentInputFontsPickerView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|         [self createAvailableFonts]; | ||||
|         self.inputTextView.inputView = [self createFontsPicker]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     self.inputTextView.text = inputValue; | ||||
|     if ([self.availableFonts indexOfObject:inputValue] == NSNotFound) { | ||||
|         [self.availableFonts insertObject:inputValue atIndex:0]; | ||||
|     } | ||||
|     [(UIPickerView *)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO]; | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     return self.inputTextView.text.length > 0 ? [self.inputTextView.text copy] : nil; | ||||
| } | ||||
|  | ||||
| #pragma mark - private | ||||
|  | ||||
| - (UIPickerView*)createFontsPicker { | ||||
|     UIPickerView *fontsPicker = [UIPickerView new]; | ||||
|     fontsPicker.dataSource = self; | ||||
|     fontsPicker.delegate = self; | ||||
|     fontsPicker.showsSelectionIndicator = YES; | ||||
|     return fontsPicker; | ||||
| } | ||||
|  | ||||
| - (void)createAvailableFonts { | ||||
|     NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray new]; | ||||
|     for (NSString *eachFontFamily in UIFont.familyNames) { | ||||
|         for (NSString *eachFontName in [UIFont fontNamesForFamilyName:eachFontFamily]) { | ||||
|             [unsortedFontsArray addObject:eachFontName]; | ||||
|         } | ||||
|     } | ||||
|     self.availableFonts = [NSMutableArray arrayWithArray:[unsortedFontsArray sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]]; | ||||
| } | ||||
|  | ||||
| #pragma mark - UIPickerViewDataSource | ||||
|  | ||||
| - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { | ||||
|     return self.availableFonts.count; | ||||
| } | ||||
|  | ||||
| #pragma mark - UIPickerViewDelegate | ||||
|  | ||||
| - (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view { | ||||
|     UILabel *fontLabel; | ||||
|     if (!view) { | ||||
|         fontLabel = [UILabel new]; | ||||
|         fontLabel.backgroundColor = UIColor.clearColor; | ||||
|         fontLabel.textAlignment = NSTextAlignmentCenter; | ||||
|     } else { | ||||
|         fontLabel = (UILabel*)view; | ||||
|     } | ||||
|     UIFont *font = [UIFont fontWithName:self.availableFonts[row] size:15.0]; | ||||
|     NSDictionary<NSString *, id> *attributesDictionary = [NSDictionary<NSString *, id> dictionaryWithObject:font forKey:NSFontAttributeName]; | ||||
|     NSAttributedString *attributesString = [[NSAttributedString alloc] initWithString:self.availableFonts[row] attributes:attributesDictionary]; | ||||
|     fontLabel.attributedText = attributesString; | ||||
|     [fontLabel sizeToFit]; | ||||
|     return fontLabel; | ||||
| } | ||||
|  | ||||
| - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { | ||||
|     self.inputTextView.text = self.availableFonts[row]; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputNotSupportedView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/18/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputTextView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputNotSupportedView : FLEXArgumentInputTextView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,25 @@ | ||||
| // | ||||
| //  FLEXArgumentInputNotSupportedView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/18/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputNotSupportedView.h" | ||||
| #import "FLEXColor.h" | ||||
|  | ||||
| @implementation FLEXArgumentInputNotSupportedView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.inputTextView.userInteractionEnabled = NO; | ||||
|         self.inputTextView.backgroundColor = [FLEXColor secondaryGroupedBackgroundColorWithAlpha:0.5]; | ||||
|         self.inputPlaceholderText = @"nil  (type not supported)"; | ||||
|         self.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputNumberView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputTextView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputNumberView : FLEXArgumentInputTextView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,62 @@ | ||||
| // | ||||
| //  FLEXArgumentInputNumberView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputNumberView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| @implementation FLEXArgumentInputNumberView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.inputTextView.keyboardType = UIKeyboardTypeNumbersAndPunctuation; | ||||
|         self.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     if ([inputValue respondsToSelector:@selector(stringValue)]) { | ||||
|         self.inputTextView.text = [inputValue stringValue]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     return [FLEXRuntimeUtility valueForNumberWithObjCType:self.typeEncoding.UTF8String fromInputString:self.inputTextView.text]; | ||||
| } | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type); | ||||
|      | ||||
|     static NSArray<NSString *> *supportedTypes = nil; | ||||
|     static dispatch_once_t onceToken; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         supportedTypes = @[ | ||||
|             @FLEXEncodeClass(NSNumber), | ||||
|             @FLEXEncodeClass(NSDecimalNumber), | ||||
|             @(@encode(char)), | ||||
|             @(@encode(int)), | ||||
|             @(@encode(short)), | ||||
|             @(@encode(long)), | ||||
|             @(@encode(long long)), | ||||
|             @(@encode(unsigned char)), | ||||
|             @(@encode(unsigned int)), | ||||
|             @(@encode(unsigned short)), | ||||
|             @(@encode(unsigned long)), | ||||
|             @(@encode(unsigned long long)), | ||||
|             @(@encode(float)), | ||||
|             @(@encode(double)), | ||||
|             @(@encode(long double)) | ||||
|         ]; | ||||
|     }); | ||||
|      | ||||
|     return type && [supportedTypes containsObject:@(type)]; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputObjectView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputTextView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputObjectView : FLEXArgumentInputTextView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,232 @@ | ||||
| // | ||||
| //  FLEXArgumentInputJSONObjectView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputObjectView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| static const CGFloat kSegmentInputMargin = 10; | ||||
|  | ||||
| typedef NS_ENUM(NSUInteger, FLEXArgInputObjectType) { | ||||
|     FLEXArgInputObjectTypeJSON, | ||||
|     FLEXArgInputObjectTypeAddress | ||||
| }; | ||||
|  | ||||
| @interface FLEXArgumentInputObjectView () | ||||
|  | ||||
| @property (nonatomic) UISegmentedControl *objectTypeSegmentControl; | ||||
| @property (nonatomic) FLEXArgInputObjectType inputType; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputObjectView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         // Start with the numbers and punctuation keyboard since quotes, curly braces, or | ||||
|         // square brackets are likely to be the first characters type for the JSON. | ||||
|         self.inputTextView.keyboardType = UIKeyboardTypeNumbersAndPunctuation; | ||||
|         self.targetSize = FLEXArgumentInputViewSizeLarge; | ||||
|  | ||||
|         self.objectTypeSegmentControl = [[UISegmentedControl alloc] initWithItems:@[@"Value", @"Address"]]; | ||||
|         [self.objectTypeSegmentControl addTarget:self action:@selector(didChangeType) forControlEvents:UIControlEventValueChanged]; | ||||
|         self.objectTypeSegmentControl.selectedSegmentIndex = 0; | ||||
|         [self addSubview:self.objectTypeSegmentControl]; | ||||
|  | ||||
|         self.inputType = [[self class] preferredDefaultTypeForObjCType:typeEncoding withCurrentValue:nil]; | ||||
|         self.objectTypeSegmentControl.selectedSegmentIndex = self.inputType; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)didChangeType { | ||||
|     self.inputType = self.objectTypeSegmentControl.selectedSegmentIndex; | ||||
|  | ||||
|     if (super.inputValue) { | ||||
|         // Trigger an update to the text field to show | ||||
|         // the address of the stored object we were given, | ||||
|         // or to show a JSON representation of the object | ||||
|         [self populateTextAreaFromValue:super.inputValue]; | ||||
|     } else { | ||||
|         // Clear the text field | ||||
|         [self populateTextAreaFromValue:nil]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setInputType:(FLEXArgInputObjectType)inputType { | ||||
|     if (_inputType == inputType) return; | ||||
|  | ||||
|     _inputType = inputType; | ||||
|  | ||||
|     // Resize input view | ||||
|     switch (inputType) { | ||||
|         case FLEXArgInputObjectTypeJSON: | ||||
|             self.targetSize = FLEXArgumentInputViewSizeLarge; | ||||
|             break; | ||||
|         case FLEXArgInputObjectTypeAddress: | ||||
|             self.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     // Change placeholder | ||||
|     switch (inputType) { | ||||
|         case FLEXArgInputObjectTypeJSON: | ||||
|             self.inputPlaceholderText = | ||||
|             @"You can put any valid JSON here, such as a string, number, array, or dictionary:" | ||||
|             "\n\"This is a string\"" | ||||
|             "\n1234" | ||||
|             "\n{ \"name\": \"Bob\", \"age\": 47 }" | ||||
|             "\n[" | ||||
|             "\n   1, 2, 3" | ||||
|             "\n]"; | ||||
|             break; | ||||
|         case FLEXArgInputObjectTypeAddress: | ||||
|             self.inputPlaceholderText = @"0x0000deadb33f"; | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     [self setNeedsLayout]; | ||||
|     [self.superview setNeedsLayout]; | ||||
| } | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     super.inputValue = inputValue; | ||||
|     [self populateTextAreaFromValue:inputValue]; | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     switch (self.inputType) { | ||||
|         case FLEXArgInputObjectTypeJSON: | ||||
|             return [FLEXRuntimeUtility objectValueFromEditableJSONString:self.inputTextView.text]; | ||||
|         case FLEXArgInputObjectTypeAddress: { | ||||
|             NSScanner *scanner = [NSScanner scannerWithString:self.inputTextView.text]; | ||||
|  | ||||
|             unsigned long long objectPointerValue; | ||||
|             if ([scanner scanHexLongLong:&objectPointerValue]) { | ||||
|                 return (__bridge id)(void *)objectPointerValue; | ||||
|             } | ||||
|  | ||||
|             return nil; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)populateTextAreaFromValue:(id)value { | ||||
|     if (!value) { | ||||
|         self.inputTextView.text = nil; | ||||
|     } else { | ||||
|         if (self.inputType == FLEXArgInputObjectTypeJSON) { | ||||
|             self.inputTextView.text = [FLEXRuntimeUtility editableJSONStringForObject:value]; | ||||
|         } else if (self.inputType == FLEXArgInputObjectTypeAddress) { | ||||
|             self.inputTextView.text = [NSString stringWithFormat:@"%p", value]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Delegate methods are not called for programmatic changes | ||||
|     [self textViewDidChange:self.inputTextView]; | ||||
| } | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGSize fitSize = [super sizeThatFits:size]; | ||||
|     fitSize.height += [self.objectTypeSegmentControl sizeThatFits:size].height + kSegmentInputMargin; | ||||
|  | ||||
|     return fitSize; | ||||
| } | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     CGFloat segmentHeight = [self.objectTypeSegmentControl sizeThatFits:self.frame.size].height; | ||||
|     self.objectTypeSegmentControl.frame = CGRectMake( | ||||
|         0.0, | ||||
|         // Our segmented control is taking the position | ||||
|         // of the text view, as far as super is concerned, | ||||
|         // and we override this property to be different | ||||
|         super.topInputFieldVerticalLayoutGuide, | ||||
|         self.frame.size.width, | ||||
|         segmentHeight | ||||
|     ); | ||||
|  | ||||
|     [super layoutSubviews]; | ||||
| } | ||||
|  | ||||
| - (CGFloat)topInputFieldVerticalLayoutGuide { | ||||
|     // Our text view is offset from the segmented control | ||||
|     CGFloat segmentHeight = [self.objectTypeSegmentControl sizeThatFits:self.frame.size].height; | ||||
|     return segmentHeight + super.topInputFieldVerticalLayoutGuide + kSegmentInputMargin; | ||||
| } | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type); | ||||
|     // Must be object type | ||||
|     return type[0] == FLEXTypeEncodingObjcObject || type[0] == FLEXTypeEncodingObjcClass; | ||||
| } | ||||
|  | ||||
| + (FLEXArgInputObjectType)preferredDefaultTypeForObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type[0] == FLEXTypeEncodingObjcObject || type[0] == FLEXTypeEncodingObjcClass); | ||||
|  | ||||
|     if (value) { | ||||
|         // If there's a current value, it must be serializable to JSON | ||||
|         // to display the JSON editor. Otherwise display the address field. | ||||
|         if ([FLEXRuntimeUtility editableJSONStringForObject:value]) { | ||||
|             return FLEXArgInputObjectTypeJSON; | ||||
|         } else { | ||||
|             return FLEXArgInputObjectTypeAddress; | ||||
|         } | ||||
|     } else { | ||||
|         // Otherwise, see if we have more type information than just 'id'. | ||||
|         // If we do, make sure the encoding is something serializable to JSON. | ||||
|         // Properties and ivars keep more detailed type encoding information than method arguments. | ||||
|         if (strcmp(type, @encode(id)) != 0) { | ||||
|             BOOL isJSONSerializableType = NO; | ||||
|  | ||||
|             // Parse class name out of the string, | ||||
|             // which is in the form `@"ClassName"` | ||||
|             Class cls = NSClassFromString(({ | ||||
|                 NSString *className = nil; | ||||
|                 NSScanner *scan = [NSScanner scannerWithString:@(type)]; | ||||
|                 NSCharacterSet *allowed = [NSCharacterSet | ||||
|                     characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$" | ||||
|                 ]; | ||||
|  | ||||
|                 // Skip over the @" then scan the name | ||||
|                 if ([scan scanString:@"@\"" intoString:nil]) { | ||||
|                     [scan scanCharactersFromSet:allowed intoString:&className]; | ||||
|                 } | ||||
|  | ||||
|                 className; | ||||
|             })); | ||||
|  | ||||
|             // Note: we can't use @encode(NSString) here because that drops | ||||
|             // the class information and just goes to @encode(id). | ||||
|             NSArray<Class> *jsonTypes = @[ | ||||
|                 [NSString class], | ||||
|                 [NSNumber class], | ||||
|                 [NSArray class], | ||||
|                 [NSDictionary class], | ||||
|             ]; | ||||
|  | ||||
|             // Look for matching types | ||||
|             for (Class jsonClass in jsonTypes) { | ||||
|                 if ([cls isSubclassOfClass:jsonClass]) { | ||||
|                     isJSONSerializableType = YES; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (isJSONSerializableType) { | ||||
|                 return FLEXArgInputObjectTypeJSON; | ||||
|             } else { | ||||
|                 return FLEXArgInputObjectTypeAddress; | ||||
|             } | ||||
|         } else { | ||||
|             return FLEXArgInputObjectTypeAddress; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputStringView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/28/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputTextView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputStringView : FLEXArgumentInputTextView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,129 @@ | ||||
| // | ||||
| //  FLEXArgumentInputStringView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/28/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputStringView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| @implementation FLEXArgumentInputStringView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         FLEXTypeEncoding type = typeEncoding[0]; | ||||
|         if (type == FLEXTypeEncodingConst) { | ||||
|             // A crash here would mean an invalid type encoding string | ||||
|             type = typeEncoding[1]; | ||||
|         } | ||||
|  | ||||
|         // Selectors don't need a multi-line text box | ||||
|         if (type == FLEXTypeEncodingSelector) { | ||||
|             self.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|         } else { | ||||
|             self.targetSize = FLEXArgumentInputViewSizeLarge; | ||||
|         } | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     if ([inputValue isKindOfClass:[NSString class]]) { | ||||
|         self.inputTextView.text = inputValue; | ||||
|     } else if ([inputValue isKindOfClass:[NSValue class]]) { | ||||
|         NSValue *value = (id)inputValue; | ||||
|         NSParameterAssert(strlen(value.objCType) == 1); | ||||
|  | ||||
|         // C-String or SEL from NSValue | ||||
|         FLEXTypeEncoding type = value.objCType[0]; | ||||
|         if (type == FLEXTypeEncodingConst) { | ||||
|             // A crash here would mean an invalid type encoding string | ||||
|             type = value.objCType[1]; | ||||
|         } | ||||
|  | ||||
|         if (type == FLEXTypeEncodingCString) { | ||||
|             self.inputTextView.text = @((const char *)value.pointerValue); | ||||
|         } else if (type == FLEXTypeEncodingSelector) { | ||||
|             self.inputTextView.text = NSStringFromSelector((SEL)value.pointerValue); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     NSString *text = self.inputTextView.text; | ||||
|     // Interpret empty string as nil. We loose the ability to set empty string as a string value, | ||||
|     // but we accept that tradeoff in exchange for not having to type quotes for every string. | ||||
|     if (!text.length) { | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     // Case: C-strings and SELs | ||||
|     if (self.typeEncoding.length <= 2) { | ||||
|         FLEXTypeEncoding type = [self.typeEncoding characterAtIndex:0]; | ||||
|         if (type == FLEXTypeEncodingConst) { | ||||
|             // A crash here would mean an invalid type encoding string | ||||
|             type = [self.typeEncoding characterAtIndex:1]; | ||||
|         } | ||||
|  | ||||
|         if (type == FLEXTypeEncodingCString || type == FLEXTypeEncodingSelector) { | ||||
|             const char *encoding = self.typeEncoding.UTF8String; | ||||
|             SEL selector = NSSelectorFromString(text); | ||||
|             return [NSValue valueWithBytes:&selector objCType:encoding]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Case: NSStrings | ||||
|     return self.inputTextView.text.copy; | ||||
| } | ||||
|  | ||||
| // TODO: Support using object address for strings, as in the object arg view. | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type); | ||||
|     unsigned long len = strlen(type); | ||||
|  | ||||
|     BOOL isConst = type[0] == FLEXTypeEncodingConst; | ||||
|     NSInteger i = isConst ? 1 : 0; | ||||
|  | ||||
|     BOOL typeIsString = strcmp(type, FLEXEncodeClass(NSString)) == 0; | ||||
|     BOOL typeIsCString = len <= 2 && type[i] == FLEXTypeEncodingCString; | ||||
|     BOOL typeIsSEL = len <= 2 && type[i] == FLEXTypeEncodingSelector; | ||||
|     BOOL valueIsString = [value isKindOfClass:[NSString class]]; | ||||
|  | ||||
|     BOOL typeIsPrimitiveString = typeIsSEL || typeIsCString; | ||||
|     BOOL typeIsSupported = typeIsString || typeIsCString || typeIsSEL; | ||||
|  | ||||
|     BOOL valueIsNSValueWithCorrectType = NO; | ||||
|     if ([value isKindOfClass:[NSValue class]]) { | ||||
|         NSValue *v = (id)value; | ||||
|         len = strlen(v.objCType); | ||||
|         if (len == 1) { | ||||
|             FLEXTypeEncoding type = v.objCType[i]; | ||||
|             if (type == FLEXTypeEncodingCString && typeIsCString) { | ||||
|                 valueIsNSValueWithCorrectType = YES; | ||||
|             } else if (type == FLEXTypeEncodingSelector && typeIsSEL) { | ||||
|                 valueIsNSValueWithCorrectType = YES; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!value && typeIsSupported) { | ||||
|         return YES; | ||||
|     } | ||||
|  | ||||
|     if (typeIsString && valueIsString) { | ||||
|         return YES; | ||||
|     } | ||||
|  | ||||
|     // Primitive strings can be input as NSStrings or NSValues | ||||
|     if (typeIsPrimitiveString && (valueIsString || valueIsNSValueWithCorrectType)) { | ||||
|         return YES; | ||||
|     } | ||||
|  | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputStructView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputStructView : FLEXArgumentInputView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,220 @@ | ||||
| // | ||||
| //  FLEXArgumentInputStructView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputStructView.h" | ||||
| #import "FLEXArgumentInputViewFactory.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXTypeEncodingParser.h" | ||||
|  | ||||
| @interface FLEXArgumentInputStructView () | ||||
|  | ||||
| @property (nonatomic) NSArray<FLEXArgumentInputView *> *argumentInputViews; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputStructView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray new]; | ||||
|         NSArray<NSString *> *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.targetSize = FLEXArgumentInputViewSizeSmall; | ||||
|              | ||||
|             if (fieldIndex < customTitles.count) { | ||||
|                 inputView.title = customTitles[fieldIndex]; | ||||
|             } else { | ||||
|                 inputView.title = [NSString stringWithFormat:@"%@ field %lu (%@)", | ||||
|                     structName, (unsigned long)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; | ||||
|              | ||||
|             if (FLEXGetSizeAndAlignment(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[fieldIndex]; | ||||
|                      | ||||
|                     if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) { | ||||
|                         inputView.inputValue = (__bridge id)fieldPointer; | ||||
|                     } else { | ||||
|                         NSValue *boxedField = [FLEXRuntimeUtility valueForPrimitivePointer:fieldPointer objCType:fieldTypeEncoding]; | ||||
|                         inputView.inputValue = boxedField; | ||||
|                     } | ||||
|                 }]; | ||||
|                 free(unboxedValue); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     NSValue *boxedStruct = nil; | ||||
|     const char *structTypeEncoding = self.typeEncoding.UTF8String; | ||||
|     NSUInteger structSize = 0; | ||||
|      | ||||
|     if (FLEXGetSizeAndAlignment(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[fieldIndex]; | ||||
|              | ||||
|             if (fieldTypeEncoding[0] == FLEXTypeEncodingObjcObject || fieldTypeEncoding[0] == FLEXTypeEncodingObjcClass) { | ||||
|                 // 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]; | ||||
|                 } | ||||
|             } | ||||
|         }]; | ||||
|          | ||||
|         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 { | ||||
|     NSParameterAssert(type); | ||||
|     if (type[0] == FLEXTypeEncodingStructBegin) { | ||||
|         return FLEXGetSizeAndAlignment(type, nil, nil); | ||||
|     } | ||||
|  | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| + (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding { | ||||
|     NSArray<NSString *> *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(CGVector)) == 0) { | ||||
|         customTitles = @[@"CGFloat dx", @"CGFloat dy"]; | ||||
|     } 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"]; | ||||
|     } else { | ||||
|         if (@available(iOS 11.0, *)) { | ||||
|             if (strcmp(typeEncoding, @encode(NSDirectionalEdgeInsets)) == 0) { | ||||
|                 customTitles = @[@"CGFloat top", @"CGFloat leading", | ||||
|                                  @"CGFloat bottom", @"CGFloat trailing"]; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return customTitles; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,13 @@ | ||||
| // | ||||
| //  FLEXArgumentInputSwitchView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputSwitchView : FLEXArgumentInputView | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,81 @@ | ||||
| // | ||||
| //  FLEXArgumentInputSwitchView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputSwitchView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputSwitchView () | ||||
|  | ||||
| @property (nonatomic) UISwitch *inputSwitch; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputSwitchView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.inputSwitch = [UISwitch new]; | ||||
|         [self.inputSwitch addTarget:self action:@selector(switchValueDidChange:) forControlEvents:UIControlEventValueChanged]; | ||||
|         [self.inputSwitch sizeToFit]; | ||||
|         [self addSubview:self.inputSwitch]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark Input/Output | ||||
|  | ||||
| - (void)setInputValue:(id)inputValue { | ||||
|     BOOL on = NO; | ||||
|     if ([inputValue isKindOfClass:[NSNumber class]]) { | ||||
|         NSNumber *number = (NSNumber *)inputValue; | ||||
|         on = [number boolValue]; | ||||
|     } else if ([inputValue isKindOfClass:[NSValue class]]) { | ||||
|         NSValue *value = (NSValue *)inputValue; | ||||
|         if (strcmp([value objCType], @encode(BOOL)) == 0) { | ||||
|             [value getValue:&on]; | ||||
|         } | ||||
|     } | ||||
|     self.inputSwitch.on = on; | ||||
| } | ||||
|  | ||||
| - (id)inputValue { | ||||
|     BOOL isOn = [self.inputSwitch isOn]; | ||||
|     NSValue *boxedBool = [NSValue value:&isOn withObjCType:@encode(BOOL)]; | ||||
|     return boxedBool; | ||||
| } | ||||
|  | ||||
| - (void)switchValueDidChange:(id)sender { | ||||
|     [self.delegate argumentInputViewValueDidChange:self]; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Layout and Sizing | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|      | ||||
|     self.inputSwitch.frame = CGRectMake(0, self.topInputFieldVerticalLayoutGuide, self.inputSwitch.frame.size.width, self.inputSwitch.frame.size.height); | ||||
| } | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGSize fitSize = [super sizeThatFits:size]; | ||||
|     fitSize.height += self.inputSwitch.frame.size.height; | ||||
|     return fitSize; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Class Helpers | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     NSParameterAssert(type); | ||||
|     // Only BOOLs. Current value is irrelevant. | ||||
|     return strcmp(type, @encode(BOOL)) == 0; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,18 @@ | ||||
| // | ||||
| //  FLEXArgumentInputTextView.h | ||||
| //  FLEXInjected | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| // | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputTextView : FLEXArgumentInputView <UITextViewDelegate> | ||||
|  | ||||
| // For subclass eyes only | ||||
|  | ||||
| @property (nonatomic, readonly) UITextView *inputTextView; | ||||
| @property (nonatomic) NSString *inputPlaceholderText; | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,155 @@ | ||||
| // | ||||
| //  FLEXArgumentInputTextView.m | ||||
| //  FLEXInjected | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| // | ||||
| // | ||||
|  | ||||
| #import "FLEXColor.h" | ||||
| #import "FLEXArgumentInputTextView.h" | ||||
| #import "FLEXUtility.h" | ||||
|  | ||||
| @interface FLEXArgumentInputTextView () | ||||
|  | ||||
| @property (nonatomic) UITextView *inputTextView; | ||||
| @property (nonatomic) UILabel *placeholderLabel; | ||||
| @property (nonatomic, readonly) NSUInteger numberOfInputLines; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputTextView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithArgumentTypeEncoding:typeEncoding]; | ||||
|     if (self) { | ||||
|         self.inputTextView = [UITextView new]; | ||||
|         self.inputTextView.font = [[self class] inputFont]; | ||||
|         self.inputTextView.backgroundColor = FLEXColor.secondaryGroupedBackgroundColor; | ||||
|         self.inputTextView.layer.cornerRadius = 10.f; | ||||
|         self.inputTextView.contentInset = UIEdgeInsetsMake(0, 5, 0, 0); | ||||
|         self.inputTextView.autocapitalizationType = UITextAutocapitalizationTypeNone; | ||||
|         self.inputTextView.autocorrectionType = UITextAutocorrectionTypeNo; | ||||
|         self.inputTextView.delegate = self; | ||||
|         self.inputTextView.inputAccessoryView = [self createToolBar]; | ||||
|         if (@available(iOS 11, *)) { | ||||
|             self.inputTextView.smartQuotesType = UITextSmartQuotesTypeNo; | ||||
|             [self.inputTextView.layer setValue:@YES forKey:@"continuousCorners"]; | ||||
|         } else { | ||||
|             self.inputTextView.layer.borderWidth = 1.f; | ||||
|             self.inputTextView.layer.borderColor = FLEXColor.borderColor.CGColor; | ||||
|         } | ||||
|  | ||||
|         self.placeholderLabel = [UILabel new]; | ||||
|         self.placeholderLabel.font = self.inputTextView.font; | ||||
|         self.placeholderLabel.textColor = FLEXColor.deemphasizedTextColor; | ||||
|         self.placeholderLabel.numberOfLines = 0; | ||||
|  | ||||
|         [self addSubview:self.inputTextView]; | ||||
|         [self.inputTextView addSubview:self.placeholderLabel]; | ||||
|  | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| #pragma mark - Private | ||||
|  | ||||
| - (UIToolbar *)createToolBar { | ||||
|     UIToolbar *toolBar = [UIToolbar new]; | ||||
|     [toolBar sizeToFit]; | ||||
|     UIBarButtonItem *spaceItem = [[UIBarButtonItem alloc] | ||||
|         initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace | ||||
|         target:nil action:nil | ||||
|     ]; | ||||
|     UIBarButtonItem *pasteItem = [[UIBarButtonItem alloc] | ||||
|         initWithTitle:@"Paste" style:UIBarButtonItemStyleDone | ||||
|         target:self.inputTextView action:@selector(paste:) | ||||
|     ]; | ||||
|     UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] | ||||
|         initWithBarButtonSystemItem:UIBarButtonSystemItemDone | ||||
|         target:self.inputTextView action:@selector(resignFirstResponder) | ||||
|     ]; | ||||
|     toolBar.items = @[spaceItem, pasteItem, doneItem]; | ||||
|     return toolBar; | ||||
| } | ||||
|  | ||||
| - (void)setInputPlaceholderText:(NSString *)placeholder { | ||||
|     self.placeholderLabel.text = placeholder; | ||||
|     if (placeholder.length) { | ||||
|         if (!self.inputTextView.text.length) { | ||||
|             self.placeholderLabel.hidden = NO; | ||||
|         } else { | ||||
|             self.placeholderLabel.hidden = YES; | ||||
|         } | ||||
|     } else { | ||||
|         self.placeholderLabel.hidden = YES; | ||||
|     } | ||||
|  | ||||
|     [self setNeedsLayout]; | ||||
| } | ||||
|  | ||||
| - (NSString *)inputPlaceholderText { | ||||
|     return self.placeholderLabel.text; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Superclass Overrides | ||||
|  | ||||
| - (BOOL)inputViewIsFirstResponder { | ||||
|     return self.inputTextView.isFirstResponder; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Layout and Sizing | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|      | ||||
|     self.inputTextView.frame = CGRectMake(0, self.topInputFieldVerticalLayoutGuide, self.bounds.size.width, [self inputTextViewHeight]); | ||||
|     // Placeholder label is positioned by insetting then origin | ||||
|     // by the content inset then the text container inset | ||||
|     CGSize s = self.inputTextView.frame.size; | ||||
|     self.placeholderLabel.frame = CGRectMake(0, 0, s.width, s.height); | ||||
|     self.placeholderLabel.frame = UIEdgeInsetsInsetRect( | ||||
|         UIEdgeInsetsInsetRect(self.placeholderLabel.frame, self.inputTextView.contentInset), | ||||
|         self.inputTextView.textContainerInset | ||||
|     ); | ||||
| } | ||||
|  | ||||
| - (NSUInteger)numberOfInputLines { | ||||
|     switch (self.targetSize) { | ||||
|         case FLEXArgumentInputViewSizeDefault: | ||||
|             return 2; | ||||
|         case FLEXArgumentInputViewSizeSmall: | ||||
|             return 1; | ||||
|         case FLEXArgumentInputViewSizeLarge: | ||||
|             return 8; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (CGFloat)inputTextViewHeight { | ||||
|     return ceil([[self class] inputFont].lineHeight * self.numberOfInputLines) + 16.0; | ||||
| } | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGSize fitSize = [super sizeThatFits:size]; | ||||
|     fitSize.height += [self inputTextViewHeight]; | ||||
|     return fitSize; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Class Helpers | ||||
|  | ||||
| + (UIFont *)inputFont { | ||||
|     return [UIFont systemFontOfSize:14.0]; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - UITextViewDelegate | ||||
|  | ||||
| - (void)textViewDidChange:(UITextView *)textView { | ||||
|     [self.delegate argumentInputViewValueDidChange:self]; | ||||
|     self.placeholderLabel.hidden = !(self.inputPlaceholderText.length && !textView.text.length); | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,64 @@ | ||||
| // | ||||
| //  FLEXArgumentInputView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/30/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) { | ||||
|     /// 2 lines, medium-sized | ||||
|     FLEXArgumentInputViewSizeDefault = 0, | ||||
|     /// One line | ||||
|     FLEXArgumentInputViewSizeSmall, | ||||
|     /// Several lines | ||||
|     FLEXArgumentInputViewSizeLarge | ||||
| }; | ||||
|  | ||||
| @protocol FLEXArgumentInputViewDelegate; | ||||
|  | ||||
| @interface FLEXArgumentInputView : UIView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding; | ||||
|  | ||||
| /// The name of the field. Optional (can be nil). | ||||
| @property (nonatomic, copy) NSString *title; | ||||
|  | ||||
| /// To populate the filed with an initial value, set this property. | ||||
| /// To reteive the value input by the user, access the property. | ||||
| /// Primitive types and structs should/will be boxed in NSValue containers. | ||||
| /// Concrete subclasses should override both the setter and getter for this property. | ||||
| /// Subclasses can call super.inputValue to access a backing store for the value. | ||||
| @property (nonatomic) id inputValue; | ||||
|  | ||||
| /// Setting this value to large will make some argument input views increase the size of their input field(s). | ||||
| /// Useful to increase the use of space if there is only one input view on screen (i.e. for property and ivar editing). | ||||
| @property (nonatomic) FLEXArgumentInputViewSize targetSize; | ||||
|  | ||||
| /// Users of the input view can get delegate callbacks for incremental changes in user input. | ||||
| @property (nonatomic, weak) id <FLEXArgumentInputViewDelegate> delegate; | ||||
|  | ||||
| // Subclasses can override | ||||
|  | ||||
| /// If the input view has one or more text views, returns YES when one of them is focused. | ||||
| @property (nonatomic, readonly) BOOL inputViewIsFirstResponder; | ||||
|  | ||||
| /// For subclasses to indicate that they can handle editing a field the give type and value. | ||||
| /// Used by FLEXArgumentInputViewFactory to create appropriate input views. | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value; | ||||
|  | ||||
| // For subclass eyes only | ||||
|  | ||||
| @property (nonatomic, readonly) UILabel *titleLabel; | ||||
| @property (nonatomic, readonly) NSString *typeEncoding; | ||||
| @property (nonatomic, readonly) CGFloat topInputFieldVerticalLayoutGuide; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @protocol FLEXArgumentInputViewDelegate <NSObject> | ||||
|  | ||||
| - (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										114
									
								
								Tweaks/FLEX/Editing/ArgumentInputViews/FLEXArgumentInputView.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								Tweaks/FLEX/Editing/ArgumentInputViews/FLEXArgumentInputView.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| // | ||||
| //  FLEXArgumentInputView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/30/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputView.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXColor.h" | ||||
|  | ||||
| @interface FLEXArgumentInputView () | ||||
|  | ||||
| @property (nonatomic) UILabel *titleLabel; | ||||
| @property (nonatomic) NSString *typeEncoding; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXArgumentInputView | ||||
|  | ||||
| - (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding { | ||||
|     self = [super initWithFrame:CGRectZero]; | ||||
|     if (self) { | ||||
|         self.typeEncoding = typeEncoding != NULL ? @(typeEncoding) : nil; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|      | ||||
|     if (self.showsTitle) { | ||||
|         CGSize constrainSize = CGSizeMake(self.bounds.size.width, CGFLOAT_MAX); | ||||
|         CGSize labelSize = [self.titleLabel sizeThatFits:constrainSize]; | ||||
|         self.titleLabel.frame = CGRectMake(0, 0, labelSize.width, labelSize.height); | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setBackgroundColor:(UIColor *)backgroundColor { | ||||
|     [super setBackgroundColor:backgroundColor]; | ||||
|     self.titleLabel.backgroundColor = backgroundColor; | ||||
| } | ||||
|  | ||||
| - (void)setTitle:(NSString *)title { | ||||
|     if (![_title isEqual:title]) { | ||||
|         _title = title; | ||||
|         self.titleLabel.text = title; | ||||
|         [self setNeedsLayout]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (UILabel *)titleLabel { | ||||
|     if (!_titleLabel) { | ||||
|         _titleLabel = [UILabel new]; | ||||
|         _titleLabel.font = [[self class] titleFont]; | ||||
|         _titleLabel.textColor = FLEXColor.primaryTextColor; | ||||
|         _titleLabel.numberOfLines = 0; | ||||
|         [self addSubview:_titleLabel]; | ||||
|     } | ||||
|     return _titleLabel; | ||||
| } | ||||
|  | ||||
| - (BOOL)showsTitle { | ||||
|     return self.title.length > 0; | ||||
| } | ||||
|  | ||||
| - (CGFloat)topInputFieldVerticalLayoutGuide { | ||||
|     CGFloat verticalLayoutGuide = 0; | ||||
|     if (self.showsTitle) { | ||||
|         CGFloat titleHeight = [self.titleLabel sizeThatFits:self.bounds.size].height; | ||||
|         verticalLayoutGuide = titleHeight + [[self class] titleBottomPadding]; | ||||
|     } | ||||
|     return verticalLayoutGuide; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Subclasses Can Override | ||||
|  | ||||
| - (BOOL)inputViewIsFirstResponder { | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| + (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value { | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Class Helpers | ||||
|  | ||||
| + (UIFont *)titleFont { | ||||
|     return [UIFont systemFontOfSize:12.0]; | ||||
| } | ||||
|  | ||||
| + (CGFloat)titleBottomPadding { | ||||
|     return 4.0; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Sizing | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGFloat height = 0; | ||||
|      | ||||
|     if (self.title.length > 0) { | ||||
|         CGSize constrainSize = CGSizeMake(size.width, CGFLOAT_MAX); | ||||
|         height += ceil([self.titleLabel sizeThatFits:constrainSize].height); | ||||
|         height += [[self class] titleBottomPadding]; | ||||
|     } | ||||
|      | ||||
|     return CGSizeMake(size.width, height); | ||||
| } | ||||
|  | ||||
| @end | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn