diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..c015ace3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: objective-c +script: xctool test -project FBTweak.xcodeproj -scheme FBTweak -sdk iphonesimulator8.1 -destination "platform=iOS Simulator,OS=8.1,name=iPhone 6" + diff --git a/FBTweak.xcodeproj/project.pbxproj b/FBTweak.xcodeproj/project.pbxproj index 64d146cf..ca06fb00 100644 --- a/FBTweak.xcodeproj/project.pbxproj +++ b/FBTweak.xcodeproj/project.pbxproj @@ -40,6 +40,10 @@ 18EFE52D189F250700DA6A5D /* FBTweakInlineTestsMRR.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EFE52C189F250700DA6A5D /* FBTweakInlineTestsMRR.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 18EFE536189F38D500DA6A5D /* FBTweakEnabled.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EFE535189F38D500DA6A5D /* FBTweakEnabled.h */; settings = {ATTRIBUTES = (Public, ); }; }; 29E9F17B18E35C9C001EAF7D /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29E9F17A18E35C9C001EAF7D /* MessageUI.framework */; }; + 5BACB31E1A12813C00C4F79D /* _FBTweakArrayViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BACB31C1A12813C00C4F79D /* _FBTweakArrayViewController.h */; }; + 5BACB31F1A12813C00C4F79D /* _FBTweakArrayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BACB31D1A12813C00C4F79D /* _FBTweakArrayViewController.m */; }; + 5BE25A521A0AA9D500C97C3E /* _FBTweakDictionaryViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BE25A501A0AA9D500C97C3E /* _FBTweakDictionaryViewController.h */; }; + 5BE25A531A0AA9D500C97C3E /* _FBTweakDictionaryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BE25A511A0AA9D500C97C3E /* _FBTweakDictionaryViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -88,6 +92,10 @@ 18EFE52C189F250700DA6A5D /* FBTweakInlineTestsMRR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBTweakInlineTestsMRR.m; sourceTree = ""; }; 18EFE535189F38D500DA6A5D /* FBTweakEnabled.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBTweakEnabled.h; sourceTree = ""; }; 29E9F17A18E35C9C001EAF7D /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; + 5BACB31C1A12813C00C4F79D /* _FBTweakArrayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakArrayViewController.h; sourceTree = ""; }; + 5BACB31D1A12813C00C4F79D /* _FBTweakArrayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakArrayViewController.m; sourceTree = ""; }; + 5BE25A501A0AA9D500C97C3E /* _FBTweakDictionaryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakDictionaryViewController.h; sourceTree = ""; }; + 5BE25A511A0AA9D500C97C3E /* _FBTweakDictionaryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = _FBTweakDictionaryViewController.m; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -211,6 +219,10 @@ 18EFE4D8189EEED800DA6A5D /* _FBTweakCollectionViewController.m */, 18EFE4DB189EF75800DA6A5D /* _FBTweakTableViewCell.h */, 18EFE4DC189EF75800DA6A5D /* _FBTweakTableViewCell.m */, + 5BE25A501A0AA9D500C97C3E /* _FBTweakDictionaryViewController.h */, + 5BE25A511A0AA9D500C97C3E /* _FBTweakDictionaryViewController.m */, + 5BACB31C1A12813C00C4F79D /* _FBTweakArrayViewController.h */, + 5BACB31D1A12813C00C4F79D /* _FBTweakArrayViewController.m */, ); name = UI; sourceTree = ""; @@ -245,8 +257,10 @@ 18EFE4D0189EC70300DA6A5D /* FBTweakInlineInternal.h in Headers */, 18EFE527189F19B300DA6A5D /* FBTweakCategory.h in Headers */, 18EFE4BC189EBC4B00DA6A5D /* FBTweakCollection.h in Headers */, + 5BE25A521A0AA9D500C97C3E /* _FBTweakDictionaryViewController.h in Headers */, 18EFE4DD189EF75800DA6A5D /* _FBTweakTableViewCell.h in Headers */, 184A94F018D26871005F2774 /* _FBTweakBindObserver.h in Headers */, + 5BACB31E1A12813C00C4F79D /* _FBTweakArrayViewController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -331,6 +345,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5BE25A531A0AA9D500C97C3E /* _FBTweakDictionaryViewController.m in Sources */, 18EFE4A7189EBA9E00DA6A5D /* FBTweakViewController.m in Sources */, 18EFE4BD189EBC4B00DA6A5D /* FBTweakCollection.m in Sources */, 18EFE4C2189EBEAD00DA6A5D /* FBTweakStore.m in Sources */, @@ -340,6 +355,7 @@ 18EFE528189F19B300DA6A5D /* FBTweakCategory.m in Sources */, 18EFE4D6189EEBC500DA6A5D /* _FBTweakCategoryViewController.m in Sources */, 18EFE4AF189EBABA00DA6A5D /* FBTweakShakeWindow.m in Sources */, + 5BACB31F1A12813C00C4F79D /* _FBTweakArrayViewController.m in Sources */, 18EFE4DA189EEED800DA6A5D /* _FBTweakCollectionViewController.m in Sources */, 184A94F118D26871005F2774 /* _FBTweakBindObserver.m in Sources */, ); diff --git a/FBTweak.xcodeproj/xcshareddata/xcschemes/FBTweak.xcscheme b/FBTweak.xcodeproj/xcshareddata/xcschemes/FBTweak.xcscheme new file mode 100644 index 00000000..4375acd2 --- /dev/null +++ b/FBTweak.xcodeproj/xcshareddata/xcschemes/FBTweak.xcscheme @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FBTweak/FBTweak.h b/FBTweak/FBTweak.h index 3fb97f07..a04c10c6 100644 --- a/FBTweak/FBTweak.h +++ b/FBTweak/FBTweak.h @@ -19,6 +19,34 @@ */ typedef id FBTweakValue; +/** + @abstract Represents a range of values for a numeric tweak. + @discussion Use this for the -possibleValues on a tweak. + */ +@interface FBTweakNumericRange : NSObject + +/** + @abstract Creates a new numeric range. + @discussion This is the designated initializer. + @param minimumValue The minimum value of the range. + @param maximumValue The maximum value of the range. + */ +- (instancetype)initWithMinimumValue:(FBTweakValue)minimumValue maximumValue:(FBTweakValue)maximumValue; + +/** + @abstract The minimum value of the range. + @discussion Will always have a value. + */ +@property (nonatomic, strong, readwrite) FBTweakValue minimumValue; + +/** + @abstract The maximum value of the range. + @discussion Will always have a value. + */ +@property (nonatomic, strong, readwrite) FBTweakValue maximumValue; + +@end + /** @abstract Represents a unique, named tweak. @discussion A tweak contains a persistent, editable value. @@ -28,6 +56,7 @@ typedef id FBTweakValue; /** @abstract Creates a new tweak model. @discussion This is the designated initializer. + @param identifier The identifier for the tweak. Required. */ - (instancetype)initWithIdentifier:(NSString *)identifier; @@ -59,34 +88,45 @@ typedef id FBTweakValue; /** @abstract The current value of the tweak. Can be nil. - @discussion Changes will be propagated to disk. Enforces minimum - and maximum values when changed. Must not be set on actions. + @discussion Changes will be propagated to disk. Enforces within + possible values when changed. Must not be set on actions. */ @property (nonatomic, strong, readwrite) FBTweakValue currentValue; +/** + @abstract The possible values of the tweak. + @discussion Optional. If nil, any value is allowed. If an + FBTweakNumericRange, represents a range of numeric values. + If an array or dictionary, contains all of the allowed values. + Should not be set on tweaks representing actions. + */ +@property (nonatomic, strong, readwrite) id possibleValues; + /** @abstract The minimum value of the tweak. - @discussion Optional. If nil, there is no minimum. + @discussion Optional. If nil, there is no minimum. Numeric only. Should not be set on tweaks representing actions. */ @property (nonatomic, strong, readwrite) FBTweakValue minimumValue; /** @abstract The maximum value of the tweak. - @discussion Optional. If nil, there is no maximum. + @discussion Optional. If nil, there is no maximum. Numeric only. Should not be set on tweaks representing actions. */ @property (nonatomic, strong, readwrite) FBTweakValue maximumValue; /** - @abstract The step value of the tweak. - @discussion Optional. If nil, the step value is calculated of miniumum and maxium. + @abstract The step value of the tweak. + @discussion Optional. If nil, the step value is calculated from + the miniumum and maxium values. Only used for numeric tweaks. */ @property (nonatomic, strong, readwrite) FBTweakValue stepValue; /** - @abstract The decimal precision value of the tweak. - @discussion Optional. If nil, the precision value is calculated of the step value. + @abstract The decimal precision value of the tweak. + @discussion Optional. If nil, the precision value is calculated from + the step value. Only used for numeric tweaks. */ @property (nonatomic, strong, readwrite) FBTweakValue precisionValue; diff --git a/FBTweak/FBTweak.m b/FBTweak/FBTweak.m index 048f13e0..a0671e50 100644 --- a/FBTweak/FBTweak.m +++ b/FBTweak/FBTweak.m @@ -9,6 +9,38 @@ #import "FBTweak.h" +@implementation FBTweakNumericRange + +- (instancetype)initWithMinimumValue:(FBTweakValue)minimumValue maximumValue:(FBTweakValue)maximumValue +{ + if ((self = [super init])) { + NSParameterAssert(minimumValue != nil); + NSParameterAssert(maximumValue != nil); + + _minimumValue = minimumValue; + _maximumValue = maximumValue; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + FBTweakValue minimumValue = [coder decodeObjectForKey:@"minimumValue"]; + FBTweakValue maximumValue = [coder decodeObjectForKey:@"maximumValue"]; + self = [self initWithMinimumValue:minimumValue maximumValue:maximumValue]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject:_minimumValue forKey:@"minimumValue"]; + [coder encodeObject:_maximumValue forKey:@"maximumValue"]; +} + +@end + @implementation FBTweak { NSHashTable *_observers; } @@ -20,8 +52,16 @@ - (instancetype)initWithCoder:(NSCoder *)coder if ((self = [self initWithIdentifier:identifier])) { _name = [coder decodeObjectForKey:@"name"]; _defaultValue = [coder decodeObjectForKey:@"defaultValue"]; - _minimumValue = [coder decodeObjectForKey:@"minimumValue"]; - _maximumValue = [coder decodeObjectForKey:@"maximumValue"]; + + if ([coder containsValueForKey:@"possibleValues"]) { + _possibleValues = [coder decodeObjectForKey:@"possibleValues"]; + } else { + // Backwards compatbility for before possibleValues was introduced. + FBTweakValue minimumValue = [coder decodeObjectForKey:@"minimumValue"]; + FBTweakValue maximumValue = [coder decodeObjectForKey:@"maximumValue"]; + _possibleValues = [[FBTweakNumericRange alloc] initWithMinimumValue:minimumValue maximumValue:maximumValue]; + } + _precisionValue = [coder decodeObjectForKey:@"precisionValue"]; _stepValue = [coder decodeObjectForKey:@"stepValue"]; @@ -49,8 +89,7 @@ - (void)encodeWithCoder:(NSCoder *)coder if (!self.isAction) { [coder encodeObject:_defaultValue forKey:@"defaultValue"]; - [coder encodeObject:_minimumValue forKey:@"minimumValue"]; - [coder encodeObject:_maximumValue forKey:@"maximumValue"]; + [coder encodeObject:_possibleValues forKey:@"possibleValues"]; [coder encodeObject:_currentValue forKey:@"currentValue"]; [coder encodeObject:_precisionValue forKey:@"precisionValue"]; [coder encodeObject:_stepValue forKey:@"stepValue"]; @@ -69,18 +108,72 @@ - (BOOL)isAction return [_defaultValue isKindOfClass:blockClass]; } +- (FBTweakValue)minimumValue +{ + if ([_possibleValues isKindOfClass:[FBTweakNumericRange class]]) { + return [(FBTweakNumericRange *)_possibleValues minimumValue]; + } else { + return nil; + } +} + +- (void)setMinimumValue:(FBTweakValue)minimumValue +{ + if (minimumValue == nil) { + _possibleValues = nil; + } else if ([_possibleValues isKindOfClass:[FBTweakNumericRange class]]) { + _possibleValues = [[FBTweakNumericRange alloc] initWithMinimumValue:minimumValue maximumValue:[(FBTweakNumericRange *)_possibleValues maximumValue]]; + } else { + _possibleValues = [[FBTweakNumericRange alloc] initWithMinimumValue:minimumValue maximumValue:minimumValue]; + } +} + +- (FBTweakValue)maximumValue +{ + if ([_possibleValues isKindOfClass:[FBTweakNumericRange class]]) { + return [(FBTweakNumericRange *)_possibleValues maximumValue]; + } else { + return nil; + } +} + +- (void)setMaximumValue:(FBTweakValue)maximumValue +{ + if (maximumValue == nil) { + _possibleValues = nil; + } else if ([_possibleValues isKindOfClass:[FBTweakNumericRange class]]) { + _possibleValues = [[FBTweakNumericRange alloc] initWithMinimumValue:[(FBTweakNumericRange *)_possibleValues minimumValue] maximumValue:maximumValue]; + } else { + _possibleValues = [[FBTweakNumericRange alloc] initWithMinimumValue:maximumValue maximumValue:maximumValue]; + } +} + - (void)setCurrentValue:(FBTweakValue)currentValue { NSAssert(!self.isAction, @"actions cannot have non-default values"); - if (_minimumValue != nil && currentValue != nil && [_minimumValue compare:currentValue] == NSOrderedDescending) { - currentValue = _minimumValue; - } - - if (_maximumValue != nil && currentValue != nil && [_maximumValue compare:currentValue] == NSOrderedAscending) { - currentValue = _maximumValue; + if (_possibleValues != nil && currentValue != nil) { + if ([_possibleValues isKindOfClass:[NSArray class]]) { + if ([_possibleValues indexOfObject:currentValue] == NSNotFound) { + currentValue = _defaultValue; + } + } else if ([_possibleValues isKindOfClass:[NSDictionary class]]) { + if ([[_possibleValues allKeys] indexOfObject:currentValue] == NSNotFound) { + currentValue = _defaultValue; + } + } else { + FBTweakValue minimumValue = self.minimumValue; + if (self.minimumValue != nil && currentValue != nil && [minimumValue compare:currentValue] == NSOrderedDescending) { + currentValue = minimumValue; + } + + FBTweakValue maximumValue = self.maximumValue; + if (maximumValue != nil && currentValue != nil && [maximumValue compare:currentValue] == NSOrderedAscending) { + currentValue = maximumValue; + } + } } - + if (_currentValue != currentValue) { _currentValue = currentValue; [[NSUserDefaults standardUserDefaults] setObject:_currentValue forKey:_identifier]; diff --git a/FBTweak/FBTweakInline.m b/FBTweak/FBTweakInline.m index d79d7436..11b4e4de 100644 --- a/FBTweak/FBTweakInline.m +++ b/FBTweak/FBTweakInline.m @@ -14,13 +14,13 @@ #import "FBTweakStore.h" #import "FBTweakCategory.h" +#if FB_TWEAK_ENABLED + #import #import #import #import -#if FB_TWEAK_ENABLED - extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry) { return [NSString stringWithFormat:@"FBTweak:%@-%@-%@", *entry->category, *entry->collection, *entry->name]; @@ -31,63 +31,35 @@ FBTweak *tweak = [[FBTweak alloc] initWithIdentifier:identifier]; tweak.name = *entry->name; + if (entry->possible != NULL) { + tweak.possibleValues = fb_tweak_entry_block_field(id, entry, possible); + } + if (strcmp(*entry->encoding, FBTweakEncodingAction) == 0) { - tweak.defaultValue = *(dispatch_block_t __strong *)entry->value; + tweak.defaultValue = *(__strong dispatch_block_t *)entry->value; } else if (strcmp(*entry->encoding, @encode(BOOL)) == 0) { - tweak.defaultValue = @(*(BOOL *)entry->value); + tweak.defaultValue = @(fb_tweak_entry_block_field(BOOL, entry, value)); } else if (strcmp(*entry->encoding, @encode(float)) == 0) { - tweak.defaultValue = [NSNumber numberWithFloat:*(float *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithFloat:*(float *)entry->min]; - tweak.maximumValue = [NSNumber numberWithFloat:*(float *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithFloat:fb_tweak_entry_block_field(float, entry, value)]; } else if (strcmp(*entry->encoding, @encode(double)) == 0) { - tweak.defaultValue = [NSNumber numberWithDouble:*(double *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithDouble:*(double *)entry->min]; - tweak.maximumValue = [NSNumber numberWithDouble:*(double *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithDouble:fb_tweak_entry_block_field(double, entry, value)]; } else if (strcmp(*entry->encoding, @encode(short)) == 0) { - tweak.defaultValue = [NSNumber numberWithShort:*(short *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithShort:*(short *)entry->min]; - tweak.maximumValue = [NSNumber numberWithShort:*(short *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithShort:fb_tweak_entry_block_field(short, entry, value)]; } else if (strcmp(*entry->encoding, @encode(unsigned short)) == 0) { - tweak.defaultValue = [NSNumber numberWithUnsignedShort:*(unsigned short int *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithUnsignedShort:*(unsigned short *)entry->min]; - tweak.maximumValue = [NSNumber numberWithUnsignedShort:*(unsigned short *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithUnsignedShort:fb_tweak_entry_block_field(unsigned short, entry, value)]; } else if (strcmp(*entry->encoding, @encode(int)) == 0) { - tweak.defaultValue = [NSNumber numberWithInt:*(int *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithInt:*(int *)entry->min]; - tweak.maximumValue = [NSNumber numberWithInt:*(int *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithInt:fb_tweak_entry_block_field(int, entry, value)]; } else if (strcmp(*entry->encoding, @encode(unsigned int)) == 0) { - tweak.defaultValue = [NSNumber numberWithUnsignedInt:*(unsigned int *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithUnsignedInt:*(unsigned int *)entry->min]; - tweak.maximumValue = [NSNumber numberWithUnsignedInt:*(unsigned int *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithUnsignedInt:fb_tweak_entry_block_field(unsigned int, entry, value)]; } else if (strcmp(*entry->encoding, @encode(long long)) == 0) { - tweak.defaultValue = [NSNumber numberWithLongLong:*(long long *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithLongLong:*(long long *)entry->min]; - tweak.maximumValue = [NSNumber numberWithLongLong:*(long long *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithLongLong:fb_tweak_entry_block_field(long long, entry, value)]; } else if (strcmp(*entry->encoding, @encode(unsigned long long)) == 0) { - tweak.defaultValue = [NSNumber numberWithUnsignedLongLong:*(unsigned long long *)entry->value]; - if (entry->min != NULL && entry->max != NULL) { - tweak.minimumValue = [NSNumber numberWithUnsignedLongLong:*(unsigned long long *)entry->min]; - tweak.maximumValue = [NSNumber numberWithUnsignedLongLong:*(unsigned long long *)entry->max]; - } + tweak.defaultValue = [NSNumber numberWithUnsignedLongLong:fb_tweak_entry_block_field(unsigned long long, entry, value)]; } else if (*entry->encoding[0] == '[') { // Assume it's a C string. - tweak.defaultValue = [NSString stringWithUTF8String:entry->value]; + tweak.defaultValue = [NSString stringWithUTF8String:fb_tweak_entry_block_field(char *, entry, value)]; } else if (strcmp(*entry->encoding, @encode(id)) == 0) { - tweak.defaultValue = *((__unsafe_unretained id *)entry->value); + tweak.defaultValue = fb_tweak_entry_block_field(id, entry, value); } else { NSCAssert(NO, @"Unknown encoding %s for tweak %@. Value was %p.", *entry->encoding, _FBTweakIdentifier(entry), entry->value); tweak = nil; @@ -114,10 +86,12 @@ + (void)load #ifdef __LP64__ typedef uint64_t fb_tweak_value; typedef struct section_64 fb_tweak_section; + typedef struct mach_header_64 fb_tweak_header; #define fb_tweak_getsectbynamefromheader getsectbynamefromheader_64 #else typedef uint32_t fb_tweak_value; typedef struct section fb_tweak_section; + typedef struct mach_header fb_tweak_header; #define fb_tweak_getsectbynamefromheader getsectbynamefromheader #endif @@ -127,15 +101,15 @@ + (void)load dladdr(&_FBTweakIdentifier, &info); const fb_tweak_value mach_header = (fb_tweak_value)info.dli_fbase; - const fb_tweak_section *section = fb_tweak_getsectbynamefromheader((void *)mach_header, FBTweakSegmentName, FBTweakSectionName); - - if (section == NULL) { - return; + + unsigned long size; + fb_tweak_entry *data = (fb_tweak_entry *) getsectiondata((const fb_tweak_header *) mach_header, FBTweakSegmentName, FBTweakSectionName, &size); + if (data == NULL) { + return ; } - - for (fb_tweak_value addr = section->offset; addr < section->offset + section->size; addr += sizeof(fb_tweak_entry)) { - fb_tweak_entry *entry = (fb_tweak_entry *)(mach_header + addr); - + size_t count = size / sizeof(fb_tweak_entry); + for (size_t i = 0; i < count; i++) { + fb_tweak_entry *entry = &data[i]; FBTweakCategory *category = [store tweakCategoryWithName:*entry->category]; if (category == nil) { category = [[FBTweakCategory alloc] initWithName:*entry->category]; diff --git a/FBTweak/FBTweakInlineInternal.h b/FBTweak/FBTweakInlineInternal.h index 32eddd1d..b79ce42c 100644 --- a/FBTweak/FBTweakInlineInternal.h +++ b/FBTweak/FBTweakInlineInternal.h @@ -40,11 +40,13 @@ typedef struct { FBTweakLiteralString *collection; FBTweakLiteralString *name; void *value; - void *min; - void *max; + void *possible; char **encoding; } fb_tweak_entry; +// cast to a pointer to a block, dereferenece said pointer, call said block +#define fb_tweak_entry_block_field(type, entry, field) (*(type (^__unsafe_unretained (*))(void))(entry->field))() + extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); #if __has_feature(objc_arc) @@ -59,31 +61,36 @@ extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); #define __FBTweakIndex(_1, _2, _3, value, ...) value #define __FBTweakIndexCount(...) __FBTweakIndex(__VA_ARGS__, 3, 2, 1) -#define __FBTweakHasRange1(__withoutRange, __withRange, ...) __withoutRange -#define __FBTweakHasRange2(__withoutRange, __withRange, ...) __FBTweakInvalidNumberOfArgumentsPassed -#define __FBTweakHasRange3(__withoutRange, __withRange, ...) __withRange -#define _FBTweakHasRange(__withoutRange, __withRange, ...) __FBTweakConcat(__FBTweakHasRange, __FBTweakIndexCount(__VA_ARGS__))(__withoutRange, __withRange) +#define __FBTweakDispatch1(__withoutRange, __withRange, __withPossible, ...) __withoutRange +#define __FBTweakDispatch2(__withoutRange, __withRange, __withPossible, ...) __withPossible +#define __FBTweakDispatch3(__withoutRange, __withRange, __withPossible, ...) __withRange +#define _FBTweakDispatch(__withoutRange, __withRange, __withPossible, ...) __FBTweakConcat(__FBTweakDispatch, __FBTweakIndexCount(__VA_ARGS__))(__withoutRange, __withRange, __withPossible) #define _FBTweakInlineWithoutRange(category_, collection_, name_, default_) \ ((^{ \ - return _FBTweakInlineWithRangeInternal(category_, collection_, name_, default_, NULL, NULL); \ + return _FBTweakInlineWithPossibleInternal(category_, collection_, name_, default_, NULL); \ })()) #define _FBTweakInlineWithRange(category_, collection_, name_, default_, min_, max_) \ ((^{ \ __attribute__((used)) static __typeof__(default_) min__ = (__typeof__(default_))min_; \ __attribute__((used)) static __typeof__(default_) max__ = (__typeof__(default_))max_; \ - return _FBTweakInlineWithRangeInternal(category_, collection_, name_, default_, &min__, &max__); \ + return _FBTweakInlineWithPossibleInternal(category_, collection_, name_, default_, [[FBTweakNumericRange alloc] initWithMinimumValue:@(min__) maximumValue:@(max__)]); \ +})()) +#define _FBTweakInlineWithPossible(category_, collection_, name_, default_, possible_) \ +((^{ \ + return _FBTweakInlineWithPossibleInternal(category_, collection_, name_, default_, possible_); \ })()) -#define _FBTweakInlineWithRangeInternal(category_, collection_, name_, default_, min__, max__) \ +#define _FBTweakInlineWithPossibleInternal(category_, collection_, name_, default_, possible_) \ ((^{ \ /* store the tweak data in the binary at compile time. */ \ __attribute__((used)) static FBTweakLiteralString category__ = category_; \ __attribute__((used)) static FBTweakLiteralString collection__ = collection_; \ __attribute__((used)) static FBTweakLiteralString name__ = name_; \ - __attribute__((used)) static __typeof__(default_) default__ = default_; \ + __attribute__((used)) static void *default__ = (__bridge void *) ^{ return default_; }; \ + __attribute__((used)) static void *possible__ = (__bridge void *) ^{ return possible_; }; \ __attribute__((used)) static char *encoding__ = (char *)@encode(__typeof__(default_)); \ __attribute__((used)) __attribute__((section (FBTweakSegmentName "," FBTweakSectionName))) static fb_tweak_entry entry = \ - { &category__, &collection__, &name__, (void *)&default__, (void *)min__, (void *)max__, &encoding__ }; \ + { &category__, &collection__, &name__, (void *)&default__, (void *)&possible__, &encoding__ }; \ \ /* find the registered tweak with the given identifier. */ \ FBTweakStore *store = [FBTweakStore sharedInstance]; \ @@ -95,8 +102,12 @@ extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); \ return __inline_tweak; \ })()) -#define _FBTweakInline(category_, collection_, name_, ...) _FBTweakHasRange(_FBTweakInlineWithoutRange, _FBTweakInlineWithRange, __VA_ARGS__)(category_, collection_, name_, __VA_ARGS__) - +#define _FBTweakInline(category_, collection_, name_, ...) _FBTweakDispatch(_FBTweakInlineWithoutRange, _FBTweakInlineWithRange, _FBTweakInlineWithPossible, __VA_ARGS__)(category_, collection_, name_, __VA_ARGS__) + +#ifdef __cplusplus +#define _FBTweakValueInternal(...) \ +((^{ static_assert(false, "C++ not supported at present. See https://github.com/facebook/Tweaks/issues/84."); nil; })()) +#else #define _FBTweakValueInternal(tweak_, category_, collection_, name_, default_) \ ((^{ \ /* returns a correctly typed version of the current tweak value */ \ @@ -114,6 +125,10 @@ extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); const int: [currentValue intValue], \ unsigned int: [currentValue unsignedIntValue], \ const unsigned int: [currentValue unsignedIntValue], \ + long: [currentValue longValue], \ + const long: [currentValue longValue], \ + unsigned long: [currentValue unsignedLongValue], \ + const unsigned long: [currentValue unsignedLongValue], \ long long: [currentValue longLongValue], \ const long long: [currentValue longLongValue], \ unsigned long long: [currentValue unsignedLongLongValue], \ @@ -129,6 +144,7 @@ extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); default: [currentValue UTF8String] \ ); \ })()) +#endif #define _FBTweakValueWithoutRange(category_, collection_, name_, default_) \ ((^{ \ @@ -140,19 +156,29 @@ extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); FBTweak *__value_tweak = _FBTweakInlineWithRange(category_, collection_, name_, default_, min_, max_); \ return _FBTweakValueInternal(__value_tweak, category_, collection_, name_, default_); \ })()) -#define _FBTweakValue(category_, collection_, name_, ...) _FBTweakHasRange(_FBTweakValueWithoutRange, _FBTweakValueWithRange, __VA_ARGS__)(category_, collection_, name_, __VA_ARGS__) +#define _FBTweakValueWithPossible(category_, collection_, name_, default_, possible_) \ +((^{ \ + FBTweak *__value_tweak = _FBTweakInlineWithPossible(category_, collection_, name_, default_, possible_); \ + return _FBTweakValueInternal(__value_tweak, category_, collection_, name_, default_); \ +})()) +#define _FBTweakValue(category_, collection_, name_, ...) _FBTweakDispatch(_FBTweakValueWithoutRange, _FBTweakValueWithRange, _FBTweakValueWithPossible, __VA_ARGS__)(category_, collection_, name_, __VA_ARGS__) #define _FBTweakBindWithoutRange(object_, property_, category_, collection_, name_, default_) \ ((^{ \ FBTweak *__bind_tweak = _FBTweakInlineWithoutRange(category_, collection_, name_, default_); \ - _FBTweakBindWithRangeInternal(object_, property_, category_, collection_, name_, default_, __bind_tweak); \ + _FBTweakBindInternal(object_, property_, category_, collection_, name_, default_, __bind_tweak); \ })()) #define _FBTweakBindWithRange(object_, property_, category_, collection_, name_, default_, min_, max_) \ ((^{ \ FBTweak *__bind_tweak = _FBTweakInlineWithRange(category_, collection_, name_, default_, min_, max_); \ - _FBTweakBindWithRangeInternal(object_, property_, category_, collection_, name_, default_, __bind_tweak); \ + _FBTweakBindInternal(object_, property_, category_, collection_, name_, default_, __bind_tweak); \ +})()) +#define _FBTweakBindWithPossible(object_, property_, category_, collection_, name_, default_, possible_) \ +((^{ \ + FBTweak *__bind_tweak = _FBTweakInlineWithPossible(category_, collection_, name_, default_, possible_); \ + _FBTweakBindInternal(object_, property_, category_, collection_, name_, default_, __bind_tweak); \ })()) -#define _FBTweakBindWithRangeInternal(object_, property_, category_, collection_, name_, default_, tweak_) \ +#define _FBTweakBindInternal(object_, property_, category_, collection_, name_, default_, tweak_) \ ((^{ \ object_.property_ = _FBTweakValueInternal(tweak_, category_, collection_, name_, default_); \ _FBTweakBindObserver *observer__ = [[_FBTweakBindObserver alloc] initWithTweak:tweak_ block:^(id object__) { \ @@ -161,7 +187,7 @@ extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); }]; \ [observer__ attachToObject:object_]; \ })()) -#define _FBTweakBind(object_, property_, category_, collection_, name_, ...) _FBTweakHasRange(_FBTweakBindWithoutRange, _FBTweakBindWithRange, __VA_ARGS__)(object_, property_, category_, collection_, name_, __VA_ARGS__) +#define _FBTweakBind(object_, property_, category_, collection_, name_, ...) _FBTweakDispatch(_FBTweakBindWithoutRange, _FBTweakBindWithRange, _FBTweakBindWithPossible, __VA_ARGS__)(object_, property_, category_, collection_, name_, __VA_ARGS__) #define _FBTweakAction(category_, collection_, name_, ...) \ _FBTweakActionInternal(category_, collection_, name_, __COUNTER__, __VA_ARGS__) @@ -177,7 +203,7 @@ extern NSString *_FBTweakIdentifier(fb_tweak_entry *entry); &__FBTweakConcat(__fb_tweak_action_collection_, suffix_), \ &__FBTweakConcat(__fb_tweak_action_name_, suffix_), \ &__FBTweakConcat(__fb_tweak_action_block_, suffix_), \ - NULL, NULL, \ + NULL, \ &__FBTweakConcat(__fb_tweak_action_encoding_, suffix_), \ }; \ diff --git a/FBTweak/FBTweakShakeWindow.m b/FBTweak/FBTweakShakeWindow.m index d91abbd7..77c3b103 100644 --- a/FBTweak/FBTweakShakeWindow.m +++ b/FBTweak/FBTweakShakeWindow.m @@ -17,11 +17,54 @@ @implementation FBTweakShakeWindow { BOOL _shaking; + BOOL _active; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if ((self = [super initWithFrame:frame])) { + _FBTweakShakeWindowCommonInit(self); + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + if ((self = [super initWithCoder:coder])) { + _FBTweakShakeWindowCommonInit(self); + } + return self; +} + +static void _FBTweakShakeWindowCommonInit(FBTweakShakeWindow *self) +{ + // Maintain this state manually using notifications so Tweaks can be used in app extensions, where UIApplication is unavailable. + self->_active = YES; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationWillResignActiveWithNotification:) name:UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_applicationDidBecomeActiveWithNotification:) name:UIApplicationDidBecomeActiveNotification object:nil]; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; +} + +- (void)_applicationWillResignActiveWithNotification:(NSNotification *)notification +{ + _active = NO; +} + +- (void)_applicationDidBecomeActiveWithNotification:(NSNotification *)notification +{ + _active = YES; } - (void)tweakViewControllerPressedDone:(FBTweakViewController *)tweakViewController { [[NSNotificationCenter defaultCenter] postNotificationName:FBTweakShakeViewControllerDidDismissNotification object:tweakViewController]; + [tweakViewController.view endEditing:YES]; [tweakViewController dismissViewControllerAnimated:YES completion:NULL]; } @@ -43,10 +86,10 @@ - (void)_presentTweaks - (BOOL)_shouldPresentTweaks { -#if FB_TWEAK_ENABLED - return _shaking && [[UIApplication sharedApplication] applicationState] == UIApplicationStateActive; -#elif TARGET_IPHONE_SIMULATOR +#if TARGET_IPHONE_SIMULATOR && FB_TWEAK_ENABLED return YES; +#elif FB_TWEAK_ENABLED + return _shaking && _active; #else return NO; #endif diff --git a/FBTweak/FBTweakViewController.h b/FBTweak/FBTweakViewController.h index 580b1482..c946c952 100644 --- a/FBTweak/FBTweakViewController.h +++ b/FBTweak/FBTweakViewController.h @@ -28,10 +28,18 @@ extern NSString *const FBTweakShakeViewControllerDidDismissNotification; /** @abstract Create a tweak view controller. @param store The tweak store to show. Usually +[FBTweakStore sharedInstance]. - @discussion The designated initializer. + @discussion Calls -[initWithStore:category:] with a nil category. */ - (instancetype)initWithStore:(FBTweakStore *)store; +/** + @abstract Create a tweak view controller drilled-down to a specific category + @param store The tweak store to show. Usually +[FBTweakStore sharedInstance]. + @param name The tweak category to drill down to. Use nil to show all categories + @discussion The designated initializer. + */ +- (instancetype)initWithStore:(FBTweakStore *)store category:(NSString *)categoryName; + /** @abstract Responds to tweak view controller actions. @discussion Named {@ref tweaksDelegate} to avoid conflicting with UINavigationController. diff --git a/FBTweak/FBTweakViewController.m b/FBTweak/FBTweakViewController.m index 02d9cb5b..5c863d36 100644 --- a/FBTweak/FBTweakViewController.m +++ b/FBTweak/FBTweakViewController.m @@ -22,15 +22,27 @@ @implementation FBTweakViewController { } - (instancetype)initWithStore:(FBTweakStore *)store +{ + return [self initWithStore:store category:nil]; +} + +- (instancetype)initWithStore:(FBTweakStore *)store category:(NSString *)categoryName { if ((self = [super init])) { _store = store; - + _FBTweakCategoryViewController *categoryViewController = [[_FBTweakCategoryViewController alloc] initWithStore:store]; categoryViewController.delegate = self; [self pushViewController:categoryViewController animated:NO]; + + FBTweakCategory *category = nil; + if (categoryName && (category = [store tweakCategoryWithName:categoryName])) { + _FBTweakCollectionViewController *collectionViewController = [[_FBTweakCollectionViewController alloc] initWithTweakCategory:category]; + collectionViewController.delegate = self; + [self pushViewController:collectionViewController animated:NO]; + } } - + return self; } diff --git a/FBTweak/_FBTweakArrayViewController.h b/FBTweak/_FBTweakArrayViewController.h new file mode 100644 index 00000000..d0926c2e --- /dev/null +++ b/FBTweak/_FBTweakArrayViewController.h @@ -0,0 +1,32 @@ +/** + Copyright (c) 2014-present, Facebook, Inc. + All rights reserved. + + This source code is licensed under the BSD-style license found in the + LICENSE file in the root directory of this source tree. An additional grant + of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class FBTweak; + +/** + @abstract Displays list of values in an array tweak. + */ +@interface _FBTweakArrayViewController : UIViewController + +/** + @abstract Creates a tweak array view controller. + @discussion This is the designated initializer. + @param tweak The tweak the view controller is for. + Must not be nil, and must have an array of possibleValues. + */ +- (instancetype)initWithTweak:(FBTweak *)tweak; + +/** + @abstract The array tweak to display in the view controller. + */ +@property (nonatomic, strong, readonly) FBTweak *tweak; + +@end diff --git a/FBTweak/_FBTweakArrayViewController.m b/FBTweak/_FBTweakArrayViewController.m new file mode 100644 index 00000000..bb7b90be --- /dev/null +++ b/FBTweak/_FBTweakArrayViewController.m @@ -0,0 +1,89 @@ +/** + Copyright (c) 2014-present, Facebook, Inc. + All rights reserved. + + This source code is licensed under the BSD-style license found in the + LICENSE file in the root directory of this source tree. An additional grant + of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "_FBTweakArrayViewController.h" +#import "FBTweak.h" + +@interface _FBTweakArrayViewController () + +@end + +@implementation _FBTweakArrayViewController { + UITableView *_tableView; +} + +- (instancetype)initWithTweak:(FBTweak *)tweak +{ + NSParameterAssert(tweak != nil); + NSParameterAssert([tweak.possibleValues isKindOfClass:[NSArray class]]); + + if ((self = [super init])) { + _tweak = tweak; + self.title = _tweak.name; + } + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + [self.view addSubview:_tableView]; +} + +- (void)dealloc +{ + _tableView.delegate = nil; + _tableView.dataSource = nil; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [self.tweak.possibleValues count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + static NSString *_FBTweakDictionaryViewControllerCellIdentifier = @"_FBTweakDictionaryViewControllerCellIdentifier"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_FBTweakDictionaryViewControllerCellIdentifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_FBTweakDictionaryViewControllerCellIdentifier]; + } + + FBTweakValue rowValue = self.tweak.possibleValues[indexPath.row]; + NSString *stringValue = [rowValue description]; + cell.textLabel.text = stringValue; + + cell.accessoryType = UITableViewCellAccessoryNone; + FBTweakValue selectedValue = (self.tweak.currentValue ?: self.tweak.defaultValue); + if ([selectedValue isEqual:rowValue]) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSString *value = self.tweak.possibleValues[indexPath.row]; + self.tweak.currentValue = value; + [self.navigationController popViewControllerAnimated:YES]; +} + +@end diff --git a/FBTweak/_FBTweakCategoryViewController.m b/FBTweak/_FBTweakCategoryViewController.m index ce112b87..162b0034 100644 --- a/FBTweak/_FBTweakCategoryViewController.m +++ b/FBTweak/_FBTweakCategoryViewController.m @@ -85,12 +85,38 @@ - (void)_done - (void)_reset { - UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Are you sure?" - message:@"Are you sure you want to reset your tweaks? This cannot be undone." - delegate:self - cancelButtonTitle:@"Cancel" - otherButtonTitles:@"Reset", nil]; - [alert show]; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + if ([UIAlertController class] != nil) { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Are you sure?" + message:@"Are you sure you want to reset your tweaks? This cannot be undone." + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + // do nothing + }]; + [alertController addAction:cancelAction]; + + __weak typeof(self) weakSelf = self; + UIAlertAction *resetAction = [UIAlertAction actionWithTitle:@"Reset" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [weakSelf.store reset]; + }]; + [alertController addAction:resetAction]; + + [self presentViewController:alertController animated:YES completion:NULL]; + } else { +#endif +#if (!defined(__has_feature) || !__has_feature(attribute_availability_app_extension)) + // This is iOS 7 or lower. We need to use UIAlertView, because UIAlertController is not available. + // UIAlertView, however, is not available in app-extensions, so to allow compilation, we conditionally compile this branch only when we're not an app-extension. UIAlertController is always available in app-extensions, so this is safe. + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Are you sure?" + message:@"Are you sure you want to reset your tweaks? This cannot be undone." + delegate:self + cancelButtonTitle:@"Cancel" + otherButtonTitles:@"Reset", nil]; + [alert show]; +#endif +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + } +#endif } - (void)_export diff --git a/FBTweak/_FBTweakCollectionViewController.m b/FBTweak/_FBTweakCollectionViewController.m index 0020d3ea..397143e9 100644 --- a/FBTweak/_FBTweakCollectionViewController.m +++ b/FBTweak/_FBTweakCollectionViewController.m @@ -9,8 +9,11 @@ #import "FBTweakCollection.h" #import "FBTweakCategory.h" +#import "FBTweak.h" #import "_FBTweakCollectionViewController.h" #import "_FBTweakTableViewCell.h" +#import "_FBTweakDictionaryViewController.h" +#import "_FBTweakArrayViewController.h" @interface _FBTweakCollectionViewController () @end @@ -25,10 +28,7 @@ - (instancetype)initWithTweakCategory:(FBTweakCategory *)category if ((self = [super init])) { _tweakCategory = category; self.title = _tweakCategory.name; - - _sortedCollections = [_tweakCategory.tweakCollections sortedArrayUsingComparator:^(FBTweakCollection *a, FBTweakCollection *b) { - return [a.name localizedStandardCompare:b.name]; - }]; + [self _reloadData]; } return self; @@ -60,6 +60,15 @@ - (void)viewWillAppear:(BOOL)animated [super viewWillAppear:animated]; [_tableView deselectRowAtIndexPath:_tableView.indexPathForSelectedRow animated:animated]; + [self _reloadData]; +} + +- (void)_reloadData +{ + _sortedCollections = [_tweakCategory.tweakCollections sortedArrayUsingComparator:^(FBTweakCollection *a, FBTweakCollection *b) { + return [a.name localizedStandardCompare:b.name]; + }]; + [_tableView reloadData]; } - (void)_done @@ -75,15 +84,17 @@ - (void)_keyboardFrameChanged:(NSNotification *)notification NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; UIViewAnimationCurve curve = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; - + + __weak typeof(self) weakSelf = self; + __weak typeof(_tableView) weakTableView = _tableView; void (^animations)() = ^{ - UIEdgeInsets contentInset = _tableView.contentInset; - contentInset.bottom = (self.view.bounds.size.height - CGRectGetMinY(endFrame)); - _tableView.contentInset = contentInset; + UIEdgeInsets contentInset = weakTableView.contentInset; + contentInset.bottom = (weakSelf.view.bounds.size.height - CGRectGetMinY(endFrame)); + weakTableView.contentInset = contentInset; - UIEdgeInsets scrollIndicatorInsets = _tableView.scrollIndicatorInsets; - scrollIndicatorInsets.bottom = (self.view.bounds.size.height - CGRectGetMinY(endFrame)); - _tableView.scrollIndicatorInsets = scrollIndicatorInsets; + UIEdgeInsets scrollIndicatorInsets = weakTableView.scrollIndicatorInsets; + scrollIndicatorInsets.bottom = (weakSelf.view.bounds.size.height - CGRectGetMinY(endFrame)); + weakTableView.scrollIndicatorInsets = scrollIndicatorInsets; }; UIViewAnimationOptions options = (curve << 16) | UIViewAnimationOptionBeginFromCurrentState; @@ -123,4 +134,17 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + FBTweakCollection *collection = _sortedCollections[indexPath.section]; + FBTweak *tweak = collection.tweaks[indexPath.row]; + if ([tweak.possibleValues isKindOfClass:[NSDictionary class]]) { + _FBTweakDictionaryViewController *vc = [[_FBTweakDictionaryViewController alloc] initWithTweak:tweak]; + [self.navigationController pushViewController:vc animated:YES]; + } else if ([tweak.possibleValues isKindOfClass:[NSArray class]]) { + _FBTweakArrayViewController *vc = [[_FBTweakArrayViewController alloc] initWithTweak:tweak]; + [self.navigationController pushViewController:vc animated:YES]; + } +} + @end diff --git a/FBTweak/_FBTweakDictionaryViewController.h b/FBTweak/_FBTweakDictionaryViewController.h new file mode 100644 index 00000000..5b1b43fd --- /dev/null +++ b/FBTweak/_FBTweakDictionaryViewController.h @@ -0,0 +1,32 @@ +/** + Copyright (c) 2014-present, Facebook, Inc. + All rights reserved. + + This source code is licensed under the BSD-style license found in the + LICENSE file in the root directory of this source tree. An additional grant + of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@class FBTweak; + +/** + @abstract Displays list of keys in a dictionary tweak. + */ +@interface _FBTweakDictionaryViewController : UIViewController + +/** + @abstract Creates a tweak dictionary view controller. + @discussion This is the designated initializer. + @param tweak The tweak the view controller is for. Must + not be nil, and must have a dictionary of possibleValues. + */ +- (instancetype)initWithTweak:(FBTweak *)tweak; + +/** + @abstract The dictionary tweak to display in the view controller. + */ +@property (nonatomic, strong, readonly) FBTweak *tweak; + +@end diff --git a/FBTweak/_FBTweakDictionaryViewController.m b/FBTweak/_FBTweakDictionaryViewController.m new file mode 100644 index 00000000..ac36c14e --- /dev/null +++ b/FBTweak/_FBTweakDictionaryViewController.m @@ -0,0 +1,103 @@ +/** + Copyright (c) 2014-present, Facebook, Inc. + All rights reserved. + + This source code is licensed under the BSD-style license found in the + LICENSE file in the root directory of this source tree. An additional grant + of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "_FBTweakDictionaryViewController.h" +#import "FBTweak.h" + +@interface _FBTweakDictionaryViewController () + +@end + +@implementation _FBTweakDictionaryViewController { + UITableView *_tableView; +} + +- (instancetype)initWithTweak:(FBTweak *)tweak +{ + NSParameterAssert(tweak != nil); + NSParameterAssert([tweak.possibleValues isKindOfClass:[NSDictionary class]]); + + if ((self = [super init])) { + _tweak = tweak; + self.title = _tweak.name; + } + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + [self.view addSubview:_tableView]; +} + +- (void)dealloc +{ + _tableView.delegate = nil; + _tableView.dataSource = nil; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [_tweak.possibleValues count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + static NSString *_FBTweakDictionaryViewControllerCellIdentifier = @"_FBTweakDictionaryViewControllerCellIdentifier"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_FBTweakDictionaryViewControllerCellIdentifier]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:_FBTweakDictionaryViewControllerCellIdentifier]; + } + + NSArray *allKeys = [self allTweakKeys]; + FBTweakValue key = allKeys[indexPath.row]; + NSString *value = _tweak.possibleValues[key]; + cell.textLabel.text = value; + + cell.accessoryType = UITableViewCellAccessoryNone; + NSString *selectedKey = (_tweak.currentValue ?: _tweak.defaultValue); + if ([selectedKey isEqual:key]) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSArray *allKeys = [self allTweakKeys]; + NSString *key = allKeys[indexPath.row]; + + self.tweak.currentValue = key; + [self.navigationController popViewControllerAnimated:YES]; +} + +- (NSArray *)allTweakKeys +{ + // Sort by visible name. + __weak typeof(self) weakSelf = self; + return [[_tweak.possibleValues allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + id value1 = weakSelf.tweak.possibleValues[obj1]; + id value2 = weakSelf.tweak.possibleValues[obj2]; + return [value1 compare:value2]; + }]; +} + +@end \ No newline at end of file diff --git a/FBTweak/_FBTweakTableViewCell.m b/FBTweak/_FBTweakTableViewCell.m index c1216b22..b344999b 100644 --- a/FBTweak/_FBTweakTableViewCell.m +++ b/FBTweak/_FBTweakTableViewCell.m @@ -17,6 +17,8 @@ typedef NS_ENUM(NSUInteger, _FBTweakTableViewCellMode) { _FBTweakTableViewCellModeReal, _FBTweakTableViewCellModeString, _FBTweakTableViewCellModeAction, + _FBTweakTableViewCellModeDictionary, + _FBTweakTableViewCellModeArray, }; @interface _FBTweakTableViewCell () @@ -33,7 +35,7 @@ @implementation _FBTweakTableViewCell { - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier; { - if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier])) { + if ((self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier])) { _accessoryView = [[UIView alloc] init]; _switch = [[UISwitch alloc] init]; @@ -48,6 +50,8 @@ - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier; _stepper = [[UIStepper alloc] init]; [_stepper addTarget:self action:@selector(_stepperChanged:) forControlEvents:UIControlEventValueChanged]; [_accessoryView addSubview:_stepper]; + + self.detailTextLabel.textColor = [UIColor blackColor]; } return self; @@ -97,35 +101,39 @@ - (void)layoutSubviews - (void)setTweak:(FBTweak *)tweak { - if (_tweak != tweak) { - _tweak = tweak; - - self.textLabel.text = tweak.name; - - FBTweakValue value = (_tweak.currentValue ?: _tweak.defaultValue); - - _FBTweakTableViewCellMode mode = _FBTweakTableViewCellModeNone; - if ([value isKindOfClass:[NSString class]]) { - mode = _FBTweakTableViewCellModeString; - } else if ([value isKindOfClass:[NSNumber class]]) { - // In the 64-bit runtime, BOOL is a real boolean. - // NSNumber doesn't always agree; compare both. - if (strcmp([value objCType], @encode(char)) == 0 || - strcmp([value objCType], @encode(_Bool)) == 0) { - mode = _FBTweakTableViewCellModeBoolean; - } else if (strcmp([value objCType], @encode(NSInteger)) == 0 || - strcmp([value objCType], @encode(NSUInteger)) == 0) { - mode = _FBTweakTableViewCellModeInteger; - } else { - mode = _FBTweakTableViewCellModeReal; - } - } else if ([_tweak isAction]) { - mode = _FBTweakTableViewCellModeAction; + _tweak = tweak; + + self.textLabel.text = tweak.name; + + FBTweakValue value = (_tweak.currentValue ?: _tweak.defaultValue); + + _FBTweakTableViewCellMode mode = _FBTweakTableViewCellModeNone; + if ([tweak.possibleValues isKindOfClass:[NSDictionary class]]) { + mode = _FBTweakTableViewCellModeDictionary; + } else if ([tweak.possibleValues isKindOfClass:[NSArray class]]) { + mode = _FBTweakTableViewCellModeArray; + } else if ([value isKindOfClass:[NSString class]]) { + mode = _FBTweakTableViewCellModeString; + } else if ([value isKindOfClass:[NSNumber class]]) { + // In the 64-bit runtime, BOOL is a real boolean. + // NSNumber doesn't always agree; compare both. + if (strcmp([value objCType], @encode(char)) == 0 || + strcmp([value objCType], @encode(_Bool)) == 0) { + mode = _FBTweakTableViewCellModeBoolean; + } else if (strcmp([value objCType], @encode(NSInteger)) == 0 || + strcmp([value objCType], @encode(NSUInteger)) == 0 || + strcmp([value objCType], @encode(int)) == 0 || + strcmp([value objCType], @encode(long)) == 0) { + mode = _FBTweakTableViewCellModeInteger; + } else { + mode = _FBTweakTableViewCellModeReal; } - - [self _updateMode:mode]; - [self _updateValue:value primary:YES write:NO]; + } else if ([_tweak isAction]) { + mode = _FBTweakTableViewCellModeAction; } + + [self _updateMode:mode]; + [self _updateValue:value primary:YES write:NO]; } - (void)_updateMode:(_FBTweakTableViewCellMode)mode @@ -134,6 +142,7 @@ - (void)_updateMode:(_FBTweakTableViewCellMode)mode self.accessoryView = _accessoryView; self.accessoryType = UITableViewCellAccessoryNone; + self.detailTextLabel.text = nil; self.selectionStyle = UITableViewCellSelectionStyleNone; if (_mode == _FBTweakTableViewCellModeBoolean) { @@ -167,13 +176,13 @@ - (void)_updateMode:(_FBTweakTableViewCellMode)mode _textField.hidden = NO; _textField.keyboardType = UIKeyboardTypeDecimalPad; _stepper.hidden = NO; - + if (_tweak.stepValue) { _stepper.stepValue = [_tweak.stepValue floatValue]; } else { _stepper.stepValue = 1.0; } - + if (_tweak.minimumValue != nil) { _stepper.minimumValue = [_tweak.minimumValue doubleValue]; } else if ([_tweak.defaultValue doubleValue] == 0) { @@ -202,7 +211,21 @@ - (void)_updateMode:(_FBTweakTableViewCellMode)mode _switch.hidden = YES; _textField.hidden = YES; _stepper.hidden = YES; - + + self.accessoryView = nil; + self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + self.selectionStyle = UITableViewCellSelectionStyleBlue; + } else if (_mode == _FBTweakTableViewCellModeDictionary) { + _switch.hidden = YES; + _textField.hidden = YES; + _stepper.hidden = YES; + self.accessoryView = nil; + self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + self.selectionStyle = UITableViewCellSelectionStyleBlue; + } else if (_mode == _FBTweakTableViewCellModeArray) { + _switch.hidden = YES; + _textField.hidden = YES; + _stepper.hidden = YES; self.accessoryView = nil; self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; self.selectionStyle = UITableViewCellSelectionStyleBlue; @@ -300,9 +323,17 @@ - (void)_updateValue:(FBTweakValue)value primary:(BOOL)primary write:(BOOL)write if (_tweak.precisionValue) { precision = [[_tweak precisionValue] longValue]; } - + NSString *format = [NSString stringWithFormat:@"%%.%ldf", precision]; _textField.text = [NSString stringWithFormat:format, [value doubleValue]]; + } else if (_mode == _FBTweakTableViewCellModeDictionary) { + if (primary) { + self.detailTextLabel.text = _tweak.possibleValues[value]; + } + } else if (_mode == _FBTweakTableViewCellModeArray) { + if (primary) { + self.detailTextLabel.text = [value description]; + } } } diff --git a/FBTweakExample/FBAppDelegate.m b/FBTweakExample/FBAppDelegate.m index be1e6df2..1ae62f1b 100644 --- a/FBTweakExample/FBAppDelegate.m +++ b/FBTweakExample/FBAppDelegate.m @@ -22,6 +22,8 @@ @implementation FBAppDelegate { UIViewController *_rootViewController; UILabel *_label; + UIButton *_tweaksButton; + FBTweak *_buttonColorTweak; FBTweak *_flipTweak; } @@ -69,11 +71,11 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( CGRect tweaksButtonFrame = _window.bounds; tweaksButtonFrame.origin.y = _label.bounds.size.height; tweaksButtonFrame.size.height = tweaksButtonFrame.size.height - _label.bounds.size.height; - UIButton *tweaksButton = [[UIButton alloc] initWithFrame:tweaksButtonFrame]; - [tweaksButton setTitle:@"Show Tweaks" forState:UIControlStateNormal]; - [tweaksButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; - [tweaksButton addTarget:self action:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside]; - [_rootViewController.view addSubview:tweaksButton]; + _tweaksButton = [[UIButton alloc] initWithFrame:tweaksButtonFrame]; + [_tweaksButton setTitle:@"Show Tweaks" forState:UIControlStateNormal]; + [_tweaksButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [_tweaksButton addTarget:self action:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside]; + [_rootViewController.view addSubview:_tweaksButton]; FBTweak *animationDurationTweak = FBTweakInline(@"Content", @"Animation", @"Duration", 0.5); animationDurationTweak.stepValue = [NSNumber numberWithFloat:0.005f]; @@ -85,6 +87,23 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [alert show]; }); + typedef NS_ENUM(NSUInteger, FBColor) { + FBBlackColor, + FBBlueColor, + FBGreenColor, + }; + + NSNumber *colorIndex = FBTweakValue(@"Content", @"Tweaks Button", @"Color", @(FBBlackColor), (@{ + @(FBBlackColor) : @"Black", + @(FBBlueColor) : @"Blue", + @(FBGreenColor) : @"Green", + })); + UIColor *color = (colorIndex.integerValue == FBBlackColor ? [UIColor blackColor] : colorIndex.integerValue == FBBlueColor ? [UIColor blueColor] : [UIColor greenColor]); + [_tweaksButton setTitleColor:color forState:UIControlStateNormal]; + + NSNumber *rotation = FBTweakValue(@"Content", @"Text", @"Rotation (radians)", @(0), (@[@(0), @(M_PI_4), @(M_PI_2)])); + _label.transform = CGAffineTransformRotate(CGAffineTransformIdentity, [rotation floatValue]); + return YES; } diff --git a/FBTweakTests/FBTweakInlineTestsARC.m b/FBTweakTests/FBTweakInlineTestsARC.m index 3cd44f7c..839848da 100644 --- a/FBTweakTests/FBTweakInlineTestsARC.m +++ b/FBTweakTests/FBTweakInlineTestsARC.m @@ -15,6 +15,25 @@ #error ARC is required. #endif +typedef NS_ENUM(unsigned long, UnsignedLongEnum) { + UnsignedLongEnumOff, + UnsignedLongEnumVerbose, + UnsignedLongEnumInfo, + UnsignedLongEnumWarn, + UnsignedLongEnumError, +}; + +@interface FBTweakTestObject : NSObject + +@property (nonatomic, assign, readwrite) UnsignedLongEnum unsignedLongProperty; + +@end + +@implementation FBTweakTestObject + +@end + + @interface FBTweakInlineTestsARC : XCTestCase @end @@ -40,6 +59,12 @@ - (void)testValueTypes __attribute__((unused)) unsigned int testUnsignedInt = FBTweakValue(@"Unsigned Int", @"Unsigned Int", @"Unsigned Int", 1); XCTAssertEqual(testUnsignedInt, (unsigned int)1, @"Unsigned Int %d", testUnsignedInt); + __attribute__((unused)) long testLong = FBTweakValue(@"Long", @"Long", @"Long", -1); + XCTAssertEqual(testLong, (long)-1, @"Long %ld", testLong); + + __attribute__((unused)) unsigned long testUnsignedLong = FBTweakValue(@"Unsigned Long", @"Unsigned Long", @"Unsigned Long", 1); + XCTAssertEqual(testUnsignedLong, (unsigned long)1, @"Unsigned Long %lu", testUnsignedLong); + __attribute__((unused)) long long testLongLong = FBTweakValue(@"Long Long", @"Long Long", @"Long Long", -1); XCTAssertEqual(testLongLong, (long long)-1, @"Long Long %lld", testLongLong); @@ -57,14 +82,16 @@ - (void)testValueTypes __attribute__((unused)) NSString *testNSString = FBTweakValue(@"NSString", @"NSString", @"NSString", @"one"); XCTAssertEqualObjects(testNSString, @"one", @"NSString %@", testNSString); + + __attribute__((unused)) NSString *testNSArray = FBTweakValue(@"NSArray", @"NSArray", @"NSArray", @"two", (@[@"one", @"two", @"three"])); + XCTAssertEqualObjects(testNSArray, @"two", @"NSArray %@", testNSArray); + + __attribute__((unused)) NSString *testNSDictionary = FBTweakValue(@"NSDictionary", @"NSDictionary", @"NSDictionary", @"key2", (@{@"key1":@"value1", @"key2":@"value2"})); + XCTAssertEqualObjects(testNSDictionary, @"key2", @"NSString %@", testNSDictionary); } - (void)testConstantValues { - const double constInput = 1.0; - double constValue = FBTweakValue(@"Const", @"Const", @"Const", constInput); - XCTAssertEqual(constValue, constInput, @"Const %f %f", constInput, constValue); - static const double staticConstInput = 1.0; double staticConstValue = FBTweakValue(@"Static", @"Static", @"Static", staticConstInput); XCTAssertEqual(staticConstValue, staticConstInput, @"Static %f %f", staticConstInput, staticConstValue); @@ -111,6 +138,14 @@ - (void)testBind FBTweak *m = FBTweakInline(@"URL", @"Request", @"Bind", 5.0); m.currentValue = @(20.0); XCTAssertEqual(v.timeoutInterval, (NSTimeInterval)20.0, @"request %@ %@", v, m); + + FBTweakTestObject *o = [FBTweakTestObject new]; + FBTweakBind(o, unsignedLongProperty, @"Test", @"Object", @"Long", UnsignedLongEnumInfo, UnsignedLongEnumOff, UnsignedLongEnumError); + XCTAssertEqual(o.unsignedLongProperty, UnsignedLongEnumInfo, @"test object: %@", @(o.unsignedLongProperty)); + + FBTweak *oTweak = FBTweakInline(@"Test", @"Object", @"Long", UnsignedLongEnumInfo); + oTweak.currentValue = @(UnsignedLongEnumWarn); + XCTAssertEqual(o.unsignedLongProperty, UnsignedLongEnumWarn, @"test object: %@", @(o.unsignedLongProperty)); } @end diff --git a/FBTweakTests/FBTweakInlineTestsMRR.m b/FBTweakTests/FBTweakInlineTestsMRR.m index 807bb2a6..2c6d5a7c 100644 --- a/FBTweakTests/FBTweakInlineTestsMRR.m +++ b/FBTweakTests/FBTweakInlineTestsMRR.m @@ -57,14 +57,16 @@ - (void)testValueTypes __attribute__((unused)) NSString *testNSString = FBTweakValue(@"NSString", @"NSString", @"NSString", @"one"); XCTAssertEqualObjects(testNSString, @"one", @"NSString %@", testNSString); + + __attribute__((unused)) NSString *testNSArray = FBTweakValue(@"NSArray", @"NSArray", @"NSArray", @"two", (@[@"one", @"two", @"three"])); + XCTAssertEqualObjects(testNSArray, @"two", @"NSArray %@", testNSArray); + + __attribute__((unused)) NSString *testNSDictionary = FBTweakValue(@"NSDictionary", @"NSDictionary", @"NSDictionary", @"key2", (@{@"key1":@"value1", @"key2":@"value2"})); + XCTAssertEqualObjects(testNSDictionary, @"key2", @"NSString %@", testNSDictionary); } - (void)testConstantValues { - const double constInput = 1.0; - double constValue = FBTweakValue(@"Const", @"Const", @"Const", constInput); - XCTAssertEqual(constValue, constInput, @"Const %f %f", constInput, constValue); - static const double staticConstInput = 1.0; double staticConstValue = FBTweakValue(@"Static", @"Static", @"Static", staticConstInput); XCTAssertEqual(staticConstValue, staticConstInput, @"Static %f %f", staticConstInput, staticConstValue); diff --git a/PATENTS b/PATENTS index 2623c557..3f2b05f5 100644 --- a/PATENTS +++ b/PATENTS @@ -1,23 +1,33 @@ -Additional Grant of Patent Rights +Additional Grant of Patent Rights Version 2 "Software" means the Tweaks software distributed by Facebook, Inc. -Facebook hereby grants you a perpetual, worldwide, royalty-free, non-exclusive, -irrevocable (subject to the termination provision below) license under any -rights in any patent claims owned by Facebook, to make, have made, use, sell, -offer to sell, import, and otherwise transfer the Software. For avoidance of -doubt, no license is granted under Facebook’s rights in any patent claims that -are infringed by (i) modifications to the Software made by you or a third party, -or (ii) the Software in combination with any software or other technology -provided by you or a third party. +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook’s rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. The license granted hereunder will terminate, automatically and without notice, -for anyone that makes any claim (including by filing any lawsuit, assertion or -other action) alleging (a) direct, indirect, or contributory infringement or -inducement to infringe any patent: (i) by Facebook or any of its subsidiaries or -affiliates, whether or not such claim is related to the Software, (ii) by any -party if such claim arises in whole or in part from any software, product or -service of Facebook or any of its subsidiaries or affiliates, whether or not -such claim is related to the Software, or (iii) by any party relating to the -Software; or (b) that any right in any patent claim of Facebook is invalid or -unenforceable. +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/README.md b/README.md index 1424d8ac..520ed1a5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Tweaks Tweaks is an easy way to fine-tune an iOS app. +[![Build Status](https://travis-ci.org/facebook/Tweaks.svg?branch=master)](https://travis-ci.org/facebook/Tweaks) ![Tweaks](https://github.com/facebook/Tweaks/blob/master/Images/Tweaks.gif?raw=true) @@ -22,7 +23,7 @@ The simplest way to create a tweak is to replace a constant with `FBTweakValue`: CGFloat animationDuration = FBTweakValue(@"Category", @"Group", @"Duration", 0.5); ``` -The first three parameters are where the tweak is listed and what it's called, and the last one is the default value. You can pass in many types of constants for the default: +The first three parameters are where the tweak is listed and what it's called, and the last one is the default value. You can pass in many types of values for the default: booleans, numbers, or strings. ```objective-c if (FBTweakValue(@"Category", @"Feature", @"Enabled", YES)) { @@ -32,7 +33,13 @@ if (FBTweakValue(@"Category", @"Feature", @"Enabled", YES)) { In release builds, the `FBTweakValue` macro expands to just the default value, so there's no performance impact. In debug builds, though, it fetches the latest value of the tweak. -For numeric tweaks (`NSInteger`, `CGFloat`, and others), you can pass an extra two parameters which are used as the minimum and maximum value for the tweak: +You can also pass a fifth parameter, which will constrain the possible values for a tweak. The fifth parameter can be an array, dictionary, or an `FBTweakNumericRange`. If it's a dictionary, the values should be strings to show in the list of choices. Arrays will show the values' `description` as choices. (Note that you have to surround array and dictionary literals with an extra set of parentheses.) + +```objective-c +self.initialMode = FBTweakValue(@"Header", @"Initial", @"Mode", @(FBSimpleMode), (@{ @(FBSimpleMode) : @"Simple", @(FBAdvancedMode) : @"Advanced" })); +``` + +For numeric tweaks (`NSInteger`, `CGFloat`, and others), you can instead pass two parameters, which constrain the value to a `FBTweakNumericRange`: ```objective-c self.red = FBTweakValue(@"Header", @"Colors", @"Red", 0.5, 0.0, 1.0); @@ -138,4 +145,3 @@ See the CONTRIBUTING file for how to help out. ## License Tweaks is BSD-licensed. We also provide an additional patent grant. - diff --git a/Tweaks.podspec b/Tweaks.podspec index 799a602e..e46c9db2 100644 --- a/Tweaks.podspec +++ b/Tweaks.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |spec| spec.name = 'Tweaks' - spec.version = '1.1.0' + spec.version = '2.0.0' spec.license = { :type => 'BSD' } spec.homepage = 'https://github.com/facebook/Tweaks' spec.authors = { 'Grant Paul' => 'tweaks@grantpaul.com', 'Kimon Tsinteris' => 'kimon@mac.com' } spec.summary = 'Easily adjust parameters for iOS apps in development.' - spec.source = { :git => 'https://github.com/facebook/Tweaks.git', :tag => '1.1.0' } + spec.source = { :git => 'https://github.com/facebook/Tweaks.git', :tag => '2.0.0' } spec.source_files = 'FBTweak/*.{h,m}' spec.requires_arc = true spec.social_media_url = 'https://twitter.com/fbOpenSource'