mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 12:54:13 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			233 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  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
 | 
