mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 12:23:58 -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 | ||||
							
								
								
									
										24
									
								
								Tweaks/FLEX/Editing/FLEXArgumentInputViewFactory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Tweaks/FLEX/Editing/FLEXArgumentInputViewFactory.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // | ||||
| //  FLEXArgumentInputViewFactory.h | ||||
| //  FLEXInjected | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| // | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
| #import "FLEXArgumentInputSwitchView.h" | ||||
|  | ||||
| @interface FLEXArgumentInputViewFactory : NSObject | ||||
|  | ||||
| /// Forwards to argumentInputViewForTypeEncoding:currentValue: with a nil currentValue. | ||||
| + (FLEXArgumentInputView *)argumentInputViewForTypeEncoding:(const char *)typeEncoding; | ||||
|  | ||||
| /// The main factory method for making argument input view subclasses that are the best fit for the type. | ||||
| + (FLEXArgumentInputView *)argumentInputViewForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue; | ||||
|  | ||||
| /// A way to check if we should try editing a filed given its type encoding and value. | ||||
| /// Useful when deciding whether to edit or explore a property, ivar, or NSUserDefaults value. | ||||
| + (BOOL)canEditFieldWithTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										70
									
								
								Tweaks/FLEX/Editing/FLEXArgumentInputViewFactory.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								Tweaks/FLEX/Editing/FLEXArgumentInputViewFactory.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| // | ||||
