mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-08-24 19:38:54 -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