| //  FLEXArgumentInputViewFactory.m | ||||
| //  FLEXInjected | ||||
| // | ||||
| //  Created by Ryan Olson on 6/15/14. | ||||
| // | ||||
| // | ||||
|  | ||||
| #import "FLEXArgumentInputViewFactory.h" | ||||
| #import "FLEXArgumentInputView.h" | ||||
| #import "FLEXArgumentInputObjectView.h" | ||||
| #import "FLEXArgumentInputNumberView.h" | ||||
| #import "FLEXArgumentInputSwitchView.h" | ||||
| #import "FLEXArgumentInputStructView.h" | ||||
| #import "FLEXArgumentInputNotSupportedView.h" | ||||
| #import "FLEXArgumentInputStringView.h" | ||||
| #import "FLEXArgumentInputFontView.h" | ||||
| #import "FLEXArgumentInputColorView.h" | ||||
| #import "FLEXArgumentInputDateView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| @implementation FLEXArgumentInputViewFactory | ||||
|  | ||||
| + (FLEXArgumentInputView *)argumentInputViewForTypeEncoding:(const char *)typeEncoding { | ||||
|     return [self argumentInputViewForTypeEncoding:typeEncoding currentValue:nil]; | ||||
| } | ||||
|  | ||||
| + (FLEXArgumentInputView *)argumentInputViewForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue { | ||||
|     Class subclass = [self argumentInputViewSubclassForTypeEncoding:typeEncoding currentValue:currentValue]; | ||||
|     if (!subclass) { | ||||
|         // Fall back to a FLEXArgumentInputNotSupportedView if we can't find a subclass that fits the type encoding. | ||||
|         // The unsupported view shows "nil" and does not allow user input. | ||||
|         subclass = [FLEXArgumentInputNotSupportedView class]; | ||||
|     } | ||||
|     // Remove the field name if there is any (e.g. \"width\"d -> d) | ||||
|     const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:typeEncoding]; | ||||
|     return [[subclass alloc] initWithArgumentTypeEncoding:typeEncoding + fieldNameOffset]; | ||||
| } | ||||
|  | ||||
| + (Class)argumentInputViewSubclassForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue { | ||||
|     // Remove the field name if there is any (e.g. \"width\"d -> d) | ||||
|     const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:typeEncoding]; | ||||
|     Class argumentInputViewSubclass = nil; | ||||
|     NSArray<Class> *inputViewClasses = @[[FLEXArgumentInputColorView class], | ||||
|                                          [FLEXArgumentInputFontView class], | ||||
|                                          [FLEXArgumentInputStringView class], | ||||
|                                          [FLEXArgumentInputStructView class], | ||||
|                                          [FLEXArgumentInputSwitchView class], | ||||
|                                          [FLEXArgumentInputDateView class], | ||||
|                                          [FLEXArgumentInputNumberView class], | ||||
|                                          [FLEXArgumentInputObjectView class]]; | ||||
|  | ||||
|     // 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. | ||||
|     for (Class inputViewClass in inputViewClasses) { | ||||
|         if ([inputViewClass supportsObjCType:typeEncoding + fieldNameOffset withCurrentValue:currentValue]) { | ||||
|             argumentInputViewSubclass = inputViewClass; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return argumentInputViewSubclass; | ||||
| } | ||||
|  | ||||
| + (BOOL)canEditFieldWithTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue { | ||||
|     return [self argumentInputViewSubclassForTypeEncoding:typeEncoding currentValue:currentValue] != nil; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										21
									
								
								Tweaks/FLEX/Editing/FLEXDefaultEditorViewController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Tweaks/FLEX/Editing/FLEXDefaultEditorViewController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| // | ||||
| //  FLEXDefaultEditorViewController.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/23/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXFieldEditorViewController.h" | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| @interface FLEXDefaultEditorViewController : FLEXVariableEditorViewController | ||||
|  | ||||
| + (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit; | ||||
|  | ||||
| + (BOOL)canEditDefaultWithValue:(nullable id)currentValue; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										80
									
								
								Tweaks/FLEX/Editing/FLEXDefaultEditorViewController.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								Tweaks/FLEX/Editing/FLEXDefaultEditorViewController.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| // | ||||
| //  FLEXDefaultEditorViewController.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/23/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXDefaultEditorViewController.h" | ||||
| #import "FLEXFieldEditorView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXArgumentInputView.h" | ||||
| #import "FLEXArgumentInputViewFactory.h" | ||||
|  | ||||
| @interface FLEXDefaultEditorViewController () | ||||
|  | ||||
| @property (nonatomic, readonly) NSUserDefaults *defaults; | ||||
| @property (nonatomic, readonly) NSString *key; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXDefaultEditorViewController | ||||
|  | ||||
| + (instancetype)target:(NSUserDefaults *)defaults key:(NSString *)key commitHandler:(void(^_Nullable)(void))onCommit { | ||||
|     FLEXDefaultEditorViewController *editor = [self target:defaults data:key commitHandler:onCommit]; | ||||
|     editor.title = @"Edit Default"; | ||||
|     return editor; | ||||
| } | ||||
|  | ||||
| - (NSUserDefaults *)defaults { | ||||
|     return [_target isKindOfClass:[NSUserDefaults class]] ? _target : nil; | ||||
| } | ||||
|  | ||||
| - (NSString *)key { | ||||
|     return _data; | ||||
| } | ||||
|  | ||||
| - (void)viewDidLoad { | ||||
|     [super viewDidLoad]; | ||||
|      | ||||
|     self.fieldEditorView.fieldDescription = self.key; | ||||
|  | ||||
|     id currentValue = [self.defaults objectForKey:self.key]; | ||||
|     FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory | ||||
|         argumentInputViewForTypeEncoding:FLEXEncodeObject(currentValue) | ||||
|         currentValue:currentValue | ||||
|     ]; | ||||
|     inputView.backgroundColor = self.view.backgroundColor; | ||||
|     inputView.inputValue = currentValue; | ||||
|     self.fieldEditorView.argumentInputViews = @[inputView]; | ||||
| } | ||||
|  | ||||
| - (void)actionButtonPressed:(id)sender { | ||||
|     id value = self.firstInputView.inputValue; | ||||
|     if (value) { | ||||
|         [self.defaults setObject:value forKey:self.key]; | ||||
|     } else { | ||||
|         [self.defaults removeObjectForKey:self.key]; | ||||
|     } | ||||
|     [self.defaults synchronize]; | ||||
|      | ||||
|     // Dismiss keyboard and handle committed changes | ||||
|     [super actionButtonPressed:sender]; | ||||
|      | ||||
|     // Go back after setting, but not for switches. | ||||
|     if (sender) { | ||||
|         [self.navigationController popViewControllerAnimated:YES]; | ||||
|     } else { | ||||
|         self.firstInputView.inputValue = [self.defaults objectForKey:self.key]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| + (BOOL)canEditDefaultWithValue:(id)currentValue { | ||||
|     return [FLEXArgumentInputViewFactory | ||||
|         canEditFieldWithTypeEncoding:FLEXEncodeObject(currentValue) | ||||
|         currentValue:currentValue | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										20
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorView.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorView.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| // | ||||
| //  FLEXFieldEditorView.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| @class FLEXArgumentInputView; | ||||
|  | ||||
| @interface FLEXFieldEditorView : UIView | ||||
|  | ||||
| @property (nonatomic, copy) NSString *targetDescription; | ||||
| @property (nonatomic, copy) NSString *fieldDescription; | ||||
|  | ||||
| @property (nonatomic, copy) NSArray<FLEXArgumentInputView *> *argumentInputViews; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										172
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorView.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorView.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | ||||
| // | ||||
| //  FLEXFieldEditorView.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXFieldEditorView.h" | ||||
| #import "FLEXArgumentInputView.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXColor.h" | ||||
|  | ||||
| @interface FLEXFieldEditorView () | ||||
|  | ||||
| @property (nonatomic) UILabel *targetDescriptionLabel; | ||||
| @property (nonatomic) UIView *targetDescriptionDivider; | ||||
| @property (nonatomic) UILabel *fieldDescriptionLabel; | ||||
| @property (nonatomic) UIView *fieldDescriptionDivider; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXFieldEditorView | ||||
|  | ||||
| - (id)initWithFrame:(CGRect)frame { | ||||
|     self = [super initWithFrame:frame]; | ||||
|     if (self) { | ||||
|         self.targetDescriptionLabel = [UILabel new]; | ||||
|         self.targetDescriptionLabel.numberOfLines = 0; | ||||
|         self.targetDescriptionLabel.font = [[self class] labelFont]; | ||||
|         [self addSubview:self.targetDescriptionLabel]; | ||||
|          | ||||
|         self.targetDescriptionDivider = [[self class] dividerView]; | ||||
|         [self addSubview:self.targetDescriptionDivider]; | ||||
|          | ||||
|         self.fieldDescriptionLabel = [UILabel new]; | ||||
|         self.fieldDescriptionLabel.numberOfLines = 0; | ||||
|         self.fieldDescriptionLabel.font = [[self class] labelFont]; | ||||
|         [self addSubview:self.fieldDescriptionLabel]; | ||||
|          | ||||
|         self.fieldDescriptionDivider = [[self class] dividerView]; | ||||
|         [self addSubview:self.fieldDescriptionDivider]; | ||||
|     } | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)layoutSubviews { | ||||
|     [super layoutSubviews]; | ||||
|      | ||||
|     CGFloat horizontalPadding = [[self class] horizontalPadding]; | ||||
|     CGFloat verticalPadding = [[self class] verticalPadding]; | ||||
|     CGFloat dividerLineHeight = [[self class] dividerLineHeight]; | ||||
|      | ||||
|     CGFloat originY = verticalPadding; | ||||
|     CGFloat originX = horizontalPadding; | ||||
|     CGFloat contentWidth = self.bounds.size.width - 2.0 * horizontalPadding; | ||||
|     CGSize constrainSize = CGSizeMake(contentWidth, CGFLOAT_MAX); | ||||
|      | ||||
|     CGSize instanceDescriptionSize = [self.targetDescriptionLabel sizeThatFits:constrainSize]; | ||||
|     self.targetDescriptionLabel.frame = CGRectMake(originX, originY, instanceDescriptionSize.width, instanceDescriptionSize.height); | ||||
|     originY = CGRectGetMaxY(self.targetDescriptionLabel.frame) + verticalPadding; | ||||
|      | ||||
|     self.targetDescriptionDivider.frame = CGRectMake(originX, originY, contentWidth, dividerLineHeight); | ||||
|     originY = CGRectGetMaxY(self.targetDescriptionDivider.frame) + verticalPadding; | ||||
|      | ||||
|     CGSize fieldDescriptionSize = [self.fieldDescriptionLabel sizeThatFits:constrainSize]; | ||||
|     self.fieldDescriptionLabel.frame = CGRectMake(originX, originY, fieldDescriptionSize.width, fieldDescriptionSize.height); | ||||
|     originY = CGRectGetMaxY(self.fieldDescriptionLabel.frame) + verticalPadding; | ||||
|      | ||||
|     self.fieldDescriptionDivider.frame = CGRectMake(originX, originY, contentWidth, dividerLineHeight); | ||||
|     originY = CGRectGetMaxY(self.fieldDescriptionDivider.frame) + verticalPadding; | ||||
|  | ||||
|     for (UIView *argumentInputView in self.argumentInputViews) { | ||||
|         CGSize inputViewSize = [argumentInputView sizeThatFits:constrainSize]; | ||||
|         argumentInputView.frame = CGRectMake(originX, originY, inputViewSize.width, inputViewSize.height); | ||||
|         originY = CGRectGetMaxY(argumentInputView.frame) + verticalPadding; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setBackgroundColor:(UIColor *)backgroundColor { | ||||
|     [super setBackgroundColor:backgroundColor]; | ||||
|     self.targetDescriptionLabel.backgroundColor = backgroundColor; | ||||
|     self.fieldDescriptionLabel.backgroundColor = backgroundColor; | ||||
| } | ||||
|  | ||||
| - (void)setTargetDescription:(NSString *)targetDescription { | ||||
|     if (![_targetDescription isEqual:targetDescription]) { | ||||
|         _targetDescription = targetDescription; | ||||
|         self.targetDescriptionLabel.text = targetDescription; | ||||
|         [self setNeedsLayout]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setFieldDescription:(NSString *)fieldDescription { | ||||
|     if (![_fieldDescription isEqual:fieldDescription]) { | ||||
|         _fieldDescription = fieldDescription; | ||||
|         self.fieldDescriptionLabel.text = fieldDescription; | ||||
|         [self setNeedsLayout]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)setArgumentInputViews:(NSArray<FLEXArgumentInputView *> *)argumentInputViews { | ||||
|     if (![_argumentInputViews isEqual:argumentInputViews]) { | ||||
|          | ||||
|         for (FLEXArgumentInputView *inputView in _argumentInputViews) { | ||||
|             [inputView removeFromSuperview]; | ||||
|         } | ||||
|          | ||||
|         _argumentInputViews = argumentInputViews; | ||||
|          | ||||
|         for (FLEXArgumentInputView *newInputView in argumentInputViews) { | ||||
|             [self addSubview:newInputView]; | ||||
|         } | ||||
|          | ||||
|         [self setNeedsLayout]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| + (UIView *)dividerView { | ||||
|     UIView *dividerView = [UIView new]; | ||||
|     dividerView.backgroundColor = [self dividerColor]; | ||||
|     return dividerView; | ||||
| } | ||||
|  | ||||
| + (UIColor *)dividerColor { | ||||
|     return FLEXColor.tertiaryBackgroundColor; | ||||
| } | ||||
|  | ||||
| + (CGFloat)horizontalPadding { | ||||
|     return 10.0; | ||||
| } | ||||
|  | ||||
| + (CGFloat)verticalPadding { | ||||
|     return 20.0; | ||||
| } | ||||
|  | ||||
| + (UIFont *)labelFont { | ||||
|     return [UIFont systemFontOfSize:14.0]; | ||||
| } | ||||
|  | ||||
| + (CGFloat)dividerLineHeight { | ||||
|     return 1.0; | ||||
| } | ||||
|  | ||||
| - (CGSize)sizeThatFits:(CGSize)size { | ||||
|     CGFloat horizontalPadding = [[self class] horizontalPadding]; | ||||
|     CGFloat verticalPadding = [[self class] verticalPadding]; | ||||
|     CGFloat dividerLineHeight = [[self class] dividerLineHeight]; | ||||
|      | ||||
|     CGFloat height = 0; | ||||
|     CGFloat availableWidth = size.width - 2.0 * horizontalPadding; | ||||
|     CGSize constrainSize = CGSizeMake(availableWidth, CGFLOAT_MAX); | ||||
|      | ||||
|     height += verticalPadding; | ||||
|     height += ceil([self.targetDescriptionLabel sizeThatFits:constrainSize].height); | ||||
|     height += verticalPadding; | ||||
|     height += dividerLineHeight; | ||||
|     height += verticalPadding; | ||||
|     height += ceil([self.fieldDescriptionLabel sizeThatFits:constrainSize].height); | ||||
|     height += verticalPadding; | ||||
|     height += dividerLineHeight; | ||||
|     height += verticalPadding; | ||||
|      | ||||
|     for (FLEXArgumentInputView *inputView in self.argumentInputViews) { | ||||
|         height += [inputView sizeThatFits:constrainSize].height; | ||||
|         height += verticalPadding; | ||||
|     } | ||||
|      | ||||
|     return CGSizeMake(size.width, height); | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										29
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorViewController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorViewController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| // | ||||
| //  FLEXFieldEditorViewController.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 11/22/18. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXVariableEditorViewController.h" | ||||
| #import "FLEXProperty.h" | ||||
| #import "FLEXIvar.h" | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| @interface FLEXFieldEditorViewController : FLEXVariableEditorViewController | ||||
|  | ||||
| /// @return nil if the property is readonly or if the type is unsupported | ||||
| + (nullable instancetype)target:(id)target property:(FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit; | ||||
| /// @return nil if the ivar type is unsupported | ||||
| + (nullable instancetype)target:(id)target ivar:(FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit; | ||||
|  | ||||
| /// Subclasses can change the button title via the \c title property | ||||
| @property (nonatomic, readonly) UIBarButtonItem *getterButton; | ||||
|  | ||||
| - (void)getterButtonPressed:(id)sender; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										149
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorViewController.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								Tweaks/FLEX/Editing/FLEXFieldEditorViewController.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| // | ||||
| //  FLEXFieldEditorViewController.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 11/22/18. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXFieldEditorViewController.h" | ||||
| #import "FLEXFieldEditorView.h" | ||||
| #import "FLEXArgumentInputViewFactory.h" | ||||
| #import "FLEXPropertyAttributes.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXColor.h" | ||||
| #import "UIBarButtonItem+FLEX.h" | ||||
|  | ||||
| @interface FLEXFieldEditorViewController () <FLEXArgumentInputViewDelegate> | ||||
|  | ||||
| @property (nonatomic) FLEXProperty *property; | ||||
| @property (nonatomic) FLEXIvar *ivar; | ||||
|  | ||||
| @property (nonatomic, readonly) id currentValue; | ||||
| @property (nonatomic, readonly) const FLEXTypeEncoding *typeEncoding; | ||||
| @property (nonatomic, readonly) NSString *fieldDescription; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXFieldEditorViewController | ||||
|  | ||||
| #pragma mark - Initialization | ||||
|  | ||||
| + (instancetype)target:(id)target property:(nonnull FLEXProperty *)property commitHandler:(void(^_Nullable)(void))onCommit { | ||||
|     FLEXFieldEditorViewController *editor = [self target:target data:property commitHandler:onCommit]; | ||||
|     editor.title = [@"Property: " stringByAppendingString:property.name]; | ||||
|     editor.property = property; | ||||
|     return editor; | ||||
| } | ||||
|  | ||||
| + (instancetype)target:(id)target ivar:(nonnull FLEXIvar *)ivar commitHandler:(void(^_Nullable)(void))onCommit { | ||||
|     FLEXFieldEditorViewController *editor = [self target:target data:ivar commitHandler:onCommit]; | ||||
|     editor.title = [@"Ivar: " stringByAppendingString:ivar.name]; | ||||
|     editor.ivar = ivar; | ||||
|     return editor; | ||||
| } | ||||
|  | ||||
| #pragma mark - Overrides | ||||
|  | ||||
| - (void)viewDidLoad { | ||||
|     [super viewDidLoad]; | ||||
|  | ||||
|     self.view.backgroundColor = FLEXColor.groupedBackgroundColor; | ||||
|  | ||||
|     // Create getter button | ||||
|     _getterButton = [[UIBarButtonItem alloc] | ||||
|         initWithTitle:@"Get" | ||||
|         style:UIBarButtonItemStyleDone | ||||
|         target:self | ||||
|         action:@selector(getterButtonPressed:) | ||||
|     ]; | ||||
|     self.toolbarItems = @[ | ||||
|         UIBarButtonItem.flex_flexibleSpace, self.getterButton, self.actionButton | ||||
|     ]; | ||||
|  | ||||
|     // Configure input view | ||||
|     self.fieldEditorView.fieldDescription = self.fieldDescription; | ||||
|     FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:self.typeEncoding]; | ||||
|     inputView.inputValue = self.currentValue; | ||||
|     inputView.delegate = self; | ||||
|     self.fieldEditorView.argumentInputViews = @[inputView]; | ||||
|  | ||||
|     // Don't show a "set" button for switches; we mutate when the switch is flipped | ||||
|     if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) { | ||||
|         self.actionButton.enabled = NO; | ||||
|         self.actionButton.title = @"Flip the switch to call the setter"; | ||||
|         // Put getter button before setter button  | ||||
|         self.toolbarItems = @[ | ||||
|             UIBarButtonItem.flex_flexibleSpace, self.actionButton, self.getterButton | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)actionButtonPressed:(id)sender { | ||||
|     if (self.property) { | ||||
|         id userInputObject = self.firstInputView.inputValue; | ||||
|         NSArray *arguments = userInputObject ? @[userInputObject] : nil; | ||||
|         SEL setterSelector = self.property.likelySetter; | ||||
|         NSError *error = nil; | ||||
|         [FLEXRuntimeUtility performSelector:setterSelector onObject:self.target withArguments:arguments error:&error]; | ||||
|         if (error) { | ||||
|             [FLEXAlert showAlert:@"Property Setter Failed" message:error.localizedDescription from:self]; | ||||
|             sender = nil; // Don't pop back | ||||
|         } | ||||
|     } else { | ||||
|         // TODO: check mutability and use mutableCopy if necessary; | ||||
|         // this currently could and would assign NSArray to NSMutableArray | ||||
|         [self.ivar setValue:self.firstInputView.inputValue onObject:self.target]; | ||||
|     } | ||||
|      | ||||
|     // Dismiss keyboard and handle committed changes | ||||
|     [super actionButtonPressed:sender]; | ||||
|  | ||||
|     // Go back after setting, but not for switches. | ||||
|     if (sender) { | ||||
|         [self.navigationController popViewControllerAnimated:YES]; | ||||
|     } else { | ||||
|         self.firstInputView.inputValue = self.currentValue; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)getterButtonPressed:(id)sender { | ||||
|     [self.fieldEditorView endEditing:YES]; | ||||
|  | ||||
|     [self exploreObjectOrPopViewController:self.currentValue]; | ||||
| } | ||||
|  | ||||
| - (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView { | ||||
|     if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) { | ||||
|         [self actionButtonPressed:nil]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #pragma mark - Private | ||||
|  | ||||
| - (id)currentValue { | ||||
|     if (self.property) { | ||||
|         return [self.property getValue:self.target]; | ||||
|     } else { | ||||
|         return [self.ivar getValue:self.target]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (const FLEXTypeEncoding *)typeEncoding { | ||||
|     if (self.property) { | ||||
|         return self.property.attributes.typeEncoding.UTF8String; | ||||
|     } else { | ||||
|         return self.ivar.typeEncoding.UTF8String; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (NSString *)fieldDescription { | ||||
|     if (self.property) { | ||||
|         return self.property.fullDescription; | ||||
|     } else { | ||||
|         return self.ivar.description; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										16
									
								
								Tweaks/FLEX/Editing/FLEXMethodCallingViewController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Tweaks/FLEX/Editing/FLEXMethodCallingViewController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| // | ||||
| //  FLEXMethodCallingViewController.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/23/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXVariableEditorViewController.h" | ||||
| #import "FLEXMethod.h" | ||||
|  | ||||
| @interface FLEXMethodCallingViewController : FLEXVariableEditorViewController | ||||
|  | ||||
| + (instancetype)target:(id)target method:(FLEXMethod *)method; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										110
									
								
								Tweaks/FLEX/Editing/FLEXMethodCallingViewController.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								Tweaks/FLEX/Editing/FLEXMethodCallingViewController.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| // | ||||
| //  FLEXMethodCallingViewController.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/23/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXMethodCallingViewController.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXFieldEditorView.h" | ||||
| #import "FLEXObjectExplorerFactory.h" | ||||
| #import "FLEXObjectExplorerViewController.h" | ||||
| #import "FLEXArgumentInputView.h" | ||||
| #import "FLEXArgumentInputViewFactory.h" | ||||
| #import "FLEXUtility.h" | ||||
|  | ||||
| @interface FLEXMethodCallingViewController () | ||||
| @property (nonatomic, readonly) FLEXMethod *method; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXMethodCallingViewController | ||||
|  | ||||
| + (instancetype)target:(id)target method:(FLEXMethod *)method { | ||||
|     return [[self alloc] initWithTarget:target method:method]; | ||||
| } | ||||
|  | ||||
| - (id)initWithTarget:(id)target method:(FLEXMethod *)method { | ||||
|     NSParameterAssert(method.isInstanceMethod == !object_isClass(target)); | ||||
|  | ||||
|     self = [super initWithTarget:target data:method commitHandler:nil]; | ||||
|     if (self) { | ||||
|         self.title = method.isInstanceMethod ? @"Method: " : @"Class Method: "; | ||||
|         self.title = [self.title stringByAppendingString:method.selectorString]; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)viewDidLoad { | ||||
|     [super viewDidLoad]; | ||||
|  | ||||
|     self.actionButton.title = @"Call"; | ||||
|  | ||||
|     // Configure field editor view | ||||
|     self.fieldEditorView.argumentInputViews = [self argumentInputViews]; | ||||
|     self.fieldEditorView.fieldDescription = [NSString stringWithFormat: | ||||
|         @"Signature:\n%@\n\nReturn Type:\n%s", | ||||
|         self.method.description, (char *)self.method.returnType | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| - (NSArray<FLEXArgumentInputView *> *)argumentInputViews { | ||||
|     Method method = self.method.objc_method; | ||||
|     NSArray *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:method]; | ||||
|     NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray new]; | ||||
|     unsigned int argumentIndex = kFLEXNumberOfImplicitArgs; | ||||
|  | ||||
|     for (NSString *methodComponent in methodComponents) { | ||||
|         char *argumentTypeEncoding = method_copyArgumentType(method, argumentIndex); | ||||
|         FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:argumentTypeEncoding]; | ||||
|         free(argumentTypeEncoding); | ||||
|  | ||||
|         inputView.backgroundColor = self.view.backgroundColor; | ||||
|         inputView.title = methodComponent; | ||||
|         [argumentInputViews addObject:inputView]; | ||||
|         argumentIndex++; | ||||
|     } | ||||
|  | ||||
|     return argumentInputViews; | ||||
| } | ||||
|  | ||||
| - (void)actionButtonPressed:(id)sender { | ||||
|     // Gather arguments | ||||
|     NSMutableArray *arguments = [NSMutableArray new]; | ||||
|     for (FLEXArgumentInputView *inputView in self.fieldEditorView.argumentInputViews) { | ||||
|         // Use NSNull as a nil placeholder; it will be interpreted as nil | ||||
|         [arguments addObject:inputView.inputValue ?: NSNull.null]; | ||||
|     } | ||||
|  | ||||
|     // Call method | ||||
|     NSError *error = nil; | ||||
|     id returnValue = [FLEXRuntimeUtility | ||||
|         performSelector:self.method.selector | ||||
|         onObject:self.target | ||||
|         withArguments:arguments | ||||
|         error:&error | ||||
|     ]; | ||||
|      | ||||
|     // Dismiss keyboard and handle committed changes | ||||
|     [super actionButtonPressed:sender]; | ||||
|  | ||||
|     // Display return value or error | ||||
|     if (error) { | ||||
|         [FLEXAlert showAlert:@"Method Call Failed" message:error.localizedDescription from:self]; | ||||
|     } else if (returnValue) { | ||||
|         // For non-nil (or void) return types, push an explorer view controller to display the returned object | ||||
|         returnValue = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:returnValue type:self.method.returnType]; | ||||
|         FLEXObjectExplorerViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:returnValue]; | ||||
|         [self.navigationController pushViewController:explorer animated:YES]; | ||||
|     } else { | ||||
|         [self exploreObjectOrPopViewController:returnValue]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (FLEXMethod *)method { | ||||
|     return _data; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										55
									
								
								Tweaks/FLEX/Editing/FLEXVariableEditorViewController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								Tweaks/FLEX/Editing/FLEXVariableEditorViewController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| // | ||||
| //  FLEXVariableEditorViewController.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
|  | ||||
| @class FLEXFieldEditorView; | ||||
| @class FLEXArgumentInputView; | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| /// An abstract screen for editing or configuring one or more variables. | ||||
| /// "Target" is the target of the edit operation, and "data" is the data | ||||
| /// you want to mutate or pass to the target when the action is performed. | ||||
| /// The action may be something like calling a method, setting an ivar, etc. | ||||
| @interface FLEXVariableEditorViewController : UIViewController { | ||||
|     @protected | ||||
|     id _target; | ||||
|     _Nullable id _data; | ||||
|     void (^_Nullable _commitHandler)(void); | ||||
| } | ||||
|  | ||||
| /// @param target The target of the operation | ||||
| /// @param data The data associated with the operation | ||||
| /// @param onCommit An action to perform when the data changes  | ||||
| + (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit; | ||||
| /// @param target The target of the operation | ||||
| /// @param data The data associated with the operation | ||||
| /// @param onCommit An action to perform when the data changes  | ||||
| - (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit; | ||||
|  | ||||
| @property (nonatomic, readonly) id target; | ||||
|  | ||||
| /// Convenience accessor since many subclasses only use one input view | ||||
| @property (nonatomic, readonly, nullable) FLEXArgumentInputView *firstInputView; | ||||
|  | ||||
| @property (nonatomic, readonly) FLEXFieldEditorView *fieldEditorView; | ||||
| /// Subclasses can change the button title via the button's \c title property | ||||
| @property (nonatomic, readonly) UIBarButtonItem *actionButton; | ||||
|  | ||||
| /// Subclasses should override to provide "set" functionality. | ||||
| /// The commit handler--if present--is called here. | ||||
| - (void)actionButtonPressed:(nullable id)sender; | ||||
|  | ||||
| /// Pushes an explorer view controller for the given object | ||||
| /// or pops the current view controller. | ||||
| - (void)exploreObjectOrPopViewController:(nullable id)objectOrNil; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										141
									
								
								Tweaks/FLEX/Editing/FLEXVariableEditorViewController.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								Tweaks/FLEX/Editing/FLEXVariableEditorViewController.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| // | ||||
| //  FLEXVariableEditorViewController.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 5/16/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXColor.h" | ||||
| #import "FLEXVariableEditorViewController.h" | ||||
| #import "FLEXFieldEditorView.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXUtility.h" | ||||
| #import "FLEXObjectExplorerFactory.h" | ||||
| #import "FLEXArgumentInputView.h" | ||||
| #import "FLEXArgumentInputViewFactory.h" | ||||
| #import "FLEXObjectExplorerViewController.h" | ||||
| #import "UIBarButtonItem+FLEX.h" | ||||
|  | ||||
| @interface FLEXVariableEditorViewController () <UIScrollViewDelegate> | ||||
| @property (nonatomic) UIScrollView *scrollView; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXVariableEditorViewController | ||||
|  | ||||
| #pragma mark - Initialization | ||||
|  | ||||
| + (instancetype)target:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit { | ||||
|     return [[self alloc] initWithTarget:target data:data commitHandler:onCommit]; | ||||
| } | ||||
|  | ||||
| - (id)initWithTarget:(id)target data:(nullable id)data commitHandler:(void(^_Nullable)(void))onCommit { | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _target = target; | ||||
|         _data = data; | ||||
|         _commitHandler = onCommit; | ||||
|         [NSNotificationCenter.defaultCenter | ||||
|             addObserver:self selector:@selector(keyboardDidShow:) | ||||
|             name:UIKeyboardDidShowNotification object:nil | ||||
|         ]; | ||||
|         [NSNotificationCenter.defaultCenter | ||||
|             addObserver:self selector:@selector(keyboardWillHide:) | ||||
|             name:UIKeyboardWillHideNotification object:nil | ||||
|         ]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (void)dealloc { | ||||
|     [NSNotificationCenter.defaultCenter removeObserver:self]; | ||||
| } | ||||
|  | ||||
| #pragma mark - UIViewController methods | ||||
|  | ||||
| - (void)keyboardDidShow:(NSNotification *)notification { | ||||
|     CGRect keyboardRectInWindow = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; | ||||
|     CGSize keyboardSize = [self.view convertRect:keyboardRectInWindow fromView:nil].size; | ||||
|     UIEdgeInsets scrollInsets = self.scrollView.contentInset; | ||||
|     scrollInsets.bottom = keyboardSize.height; | ||||
|     self.scrollView.contentInset = scrollInsets; | ||||
|     self.scrollView.scrollIndicatorInsets = scrollInsets; | ||||
|      | ||||
|     // Find the active input view and scroll to make sure it's visible. | ||||
|     for (FLEXArgumentInputView *argumentInputView in self.fieldEditorView.argumentInputViews) { | ||||
|         if (argumentInputView.inputViewIsFirstResponder) { | ||||
|             CGRect scrollToVisibleRect = [self.scrollView convertRect:argumentInputView.bounds fromView:argumentInputView]; | ||||
|             [self.scrollView scrollRectToVisible:scrollToVisibleRect animated:YES]; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)keyboardWillHide:(NSNotification *)notification { | ||||
|     UIEdgeInsets scrollInsets = self.scrollView.contentInset; | ||||
|     scrollInsets.bottom = 0.0; | ||||
|     self.scrollView.contentInset = scrollInsets; | ||||
|     self.scrollView.scrollIndicatorInsets = scrollInsets; | ||||
| } | ||||
|  | ||||
| - (void)viewDidLoad { | ||||
|     [super viewDidLoad]; | ||||
|      | ||||
|     self.view.backgroundColor = FLEXColor.scrollViewBackgroundColor; | ||||
|      | ||||
|     self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; | ||||
|     self.scrollView.backgroundColor = self.view.backgroundColor; | ||||
|     self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | ||||
|     self.scrollView.delegate = self; | ||||
|     [self.view addSubview:self.scrollView]; | ||||
|      | ||||
|     _fieldEditorView = [FLEXFieldEditorView new]; | ||||
|     self.fieldEditorView.targetDescription = [NSString stringWithFormat:@"%@ %p", [self.target class], self.target]; | ||||
|     [self.scrollView addSubview:self.fieldEditorView]; | ||||
|      | ||||
|     _actionButton = [[UIBarButtonItem alloc] | ||||
|         initWithTitle:@"Set" | ||||
|         style:UIBarButtonItemStyleDone | ||||
|         target:self | ||||
|         action:@selector(actionButtonPressed:) | ||||
|     ]; | ||||
|      | ||||
|     self.navigationController.toolbarHidden = NO; | ||||
|     self.toolbarItems = @[UIBarButtonItem.flex_flexibleSpace, self.actionButton]; | ||||
| } | ||||
|  | ||||
| - (void)viewWillLayoutSubviews { | ||||
|     CGSize constrainSize = CGSizeMake(self.scrollView.bounds.size.width, CGFLOAT_MAX); | ||||
|     CGSize fieldEditorSize = [self.fieldEditorView sizeThatFits:constrainSize]; | ||||
|     self.fieldEditorView.frame = CGRectMake(0, 0, fieldEditorSize.width, fieldEditorSize.height); | ||||
|     self.scrollView.contentSize = fieldEditorSize; | ||||
| } | ||||
|  | ||||
| #pragma mark - Public | ||||
|  | ||||
| - (FLEXArgumentInputView *)firstInputView { | ||||
|     return [self.fieldEditorView argumentInputViews].firstObject; | ||||
| } | ||||
|  | ||||
| - (void)actionButtonPressed:(id)sender { | ||||
|     // Subclasses can override | ||||
|     [self.fieldEditorView endEditing:YES]; | ||||
|     if (_commitHandler) { | ||||
|         _commitHandler(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)exploreObjectOrPopViewController:(id)objectOrNil { | ||||
|     if (objectOrNil) { | ||||
|         // For non-nil (or void) return types, push an explorer view controller to display the object | ||||
|         FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:objectOrNil]; | ||||
|         [self.navigationController pushViewController:explorerViewController animated:YES]; | ||||
|     } else { | ||||
|         // If we didn't get a returned object but the method call succeeded, | ||||
|         // pop this view controller off the stack to indicate that the call went through. | ||||
|         [self.navigationController popViewControllerAnimated:YES]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @end | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn