diff --git a/FBTweak.xcodeproj/project.pbxproj b/FBTweak.xcodeproj/project.pbxproj index ca06fb00..1c9b5d9b 100644 --- a/FBTweak.xcodeproj/project.pbxproj +++ b/FBTweak.xcodeproj/project.pbxproj @@ -44,6 +44,25 @@ 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 */; }; + 5E1708C91905B89800402135 /* _FBSliderView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E1708C31905B89800402135 /* _FBSliderView.h */; }; + 5E1708CA1905B89800402135 /* _FBSliderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E1708C41905B89800402135 /* _FBSliderView.m */; }; + 5E1708DD190B147000402135 /* _FBKeyboardManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E1708DB190B147000402135 /* _FBKeyboardManager.h */; }; + 5E1708DE190B147000402135 /* _FBKeyboardManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E1708DC190B147000402135 /* _FBKeyboardManager.m */; }; + 5E1F48EB1901E4D500D7C4A2 /* _FBColorUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E1F48E91901E4D500D7C4A2 /* _FBColorUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5E1F48EC1901E4D500D7C4A2 /* _FBColorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E1F48EA1901E4D500D7C4A2 /* _FBColorUtils.m */; }; + 5E1F48ED1901E80800D7C4A2 /* _FBColorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E1F48EA1901E4D500D7C4A2 /* _FBColorUtils.m */; }; + 5E66FDCD1B80C309007464F3 /* _FBColorComponentCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E66FDCB1B80C309007464F3 /* _FBColorComponentCell.h */; }; + 5E66FDCE1B80C309007464F3 /* _FBColorComponentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E66FDCC1B80C309007464F3 /* _FBColorComponentCell.m */; }; + 5E66FDD21B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E66FDD01B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.h */; }; + 5E66FDD31B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E66FDD11B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.m */; }; + 5E66FDD51B80EB6D007464F3 /* _FBTweakColorViewControllerDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E66FDD41B80EB6D007464F3 /* _FBTweakColorViewControllerDataSource.h */; }; + 5E66FDD81B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E66FDD61B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.h */; }; + 5E66FDD91B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E66FDD71B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.m */; }; + 5E66FDDC1B80FA78007464F3 /* _FBColorWheelCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E66FDDA1B80FA78007464F3 /* _FBColorWheelCell.h */; }; + 5E66FDDD1B80FA78007464F3 /* _FBColorWheelCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E66FDDB1B80FA78007464F3 /* _FBColorWheelCell.m */; }; + 5EA9A2E91911968D0071AB23 /* _FBTweakColorViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5EA9A2E71911968D0071AB23 /* _FBTweakColorViewController.h */; }; + 5EA9A2EA1911968D0071AB23 /* _FBTweakColorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EA9A2E81911968D0071AB23 /* _FBTweakColorViewController.m */; }; + 5EB0EA4018F5EFF3009481A6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EB0EA3F18F5EFF3009481A6 /* CoreGraphics.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -84,7 +103,7 @@ 18EFE4D3189EEBC500DA6A5D /* _FBTweakCategoryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakCategoryViewController.h; sourceTree = ""; }; 18EFE4D4189EEBC500DA6A5D /* _FBTweakCategoryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakCategoryViewController.m; sourceTree = ""; }; 18EFE4D7189EEED800DA6A5D /* _FBTweakCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakCollectionViewController.h; sourceTree = ""; }; - 18EFE4D8189EEED800DA6A5D /* _FBTweakCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakCollectionViewController.m; sourceTree = ""; }; + 18EFE4D8189EEED800DA6A5D /* _FBTweakCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _FBTweakCollectionViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 18EFE4DB189EF75800DA6A5D /* _FBTweakTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakTableViewCell.h; sourceTree = ""; }; 18EFE4DC189EF75800DA6A5D /* _FBTweakTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakTableViewCell.m; sourceTree = ""; }; 18EFE525189F19B300DA6A5D /* FBTweakCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBTweakCategory.h; sourceTree = ""; }; @@ -96,6 +115,24 @@ 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; }; + 5E1708C31905B89800402135 /* _FBSliderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = _FBSliderView.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 5E1708C41905B89800402135 /* _FBSliderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _FBSliderView.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 5E1708DB190B147000402135 /* _FBKeyboardManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBKeyboardManager.h; sourceTree = ""; }; + 5E1708DC190B147000402135 /* _FBKeyboardManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _FBKeyboardManager.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 5E1F48E91901E4D500D7C4A2 /* _FBColorUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = _FBColorUtils.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 5E1F48EA1901E4D500D7C4A2 /* _FBColorUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _FBColorUtils.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 5E66FDCB1B80C309007464F3 /* _FBColorComponentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = _FBColorComponentCell.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 5E66FDCC1B80C309007464F3 /* _FBColorComponentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _FBColorComponentCell.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 5E66FDD01B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakColorViewControllerRGBDataSource.h; sourceTree = ""; }; + 5E66FDD11B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakColorViewControllerRGBDataSource.m; sourceTree = ""; }; + 5E66FDD41B80EB6D007464F3 /* _FBTweakColorViewControllerDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = _FBTweakColorViewControllerDataSource.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 5E66FDD61B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakColorViewControllerHSBDataSource.h; sourceTree = ""; }; + 5E66FDD71B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _FBTweakColorViewControllerHSBDataSource.m; sourceTree = ""; }; + 5E66FDDA1B80FA78007464F3 /* _FBColorWheelCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBColorWheelCell.h; sourceTree = ""; }; + 5E66FDDB1B80FA78007464F3 /* _FBColorWheelCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _FBColorWheelCell.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 5EA9A2E71911968D0071AB23 /* _FBTweakColorViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _FBTweakColorViewController.h; sourceTree = ""; }; + 5EA9A2E81911968D0071AB23 /* _FBTweakColorViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = _FBTweakColorViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 5EB0EA3F18F5EFF3009481A6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -103,6 +140,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5EB0EA4018F5EFF3009481A6 /* CoreGraphics.framework in Frameworks */, 29E9F17B18E35C9C001EAF7D /* MessageUI.framework in Frameworks */, 18EFE472189EBA4900DA6A5D /* Foundation.framework in Frameworks */, ); @@ -144,6 +182,7 @@ 18EFE470189EBA4900DA6A5D /* Frameworks */ = { isa = PBXGroup; children = ( + 5EB0EA3F18F5EFF3009481A6 /* CoreGraphics.framework */, 29E9F17A18E35C9C001EAF7D /* MessageUI.framework */, 18EFE471189EBA4900DA6A5D /* Foundation.framework */, 18EFE47F189EBA4900DA6A5D /* XCTest.framework */, @@ -159,6 +198,7 @@ 18EFE532189F286000DA6A5D /* Inline */, 18EFE4B9189EBC3500DA6A5D /* Model */, 18EFE4BE189EBD0800DA6A5D /* UI */, + 5E8F532B1B83CA760002F73F /* Utils */, 18EFE474189EBA4900DA6A5D /* Supporting Files */, ); path = FBTweak; @@ -223,6 +263,21 @@ 5BE25A511A0AA9D500C97C3E /* _FBTweakDictionaryViewController.m */, 5BACB31C1A12813C00C4F79D /* _FBTweakArrayViewController.h */, 5BACB31D1A12813C00C4F79D /* _FBTweakArrayViewController.m */, + 5EA9A2E71911968D0071AB23 /* _FBTweakColorViewController.h */, + 5EA9A2E81911968D0071AB23 /* _FBTweakColorViewController.m */, + 5E66FDD41B80EB6D007464F3 /* _FBTweakColorViewControllerDataSource.h */, + 5E66FDD01B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.h */, + 5E66FDD11B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.m */, + 5E66FDD61B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.h */, + 5E66FDD71B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.m */, + 5E66FDCB1B80C309007464F3 /* _FBColorComponentCell.h */, + 5E66FDCC1B80C309007464F3 /* _FBColorComponentCell.m */, + 5E66FDDA1B80FA78007464F3 /* _FBColorWheelCell.h */, + 5E66FDDB1B80FA78007464F3 /* _FBColorWheelCell.m */, + 5E1708C31905B89800402135 /* _FBSliderView.h */, + 5E1708C41905B89800402135 /* _FBSliderView.m */, + 5E1708DB190B147000402135 /* _FBKeyboardManager.h */, + 5E1708DC190B147000402135 /* _FBKeyboardManager.m */, ); name = UI; sourceTree = ""; @@ -239,6 +294,15 @@ name = Inline; sourceTree = ""; }; + 5E8F532B1B83CA760002F73F /* Utils */ = { + isa = PBXGroup; + children = ( + 5E1F48E91901E4D500D7C4A2 /* _FBColorUtils.h */, + 5E1F48EA1901E4D500D7C4A2 /* _FBColorUtils.m */, + ); + name = Utils; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -247,19 +311,28 @@ buildActionMask = 2147483647; files = ( 18EFE4B7189EBC3100DA6A5D /* FBTweak.h in Headers */, + 5E1708DD190B147000402135 /* _FBKeyboardManager.h in Headers */, + 5E66FDD81B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.h in Headers */, 18EFE4D5189EEBC500DA6A5D /* _FBTweakCategoryViewController.h in Headers */, 18EFE4D9189EEED800DA6A5D /* _FBTweakCollectionViewController.h in Headers */, 18EFE4B4189EBAC200DA6A5D /* FBTweakViewController.h in Headers */, 18EFE4C1189EBEAD00DA6A5D /* FBTweakStore.h in Headers */, + 5E66FDD51B80EB6D007464F3 /* _FBTweakColorViewControllerDataSource.h in Headers */, 18EFE4B2189EBAC200DA6A5D /* FBTweakInline.h in Headers */, + 5EA9A2E91911968D0071AB23 /* _FBTweakColorViewController.h in Headers */, 18EFE4B3189EBAC200DA6A5D /* FBTweakShakeWindow.h in Headers */, + 5E1F48EB1901E4D500D7C4A2 /* _FBColorUtils.h in Headers */, 18EFE536189F38D500DA6A5D /* FBTweakEnabled.h in Headers */, + 5E66FDCD1B80C309007464F3 /* _FBColorComponentCell.h in Headers */, 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 */, + 5E66FDDC1B80FA78007464F3 /* _FBColorWheelCell.h in Headers */, + 5E1708C91905B89800402135 /* _FBSliderView.h in Headers */, + 5E66FDD21B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.h in Headers */, 5BACB31E1A12813C00C4F79D /* _FBTweakArrayViewController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -345,18 +418,26 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5E1708CA1905B89800402135 /* _FBSliderView.m in Sources */, 5BE25A531A0AA9D500C97C3E /* _FBTweakDictionaryViewController.m in Sources */, 18EFE4A7189EBA9E00DA6A5D /* FBTweakViewController.m in Sources */, 18EFE4BD189EBC4B00DA6A5D /* FBTweakCollection.m in Sources */, 18EFE4C2189EBEAD00DA6A5D /* FBTweakStore.m in Sources */, + 5E66FDD91B80ECDA007464F3 /* _FBTweakColorViewControllerHSBDataSource.m in Sources */, 18EFE4AB189EBAAD00DA6A5D /* FBTweakInline.m in Sources */, 18EFE4B8189EBC3100DA6A5D /* FBTweak.m in Sources */, 18EFE4DE189EF75800DA6A5D /* _FBTweakTableViewCell.m in Sources */, 18EFE528189F19B300DA6A5D /* FBTweakCategory.m in Sources */, + 5E1F48EC1901E4D500D7C4A2 /* _FBColorUtils.m in Sources */, + 5E1708DE190B147000402135 /* _FBKeyboardManager.m in Sources */, + 5E66FDDD1B80FA78007464F3 /* _FBColorWheelCell.m in Sources */, 18EFE4D6189EEBC500DA6A5D /* _FBTweakCategoryViewController.m in Sources */, 18EFE4AF189EBABA00DA6A5D /* FBTweakShakeWindow.m in Sources */, + 5E66FDCE1B80C309007464F3 /* _FBColorComponentCell.m in Sources */, 5BACB31F1A12813C00C4F79D /* _FBTweakArrayViewController.m in Sources */, 18EFE4DA189EEED800DA6A5D /* _FBTweakCollectionViewController.m in Sources */, + 5EA9A2EA1911968D0071AB23 /* _FBTweakColorViewController.m in Sources */, + 5E66FDD31B80E4C1007464F3 /* _FBTweakColorViewControllerRGBDataSource.m in Sources */, 184A94F118D26871005F2774 /* _FBTweakBindObserver.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -366,6 +447,7 @@ buildActionMask = 2147483647; files = ( 18EFE52D189F250700DA6A5D /* FBTweakInlineTestsMRR.m in Sources */, + 5E1F48ED1901E80800D7C4A2 /* _FBColorUtils.m in Sources */, 18EFE4D2189ECF2000DA6A5D /* FBTweakInlineTestsARC.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/FBTweak/FBTweak.m b/FBTweak/FBTweak.m index a0671e50..c774d81d 100644 --- a/FBTweak/FBTweak.m +++ b/FBTweak/FBTweak.m @@ -76,7 +76,8 @@ - (instancetype)initWithIdentifier:(NSString *)identifier { if ((self = [super init])) { _identifier = identifier; - _currentValue = [[NSUserDefaults standardUserDefaults] objectForKey:_identifier]; + NSData *archivedValue = [[NSUserDefaults standardUserDefaults] objectForKey:_identifier]; + _currentValue = (archivedValue == nil ? archivedValue : [NSKeyedUnarchiver unarchiveObjectWithData:archivedValue]); } return self; @@ -176,8 +177,9 @@ - (void)setCurrentValue:(FBTweakValue)currentValue if (_currentValue != currentValue) { _currentValue = currentValue; - [[NSUserDefaults standardUserDefaults] setObject:_currentValue forKey:_identifier]; - + // we can't store UIColor to the plist file. That is why we archive value to the NSData. + [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_currentValue] forKey:_identifier]; + for (id observer in [_observers setRepresentation]) { [observer tweakDidChange:self]; } diff --git a/FBTweak/FBTweakInline.m b/FBTweak/FBTweakInline.m index 4234b749..bdf3fe9c 100644 --- a/FBTweak/FBTweakInline.m +++ b/FBTweak/FBTweakInline.m @@ -14,6 +14,7 @@ #import "FBTweakStore.h" #import "FBTweakCategory.h" +#import #import #import #import diff --git a/FBTweak/FBTweakInlineInternal.h b/FBTweak/FBTweakInlineInternal.h index b79ce42c..289e6ef2 100644 --- a/FBTweak/FBTweakInlineInternal.h +++ b/FBTweak/FBTweakInlineInternal.h @@ -48,7 +48,7 @@ typedef struct { #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) #define _FBTweakRelease(x) #else diff --git a/FBTweak/FBTweakShakeWindow.m b/FBTweak/FBTweakShakeWindow.m index 77c3b103..d3515b33 100644 --- a/FBTweak/FBTweakShakeWindow.m +++ b/FBTweak/FBTweakShakeWindow.m @@ -11,6 +11,7 @@ #import "FBTweakStore.h" #import "FBTweakShakeWindow.h" #import "FBTweakViewController.h" +#import "_FBKeyboardManager.h" // Minimum shake time required to present tweaks on device. static CFTimeInterval _FBTweakShakeWindowMinTimeInterval = 0.4; diff --git a/FBTweak/_FBColorComponentCell.h b/FBTweak/_FBColorComponentCell.h new file mode 100644 index 00000000..93e4f5ed --- /dev/null +++ b/FBTweak/_FBColorComponentCell.h @@ -0,0 +1,46 @@ +/** + 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 _FBColorComponentCell; + +@protocol _FBColorComponentCellDelegate + +- (void)colorComponentCell:(_FBColorComponentCell*)cell didChangeValue:(CGFloat)value; + +@end + +/** + @abstract A cell to edit a color component. + */ +@interface _FBColorComponentCell : UITableViewCell + +//! @abstract The title. +@property(nonatomic, copy) NSString *title; + +//! @abstract The current value. The default value is 0.0. +@property(nonatomic, assign) CGFloat value; + +//! @abstract The minimum value. The default value is 0.0. +@property(nonatomic, assign) CGFloat minimumValue; + +//! @abstract The maximum value. The default value is 255.0. +@property(nonatomic, assign) CGFloat maximumValue; + +//! @abstract The format string to apply for textfield value. `%.f` by default. +@property(nonatomic, copy) NSString *format; + +//! @abstract The array of CGColorRef objects defining the color of each gradient stop on the track. +@property(nonatomic, copy) NSArray *colors; + +//! @abstract The cell's delegate. +@property(nonatomic, weak) id<_FBColorComponentCellDelegate> delegate; + +@end diff --git a/FBTweak/_FBColorComponentCell.m b/FBTweak/_FBColorComponentCell.m new file mode 100644 index 00000000..82fc1c74 --- /dev/null +++ b/FBTweak/_FBColorComponentCell.m @@ -0,0 +1,166 @@ +/** + 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 "_FBColorComponentCell.h" +#import "_FBSliderView.h" + +extern CGFloat const _FBRGBColorComponentMaxValue; +static CGFloat const _FBColorComponentMargin = 5.0f; +static CGFloat const _FBColorComponentTextSpacing = 10.0f; +static CGFloat const _FBColorComponentTextFieldWidth = 50.0f; + +@interface _FBColorComponentCell () + +@end + +@implementation _FBColorComponentCell { + UILabel *_label; + _FBSliderView *_slider; + UITextField *_textField; +} + +- (instancetype)init +{ + if ((self = [super init])) { + [self _init]; + } + + return self; +} + +- (void)setTitle:(NSString *)title +{ + _label.text = title; +} + +- (void)setMinimumValue:(CGFloat)minimumValue +{ + _slider.minimumValue = minimumValue; +} + +- (void)setMaximumValue:(CGFloat)maximumValue +{ + _slider.maximumValue = maximumValue; +} + +- (void)setValue:(CGFloat)value +{ + _slider.value = value; + _textField.text = [NSString stringWithFormat:_format, value]; +} + +- (NSString*)title +{ + return _label.text; +} + +- (CGFloat)minimumValue +{ + return _slider.minimumValue; +} + +- (CGFloat)maximumValue +{ + return _slider.maximumValue; +} + +- (CGFloat)value +{ + return _slider.value; +} + +- (void)setColors:(NSArray *)colors +{ + _slider.colors = colors; +} + +- (NSArray *)colors +{ + return _slider.colors; +} + +#pragma mark - UITextFieldDelegate methods + +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + [self setValue:[textField.text floatValue]]; + [self.delegate colorComponentCell:self didChangeValue:self.value]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField +{ + [textField resignFirstResponder]; + return YES; +} + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string +{ + NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string]; + + //first, check if the new string is numeric only. If not, return NO; + if ([newString rangeOfCharacterFromSet:[self _invertedDecimalDigitAndDotCharacterSet]].location != NSNotFound) { + return NO; + } + + return [newString floatValue] <= _slider.maximumValue; +} + +-(void)layoutSubviews +{ + [super layoutSubviews]; + + CGFloat cellHeight = CGRectGetHeight(self.bounds); + CGFloat cellWidth = CGRectGetWidth(self.bounds); + CGFloat labelWidth = [_label sizeThatFits:CGSizeZero].width; + _label.frame = CGRectIntegral((CGRect){_FBColorComponentMargin, _FBColorComponentMargin, labelWidth, cellHeight - 2 * _FBColorComponentMargin}); + _textField.frame = CGRectIntegral((CGRect){cellWidth - _FBColorComponentMargin - _FBColorComponentTextFieldWidth, _FBColorComponentMargin, _FBColorComponentTextFieldWidth, cellHeight - 2 * _FBColorComponentMargin}); + CGFloat sliderWidth = CGRectGetMinX(_textField.frame) - CGRectGetMaxX(_label.frame) - 2 * _FBColorComponentTextSpacing; + _slider.frame = CGRectIntegral((CGRect){CGRectGetMaxX(_label.frame) + _FBColorComponentTextSpacing, _FBColorComponentMargin, sliderWidth, cellHeight - 2 * _FBColorComponentMargin}); +} + +#pragma mark - Private methods + +- (void)_init +{ + self.selectionStyle = UITableViewCellSelectionStyleNone; + + _format = @"%.f"; + + _label = [[UILabel alloc] init]; + _label.adjustsFontSizeToFitWidth = YES; + [self.contentView addSubview:_label]; + + _slider = [[_FBSliderView alloc] init]; + _slider.maximumValue = _FBRGBColorComponentMaxValue; + [self.contentView addSubview:_slider]; + + _textField = [[UITextField alloc] init]; + _textField.textAlignment = NSTextAlignmentCenter; + [_textField setKeyboardType:UIKeyboardTypeNumbersAndPunctuation]; + [self.contentView addSubview:_textField]; + + [self setValue:0.0f]; + [_slider addTarget:self action:@selector(_didChangeSliderValue:) forControlEvents:UIControlEventValueChanged]; + [_textField setDelegate:self]; +} + +- (void)_didChangeSliderValue:(_FBSliderView*)sender +{ + [self setValue:sender.value]; + [self.delegate colorComponentCell:self didChangeValue:self.value]; +} + +- (NSCharacterSet*)_invertedDecimalDigitAndDotCharacterSet +{ + NSMutableCharacterSet *characterSet = [NSMutableCharacterSet decimalDigitCharacterSet]; + [characterSet addCharactersInString:@"."]; + return [[characterSet invertedSet] copy]; +} + +@end diff --git a/FBTweak/_FBColorUtils.h b/FBTweak/_FBColorUtils.h new file mode 100644 index 00000000..94712989 --- /dev/null +++ b/FBTweak/_FBColorUtils.h @@ -0,0 +1,93 @@ +/** + 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 + +typedef struct { CGFloat red, green, blue, alpha; } RGB; +typedef struct { CGFloat hue, saturation, brightness, alpha; } HSB; + +extern CGFloat const _FBRGBColorComponentMaxValue; +extern CGFloat const _FBAlphaComponentMaxValue; +extern CGFloat const _FBHSBColorComponentMaxValue; +extern NSUInteger const _FBRGBAColorComponentsSize; +extern NSUInteger const _FBHSBAColorComponentsSize; + +typedef NS_ENUM(NSUInteger, _FBRGBColorComponent) { + _FBRGBColorComponentRed, + _FBRGBColorComponentGreed, + _FBRGBColorComponentBlue, + _FBRGBColorComponentAlpha +}; + +typedef NS_ENUM(NSUInteger, _FBHSBColorComponent) { + _FBHSBColorComponentHue, + _FBHSBColorComponentSaturation, + _FBHSBColorComponentBrightness, + _FBHSBColorComponentAlpha +}; + +/** + * Converts an RGB color value to HSV. + * Assumes r, g, and b are contained in the set [0, 1] and + * returns h, s, and b in the set [0, 1]. + * + * @param rgb The rgb color values + * @return The hsb color values + */ +extern HSB _FBRGB2HSB(RGB rgb); + +/** + * Converts an HSB color value to RGB. + * Assumes h, s, and b are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param hsb The hsb color values + * @return The rgb color values + */ +extern RGB _FBHSB2RGB(HSB hsb); + +/** + * Returns the rgb values of the color components. + * + * @param color The color value. + * + * @return The values of the color components (including alpha). + */ +extern RGB _FBRGBColorComponents(UIColor *color); + +/** + * Returns the color wheel's hue value according to the position, color wheel's center and radius. + * + * @param position The position in the color wheel. + * @param center The color wheel's center. + * @param radius The color wheel's radius. + * + * @return The hue value. + */ +extern CGFloat _FBGetColorWheelHue(CGPoint position, CGPoint center, CGFloat radius); + +/** + * Returns the color wheel's saturation value according to the position, color wheel's center and radius. + * + * @param position The position in the color wheel. + * @param center The color wheel's center. + * @param radius The color wheel's radius. + * + * @return The saturation value. + */ +extern CGFloat _FBGetColorWheelSaturation(CGPoint position, CGPoint center, CGFloat radius); + +/** + * Creates the color wheel with specified diameter. + * + * @param diameter The color wheel's diameter. + * + * @return The color wheel image. + */ +extern CGImageRef _FBCreateColorWheelImage(CGFloat diameter); diff --git a/FBTweak/_FBColorUtils.m b/FBTweak/_FBColorUtils.m new file mode 100644 index 00000000..4351f221 --- /dev/null +++ b/FBTweak/_FBColorUtils.m @@ -0,0 +1,141 @@ +/** + 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 "_FBColorUtils.h" + +CGFloat const _FBRGBColorComponentMaxValue = 255.0f; +CGFloat const _FBAlphaComponentMaxValue = 100.0f; +CGFloat const _FBHSBColorComponentMaxValue = 1.0f; +NSUInteger const _FBRGBAColorComponentsSize = 4; +NSUInteger const _FBHSBAColorComponentsSize = 4; + +extern HSB _FBRGB2HSB(RGB rgb) +{ + double rd = (double) rgb.red; + double gd = (double) rgb.green; + double bd = (double) rgb.blue; + double max = fmax(rd, fmax(gd, bd)); + double min = fmin(rd, fmin(gd, bd)); + double h = 0, s, b = max; + + double d = max - min; + s = max == 0 ? 0 : d / max; + + if (max == min) { + h = 0; // achromatic + } else { + if (max == rd) { + h = (gd - bd) / d + (gd < bd ? 6 : 0); + } else if (max == gd) { + h = (bd - rd) / d + 2; + } else if (max == bd) { + h = (rd - gd) / d + 4; + } + h /= 6; + } + + return (HSB){.hue = h, .saturation = s, .brightness = b, .alpha = rgb.alpha}; +} + +extern RGB _FBHSB2RGB(HSB hsb) +{ + double r, g, b; + + int i = hsb.hue * 6; + double f = hsb.hue * 6 - i; + double p = hsb.brightness * (1 - hsb.saturation); + double q = hsb.brightness * (1 - f * hsb.saturation); + double t = hsb.brightness * (1 - (1 - f) * hsb.saturation); + + switch(i % 6){ + case 0: r = hsb.brightness, g = t, b = p; break; + case 1: r = q, g = hsb.brightness, b = p; break; + case 2: r = p, g = hsb.brightness, b = t; break; + case 3: r = p, g = q, b = hsb.brightness; break; + case 4: r = t, g = p, b = hsb.brightness; break; + case 5: r = hsb.brightness, g = p, b = q; break; + } + + return (RGB){.red = r, .green = g, .blue = b, .alpha = hsb.alpha}; +} + +extern RGB _FBRGBColorComponents(UIColor *color) +{ + RGB result; + CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)); + if (colorSpaceModel != kCGColorSpaceModelRGB && colorSpaceModel != kCGColorSpaceModelMonochrome) { + return result; + } + const CGFloat *components = CGColorGetComponents(color.CGColor); + if (colorSpaceModel == kCGColorSpaceModelMonochrome) { + result.red = result.green = result.blue = components[0]; + result.alpha = components[1]; + } else { + result.red = components[0]; + result.green = components[1]; + result.blue = components[2]; + result.alpha = components[3]; + } + return result; +} + +extern CGFloat _FBGetColorWheelHue(CGPoint position, CGPoint center, CGFloat radius) { + CGFloat dx = (CGFloat)(position.x - center.x) / radius; + CGFloat dy = (CGFloat)(position.y - center.y) / radius; + CGFloat d = sqrtf(dx*dx + dy*dy); + CGFloat hue = 0; + if (d != 0) { + hue = acosf(dx / d) / M_PI / 2.0f; + if (dy < 0) { + hue = 1.0 - hue; + } + } + return hue; +} + +extern CGFloat _FBGetColorWheelSaturation(CGPoint position, CGPoint center, CGFloat radius) { + CGFloat dx = (CGFloat)(position.x - center.x) / radius; + CGFloat dy = (CGFloat)(position.y - center.y) / radius; + return sqrtf(dx*dx + dy*dy); +} + +extern CGImageRef _FBCreateColorWheelImage(CGFloat diameter) { + CFMutableDataRef bitmapData = CFDataCreateMutable(NULL, 0); + CFDataSetLength(bitmapData, diameter * diameter * 4); + UInt8 * bitmap = CFDataGetMutableBytePtr(bitmapData); + for (int y = 0; y < diameter; y++) { + for (int x = 0; x < diameter; x++) { + CGFloat hue = _FBGetColorWheelHue(CGPointMake(x, y), (CGPoint){diameter / 2, diameter / 2}, diameter / 2); + CGFloat saturation = _FBGetColorWheelSaturation(CGPointMake(x, y), (CGPoint){diameter / 2, diameter / 2}, diameter / 2); + CGFloat a = 0.0f; + RGB rgb = {0.0f, 0.0f, 0.0f, 0.0f}; + if (saturation < 1.0) { + // Antialias the edge of the circle. + if (saturation > 0.99) a = (1.0 - saturation) * 100; + else a = 1.0; + HSB hsb = {hue, saturation, 1.0f, a}; + rgb = _FBHSB2RGB(hsb); + } + + int i = 4 * (x + y * diameter); + bitmap[i] = rgb.red * 0xff; + bitmap[i+1] = rgb.green * 0xff; + bitmap[i+2] = rgb.blue * 0xff; + bitmap[i+3] = rgb.alpha * 0xff; + } + } + + CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(bitmapData); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGImageRef imageRef = CGImageCreate(diameter, diameter, 8, 32, diameter * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaLast, dataProvider, NULL, 0, kCGRenderingIntentDefault); + CGDataProviderRelease(dataProvider); + CGColorSpaceRelease(colorSpace); + CFRelease(bitmapData); + return imageRef; +} diff --git a/FBTweak/_FBColorWheelCell.h b/FBTweak/_FBColorWheelCell.h new file mode 100644 index 00000000..7ceb7ae4 --- /dev/null +++ b/FBTweak/_FBColorWheelCell.h @@ -0,0 +1,31 @@ +/** + 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 _FBColorWheelCell; + +@protocol _FBColorWheelCellDelegate + +- (void)colorWheelCell:(_FBColorWheelCell*)cell didChangeHue:(CGFloat)hue saturation:(CGFloat)saturation; + +@end + +/** + @abstract A cell to edit the hue and saturation color components. + */ +@interface _FBColorWheelCell : UITableViewCell + +- (void)setHue:(CGFloat)hue; +- (void)setSaturation:(CGFloat)saturation; + +//! @abstract The cell's delegate. +@property(nonatomic, weak) id<_FBColorWheelCellDelegate> delegate; + +@end diff --git a/FBTweak/_FBColorWheelCell.m b/FBTweak/_FBColorWheelCell.m new file mode 100644 index 00000000..014869a7 --- /dev/null +++ b/FBTweak/_FBColorWheelCell.m @@ -0,0 +1,137 @@ +/** + 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 "_FBColorWheelCell.h" +#import "_FBColorUtils.h" + +static CGFloat const _FBColorWheelDiameter = 200.0; +static CGFloat const _FBColorWheelIndicatorDiameter = 33.0; + +@interface _FBColorWheelCell () + +@end + +@implementation _FBColorWheelCell { + CALayer *_colorWheelLayer; + CALayer *_indicatorLayer; + CGFloat _hue; + CGFloat _saturation; +} + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier +{ + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + _colorWheelLayer = [CALayer layer]; + _colorWheelLayer.anchorPoint = (CGPoint){0.5, 0.5}; + _colorWheelLayer.bounds = (CGRect){0, 0, _FBColorWheelDiameter, _FBColorWheelDiameter}; + _colorWheelLayer.contents = (__bridge_transfer id)_FBCreateColorWheelImage(_FBColorWheelDiameter); + [self.layer addSublayer:_colorWheelLayer]; + _indicatorLayer = [self _createIndicatorLayer]; + [self.layer addSublayer:_indicatorLayer]; + + UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handlePanGesture:)]; + panGestureRecognizer.minimumNumberOfTouches = 1; + panGestureRecognizer.maximumNumberOfTouches = 1; + panGestureRecognizer.delegate = self; + [self addGestureRecognizer:panGestureRecognizer]; + [self setSelectionStyle:UITableViewCellSelectionStyleNone]; + } + return self; +} + +- (void)setHue:(CGFloat)hue +{ + _hue = hue; + [self _setSelectedPoint:[self _selectedPoint]]; +} + +- (void)setSaturation:(CGFloat)saturation +{ + _saturation = saturation; + [self _setSelectedPoint:[self _selectedPoint]]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + _colorWheelLayer.position = (CGPoint){CGRectGetMidX(self.contentView.bounds), CGRectGetMidY(self.contentView.bounds)}; + [self _setSelectedPoint:[self _selectedPoint]]; +} + +#pragma mark - UIGestureRecognizerDelegate + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch +{ + CGPoint position = [touch locationInView:self]; + return [self _shouldHandleTouchAtPosition:position]; +} + +#pragma mark - Private methods + +- (CALayer*)_createIndicatorLayer +{ + UIColor *edgeColor = [UIColor colorWithWhite:0.9 alpha:0.8]; + CALayer* indicatorLayer = [CALayer layer]; + indicatorLayer.cornerRadius = _FBColorWheelIndicatorDiameter / 2; + indicatorLayer.borderColor = edgeColor.CGColor; + indicatorLayer.borderWidth = 2; + indicatorLayer.backgroundColor = [UIColor whiteColor].CGColor; + indicatorLayer.bounds = CGRectMake(0, 0, _FBColorWheelIndicatorDiameter, _FBColorWheelIndicatorDiameter); + indicatorLayer.position = CGPointMake(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2); + indicatorLayer.shadowColor = [UIColor blackColor].CGColor; + indicatorLayer.shadowOffset = CGSizeZero; + indicatorLayer.shadowRadius = 1; + indicatorLayer.shadowOpacity = 0.5f; + return indicatorLayer; +} + +- (BOOL)_shouldHandleTouchAtPosition:(CGPoint)position +{ + CGFloat radius = _FBColorWheelDiameter / 2; + CGPoint center = self.contentView.center; + CGFloat dist = sqrtf((center.x - position.x) * (center.x - position.x) + (center.y - position.y) * (center.y - position.y)); + return dist <= radius; +} + +- (void)_handlePanGesture:(UIPanGestureRecognizer*)panGestureRecognizer +{ + CGPoint position = [panGestureRecognizer locationInView:self]; + if (![self _shouldHandleTouchAtPosition:position]) { + return; + } + CGFloat radius = _FBColorWheelDiameter / 2; + CGPoint center = self.contentView.center; + _hue = _FBGetColorWheelHue(position, center, radius); + _saturation = _FBGetColorWheelSaturation(position, center, radius); + [self _setSelectedPoint:position]; + [self.delegate colorWheelCell:self didChangeHue:_hue saturation:_saturation]; +} + +- (void)_setSelectedPoint:(CGPoint)point +{ + UIColor *selectedColor = [UIColor colorWithHue:_hue saturation:_saturation brightness:1.0f alpha:1.0f]; + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue + forKey:kCATransactionDisableActions]; + _indicatorLayer.position = point; + _indicatorLayer.backgroundColor = selectedColor.CGColor; + [CATransaction commit]; +} + +- (CGPoint)_selectedPoint +{ + CGFloat radius = _saturation * _FBColorWheelDiameter / 2; + CGFloat x = _FBColorWheelDiameter / 2 + radius * cosf(_hue * M_PI * 2.0f) + CGRectGetMinX(_colorWheelLayer.frame); + CGFloat y = _FBColorWheelDiameter / 2 + radius * sinf(_hue * M_PI * 2.0f) + CGRectGetMinY(_colorWheelLayer.frame); + return CGPointMake(x, y); +} + +@end diff --git a/FBTweak/_FBKeyboardManager.h b/FBTweak/_FBKeyboardManager.h new file mode 100644 index 00000000..c0e49ef0 --- /dev/null +++ b/FBTweak/_FBKeyboardManager.h @@ -0,0 +1,34 @@ +/** + 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 + +/** + @abstract Keyboard manager. Adjusts the content so that the target object remains visible. + */ +@interface _FBKeyboardManager : NSObject + +/** + @abstract Creates a keyboard manager. + @discussion This is the designated initializer. + @param scrollView The that contains the content to adjust. + */ +- (instancetype)initWithViewScrollView:(UIScrollView*)scrollView; + +/** + * Enables the keyboard manager. The manager is enabled by default. + */ +- (void)enable; + +/** + * Disables the keyboard manager. + */ +- (void)disable; + +@end diff --git a/FBTweak/_FBKeyboardManager.m b/FBTweak/_FBKeyboardManager.m new file mode 100644 index 00000000..ec20e036 --- /dev/null +++ b/FBTweak/_FBKeyboardManager.m @@ -0,0 +1,76 @@ +/** + 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 "_FBKeyboardManager.h" + +@implementation _FBKeyboardManager { + __weak UIScrollView *_scrollView; +} + +- (instancetype)init +{ + return [self initWithViewScrollView:nil]; +} + +- (instancetype)initWithViewScrollView:(UIScrollView*)scrollView +{ + self = [super init]; + if (self) { + _scrollView = scrollView; + [self enable]; + } + return self; +} + +- (void)enable +{ + [self disable]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_keyboardFrameChanged:) name:UIKeyboardWillChangeFrameNotification object:nil]; +} + +- (void)disable +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil]; +} + +- (void)dealloc +{ + [self disable]; +} + +#pragma mark - Private methods + +- (void)_keyboardFrameChanged:(NSNotification *)notification +{ + UIView *contentView = _scrollView.superview; + + NSDictionary *userInfo = [notification userInfo]; + CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + endFrame = [contentView.window convertRect:endFrame fromWindow:nil]; + endFrame = [contentView convertRect:endFrame fromView:contentView.window]; + + NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + + void (^animations)() = ^{ + UIEdgeInsets contentInset = _scrollView.contentInset; + contentInset.bottom = (contentView.bounds.size.height - CGRectGetMinY(endFrame)); + _scrollView.contentInset = contentInset; + + UIEdgeInsets scrollIndicatorInsets = _scrollView.scrollIndicatorInsets; + scrollIndicatorInsets.bottom = (contentView.bounds.size.height - CGRectGetMinY(endFrame)); + _scrollView.scrollIndicatorInsets = scrollIndicatorInsets; + }; + + UIViewAnimationOptions options = (curve << 16) | UIViewAnimationOptionBeginFromCurrentState; + + [UIView animateWithDuration:duration delay:0 options:options animations:animations completion:NULL]; +} + +@end diff --git a/FBTweak/_FBSliderView.h b/FBTweak/_FBSliderView.h new file mode 100644 index 00000000..cde6cc74 --- /dev/null +++ b/FBTweak/_FBSliderView.h @@ -0,0 +1,36 @@ +/** + 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 + +/** + @abstract A slider with a gradient track. + */ +@interface _FBSliderView : UIControl + +/** + * The slider's current value. The default value is 0.0. + */ +@property(nonatomic, assign) CGFloat value; +/** + * The minimum value of the slider. The default value is 0.0. + */ +@property(nonatomic, assign) CGFloat minimumValue; +/** + * The maximum value of the slider. The default value is 1.0. + */ +@property(nonatomic, assign) CGFloat maximumValue; + +/** + * The array of CGColorRef objects defining the color of each gradient stop on the track. + * The location of each gradient stop is evaluated with formula: i * width_of_the_track / number_of_colors. + */ +@property(nonatomic, copy) NSArray *colors; + +@end diff --git a/FBTweak/_FBSliderView.m b/FBTweak/_FBSliderView.m new file mode 100644 index 00000000..5a7305ef --- /dev/null +++ b/FBTweak/_FBSliderView.m @@ -0,0 +1,164 @@ +/** + 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 "_FBSliderView.h" + +static const CGFloat _FBSliderViewHeight = 28.0f; +static const CGFloat _FBSliderViewThumbDimension = 28.0f; +static const CGFloat _FBSliderViewTrackHeight = 3.0f; + +@implementation _FBSliderView { + CALayer *_thumbLayer; + CAGradientLayer *_trackLayer; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _minimumValue = 0.0f; + _maximumValue = 1.0f; + _value = 0.0f; + + self.layer.delegate = self; + + _trackLayer = [CAGradientLayer layer]; + _trackLayer.cornerRadius = _FBSliderViewTrackHeight / 2.0f; + _trackLayer.startPoint = CGPointMake(0.0f, 0.5f); + _trackLayer.endPoint = CGPointMake(1.0f, 0.5f); + [self.layer addSublayer:_trackLayer]; + + _thumbLayer = [CALayer layer]; + _thumbLayer.cornerRadius = _FBSliderViewHeight / 2; + _thumbLayer.backgroundColor = [UIColor whiteColor].CGColor; + _thumbLayer.shadowColor = [UIColor blackColor].CGColor; + _thumbLayer.shadowOffset = CGSizeZero; + _thumbLayer.shadowRadius = 2; + _thumbLayer.shadowOpacity = 0.5f; + [self.layer addSublayer:_thumbLayer]; + + __attribute__((objc_precise_lifetime)) id color = (__bridge id)[UIColor blueColor].CGColor; + [self setColors:@[color, color]]; + } + return self; +} + +- (CGSize)intrinsicContentSize +{ + return CGSizeMake(UIViewNoIntrinsicMetric, _FBSliderViewHeight); +} + +- (void)setValue:(CGFloat)value +{ + if (value < _minimumValue) { + _value = _minimumValue; + } else if (value > _maximumValue) { + _value = _maximumValue; + } else { + _value = value; + } + [self _updateThumbPositionWithValue:_value]; +} + +- (void)setColors:(NSArray*)colors +{ + _trackLayer.colors = colors; + [self _updateLocations]; +} + +- (NSArray*)colors +{ + return _trackLayer.colors; +} + +#pragma mark - UIControl touch tracking events + +- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event +{ + CGPoint touchPoint = [touch locationInView:self]; + if (CGRectContainsPoint(CGRectInset(_thumbLayer.frame, -10.0, -10.0), touchPoint)) { + [self _setValueWithPosition:touchPoint.x]; + return YES; + } + return NO; +} + +- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event +{ + CGPoint touchPoint = [touch locationInView:self]; + [self _setValueWithPosition:touchPoint.x]; + return YES; +} + +- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event +{ + CGPoint touchPoint = [touch locationInView:self]; + [self _setValueWithPosition:touchPoint.x]; +} + +- (void)layoutSublayersOfLayer:(CALayer *)layer +{ + if (layer == self.layer) { + CGFloat height = _FBSliderViewHeight; + CGFloat width = CGRectGetWidth(self.bounds); + _trackLayer.bounds = CGRectMake(0, 0, width , _FBSliderViewTrackHeight); + _trackLayer.position = CGPointMake(CGRectGetWidth(self.bounds) / 2, height / 2); + _thumbLayer.bounds = CGRectMake(0, 0, _FBSliderViewThumbDimension, _FBSliderViewThumbDimension); + [self _updateThumbPositionWithValue:_value]; + } +} + +#pragma mark - Private methods + +- (void)_setValueWithPosition:(CGFloat)position +{ + CGFloat width = CGRectGetWidth(self.bounds); + if (position < 0) { + position = 0; + } else if (position > width) { + position = width; + } + CGFloat percentage = position / width; + CGFloat value = _minimumValue + percentage * (_maximumValue - _minimumValue); + [self setValue:value]; + [self sendActionsForControlEvents:UIControlEventValueChanged]; +} + +- (void)_updateLocations +{ + NSUInteger size = [_trackLayer.colors count]; + if (size == [_trackLayer.locations count]) { + return; + } + CGFloat step = 1.0f / (size - 1); + NSMutableArray *locations = [NSMutableArray array]; + [locations addObject:@(0.0f)]; + for (NSUInteger i = 1; i < size - 1; ++i) { + [locations addObject:@(i * step)]; + } + [locations addObject:@(1.0f)]; + _trackLayer.locations = [locations copy]; +} + +- (void)_updateThumbPositionWithValue:(CGFloat)value +{ + CGFloat width = CGRectGetWidth(self.bounds); + if (width == 0) { + return; + } + CGFloat percentage = (_value - _minimumValue) / (_maximumValue - _minimumValue); + CGFloat position = width * percentage; + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue + forKey:kCATransactionDisableActions]; + _thumbLayer.position = CGPointMake(position - ((position - width / 2) / (width / 2)) * _FBSliderViewThumbDimension / 2, _FBSliderViewHeight / 2); + [CATransaction commit]; +} + +@end diff --git a/FBTweak/_FBTweakCollectionViewController.m b/FBTweak/_FBTweakCollectionViewController.m index 9345f4ea..14c920a3 100644 --- a/FBTweak/_FBTweakCollectionViewController.m +++ b/FBTweak/_FBTweakCollectionViewController.m @@ -12,8 +12,10 @@ #import "FBTweak.h" #import "_FBTweakCollectionViewController.h" #import "_FBTweakTableViewCell.h" +#import "_FBTweakColorViewController.h" #import "_FBTweakDictionaryViewController.h" #import "_FBTweakArrayViewController.h" +#import "_FBKeyboardManager.h" @interface _FBTweakCollectionViewController () @end @@ -21,6 +23,7 @@ @interface _FBTweakCollectionViewController () + +@class FBTweak; + +/** + * @abstract Displays a view to edit a tweak with color value. + */ +@interface _FBTweakColorViewController : UIViewController + +/** + @abstract Create a RGB view controller. + @param tweak The tweak with color value to edit. + @discussion This is the designated initializer. + */ +- (instancetype)initWithTweak:(FBTweak *)tweak; + +//! @abstract The color tweak to display in the view controller. +@property (nonatomic, strong, readonly) FBTweak *tweak; + +@end diff --git a/FBTweak/_FBTweakColorViewController.m b/FBTweak/_FBTweakColorViewController.m new file mode 100644 index 00000000..83113210 --- /dev/null +++ b/FBTweak/_FBTweakColorViewController.m @@ -0,0 +1,124 @@ +/** + 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 "_FBTweakColorViewController.h" +#import "_FBTweakColorViewControllerHSBDataSource.h" +#import "_FBTweakColorViewControllerRGBDataSource.h" +#import "_FBKeyboardManager.h" +#import "FBTweak.h" + +static void *kContext = &kContext; +static CGFloat const _FBTweakColorCellDefaultHeight = 44.0; +static CGFloat const _FBColorWheelCellHeight = 220.0f; + +@interface _FBTweakColorViewController () + +@end + +@implementation _FBTweakColorViewController { + NSObject <_FBTweakColorViewControllerDataSource> *_rgbDataSource; + NSObject <_FBTweakColorViewControllerDataSource> *_hsbDataSource; + FBTweak *_tweak; + _FBKeyboardManager *_keyboardManager; + UITableView *_tableView; +} + +- (instancetype)initWithTweak:(FBTweak*)tweak +{ + NSParameterAssert([tweak.defaultValue isKindOfClass:[UIColor class]]); + self = [super init]; + if (self) { + _tweak = tweak; + _rgbDataSource = [[_FBTweakColorViewControllerRGBDataSource alloc] init]; + _hsbDataSource = [[_FBTweakColorViewControllerHSBDataSource alloc] init]; + [_rgbDataSource addObserver:self forKeyPath:NSStringFromSelector(@selector(value)) options:NSKeyValueObservingOptionNew context:kContext]; + [_hsbDataSource addObserver:self forKeyPath:NSStringFromSelector(@selector(value)) options:NSKeyValueObservingOptionNew context:kContext]; + } + return self; +} + +- (void)dealloc +{ + [_rgbDataSource removeObserver:self forKeyPath:NSStringFromSelector(@selector(value))]; + [_hsbDataSource removeObserver:self forKeyPath:NSStringFromSelector(@selector(value))]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; + _tableView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + _tableView.delegate = self; + [self.view addSubview:_tableView]; + + _keyboardManager = [[_FBKeyboardManager alloc] initWithViewScrollView:_tableView]; + + UISegmentedControl *segmentedControl = [self _createSegmentedControl]; + self.navigationItem.titleView = segmentedControl; + segmentedControl.selectedSegmentIndex = 0; + [self _segmentControlDidChangeValue:segmentedControl]; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + [_keyboardManager enable]; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + [_keyboardManager disable]; +} + +#pragma mark - KVO methods + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(NSObject<_FBTweakColorViewControllerDataSource>*)dataSource change:(NSDictionary *)change context:(void *)context +{ + if (context != kContext) { + return; + } + _tweak.currentValue = dataSource.value; +} + +#pragma mark - UITableViewDelegate + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (tableView.dataSource == _hsbDataSource && indexPath.section == 1 && indexPath.row == 0) { + return _FBColorWheelCellHeight; + } + return _FBTweakColorCellDefaultHeight; +} + +#pragma mark - Private methods + +- (UIColor*)_colorValue +{ + return _tweak.currentValue ?: _tweak.defaultValue; +} + +- (void)_segmentControlDidChangeValue:(UISegmentedControl*)sender +{ + NSObject<_FBTweakColorViewControllerDataSource>* dataSource = sender.selectedSegmentIndex == 0 ? _rgbDataSource : _hsbDataSource; + dataSource.value = [self _colorValue]; + _tableView.dataSource = dataSource; + [_tableView reloadData]; +} + +- (UISegmentedControl*)_createSegmentedControl +{ + UISegmentedControl *segmentedControl = [[UISegmentedControl alloc] initWithItems:@[@"RGB", @"HSB"]]; + [segmentedControl addTarget:self action:@selector(_segmentControlDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + [segmentedControl sizeToFit]; + return segmentedControl; +} + +@end diff --git a/FBTweak/_FBTweakColorViewControllerDataSource.h b/FBTweak/_FBTweakColorViewControllerDataSource.h new file mode 100644 index 00000000..89cb0753 --- /dev/null +++ b/FBTweak/_FBTweakColorViewControllerDataSource.h @@ -0,0 +1,22 @@ +/** + 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 + +/** + @abstract The _FBTweakColorViewControllerDataSource protocol declares an interface that provides cells for table view. + */ +@protocol _FBTweakColorViewControllerDataSource + +@required + +//! @abstract The current color value. +@property(nonatomic, strong) UIColor *value; + +@end diff --git a/FBTweak/_FBTweakColorViewControllerHSBDataSource.h b/FBTweak/_FBTweakColorViewControllerHSBDataSource.h new file mode 100644 index 00000000..cd95b9b2 --- /dev/null +++ b/FBTweak/_FBTweakColorViewControllerHSBDataSource.h @@ -0,0 +1,18 @@ +/** + 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 +#import "_FBTweakColorViewControllerDataSource.h" + +/** + @abstract A data source to privide cell's for HSB table view. + */ +@interface _FBTweakColorViewControllerHSBDataSource : NSObject <_FBTweakColorViewControllerDataSource> + +@end diff --git a/FBTweak/_FBTweakColorViewControllerHSBDataSource.m b/FBTweak/_FBTweakColorViewControllerHSBDataSource.m new file mode 100644 index 00000000..a29022f7 --- /dev/null +++ b/FBTweak/_FBTweakColorViewControllerHSBDataSource.m @@ -0,0 +1,164 @@ +/** + 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 "_FBTweakColorViewControllerHSBDataSource.h" +#import "_FBColorComponentCell.h" +#import "_FBColorWheelCell.h" +#import "_FBColorUtils.h" + +@interface _FBTweakColorViewControllerHSBDataSource () <_FBColorComponentCellDelegate, _FBColorWheelCellDelegate> + +@end + +@implementation _FBTweakColorViewControllerHSBDataSource { + NSArray *_titles; + NSArray *_maxValues; + HSB _colorComponents; + NSArray *_colorComponentCells; + UITableViewCell *_colorSampleCell; + _FBColorWheelCell *_colorWheelCell; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _titles = @[@"H", @"S", @"B", @"A"]; + _maxValues = @[@(_FBHSBColorComponentMaxValue), @(_FBHSBColorComponentMaxValue), @(_FBHSBColorComponentMaxValue), + @(_FBAlphaComponentMaxValue)]; + [self _createCells]; + } + return self; +} + +- (void)setValue:(UIColor *)value +{ + _colorComponents = _FBRGB2HSB(_FBRGBColorComponents(value)); + [self _reloadData]; +} + +- (UIColor*)value +{ + return [UIColor colorWithHue:_colorComponents.hue saturation:_colorComponents.saturation brightness:_colorComponents.brightness alpha:_colorComponents.alpha]; +} + +#pragma mark - _FBColorComponentCellDelegate + +- (void)colorComponentCell:(_FBColorComponentCell*)cell didChangeValue:(CGFloat)value +{ + [self _setValue:cell.value forColorComponent:[_colorComponentCells indexOfObject:cell]]; + [self _reloadData]; +} + +#pragma mark - _FBColorWheelCellDelegate + +- (void)colorWheelCell:(_FBColorWheelCell*)cell didChangeHue:(CGFloat)hue saturation:(CGFloat)saturation +{ + [self _setValue:hue forColorComponent:_FBHSBColorComponentHue]; + [self _setValue:saturation forColorComponent:_FBHSBColorComponentSaturation]; + [self _reloadData]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 2; // color sample + color components +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + if (section == 0) { + return 1; + } + return [_colorComponentCells count] + 1; // color wheel + hsba components +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == 0) { + return _colorSampleCell; + } + if (indexPath.row == 0) { + return _colorWheelCell; + } + return _colorComponentCells[indexPath.row - 1]; +} + +#pragma mark - Private methods + +- (void)_reloadData +{ + _colorSampleCell.backgroundColor = self.value; + _colorWheelCell.hue = _colorComponents.hue; + _colorWheelCell.saturation = _colorComponents.saturation; + + NSArray *components = [self _colorComponentsWithHSB:_colorComponents]; + for (int i = 0; i < _FBHSBAColorComponentsSize; ++i) { + _FBColorComponentCell *cell = _colorComponentCells[i]; + if (i == _FBRGBAColorComponentsSize - 2) { // set colors for brightness component only + UIColor *tmp = [UIColor colorWithHue:_colorComponents.hue saturation:_colorComponents.saturation brightness:1.0f alpha:1.0f]; + cell.colors = @[(id)[UIColor blackColor].CGColor, (id)tmp.CGColor]; + } + cell.value = [components[i] floatValue] * (i == _FBHSBAColorComponentsSize - 1 ? [_maxValues[i] floatValue] : 1); + } +} + +- (void)_createCells +{ + NSArray *components = [self _colorComponentsWithHSB:_colorComponents]; + NSMutableArray *tmp = [NSMutableArray array]; + for (int i = 0; i < _FBHSBAColorComponentsSize; ++i) { + _FBColorComponentCell *cell = [[_FBColorComponentCell alloc] init]; + if (i == _FBRGBAColorComponentsSize - 2) { // set colors for brightness component only + UIColor *tmp = [UIColor colorWithHue:_colorComponents.hue saturation:_colorComponents.saturation brightness:1.0f alpha:1.0f]; + cell.colors = @[(id)[UIColor blackColor].CGColor, (id)tmp.CGColor]; + } + cell.format = i == _FBHSBAColorComponentsSize - 1 ? @"%.f" : @"%.2f"; + cell.value = [components[i] floatValue] * (i == _FBHSBAColorComponentsSize - 1 ? [_maxValues[i] floatValue] : 1); + cell.title = _titles[i]; + cell.maximumValue = [_maxValues[i] floatValue]; + cell.delegate = self; + [tmp addObject:cell]; + } + _colorComponentCells = [tmp copy]; + + _colorSampleCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + _colorSampleCell.backgroundColor = self.value; + + _colorWheelCell = [[_FBColorWheelCell alloc] init]; + _colorWheelCell.delegate = self; +} + +- (void)_setValue:(CGFloat)value forColorComponent:(_FBHSBColorComponent)colorComponent +{ + [self willChangeValueForKey:NSStringFromSelector(@selector(value))]; + switch (colorComponent) { + case _FBHSBColorComponentHue: + _colorComponents.hue = value; + break; + case _FBHSBColorComponentSaturation: + _colorComponents.saturation = value; + break; + case _FBHSBColorComponentBrightness: + _colorComponents.brightness = value; + break; + case _FBHSBColorComponentAlpha: + _colorComponents.alpha = value / _FBAlphaComponentMaxValue; + break; + } + [self didChangeValueForKey:NSStringFromSelector(@selector(value))]; +} + +- (NSArray*)_colorComponentsWithHSB:(HSB)hsb +{ + return @[@(hsb.hue), @(hsb.saturation), @(hsb.brightness), @(hsb.alpha)]; +} + +@end diff --git a/FBTweak/_FBTweakColorViewControllerRGBDataSource.h b/FBTweak/_FBTweakColorViewControllerRGBDataSource.h new file mode 100644 index 00000000..5d5e014a --- /dev/null +++ b/FBTweak/_FBTweakColorViewControllerRGBDataSource.h @@ -0,0 +1,18 @@ +/** + 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 +#import "_FBTweakColorViewControllerDataSource.h" + +/** + @abstract A data source to privide cell's for RGB table view. + */ +@interface _FBTweakColorViewControllerRGBDataSource : NSObject <_FBTweakColorViewControllerDataSource> + +@end diff --git a/FBTweak/_FBTweakColorViewControllerRGBDataSource.m b/FBTweak/_FBTweakColorViewControllerRGBDataSource.m new file mode 100644 index 00000000..19ae8e7f --- /dev/null +++ b/FBTweak/_FBTweakColorViewControllerRGBDataSource.m @@ -0,0 +1,161 @@ +/** + 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 "_FBTweakColorViewControllerRGBDataSource.h" +#import "_FBColorComponentCell.h" +#import "_FBColorUtils.h" + +@interface _FBTweakColorViewControllerRGBDataSource () <_FBColorComponentCellDelegate> + +@end + +@implementation _FBTweakColorViewControllerRGBDataSource { + NSArray *_titles; + NSArray *_maxValues; + RGB _colorComponents; + NSArray *_colorComponentCells; + UITableViewCell *_colorSampleCell; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _titles = @[@"R", @"G", @"B", @"A"]; + _maxValues = @[@(_FBRGBColorComponentMaxValue), @(_FBRGBColorComponentMaxValue), @(_FBRGBColorComponentMaxValue), + @(_FBAlphaComponentMaxValue)]; + [self _createCells]; + } + return self; +} + +- (void)setValue:(UIColor *)value +{ + [self willChangeValueForKey:NSStringFromSelector(@selector(value))]; + _colorComponents = _FBRGBColorComponents(value); + [self _reloadData]; + [self didChangeValueForKey:NSStringFromSelector(@selector(value))]; +} + +- (UIColor*)value +{ + return [UIColor colorWithRed:_colorComponents.red green:_colorComponents.green blue:_colorComponents.blue alpha:_colorComponents.alpha]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 2; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + if (section == 0) { + return 1; + } + return [_colorComponentCells count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == 0) { + return _colorSampleCell; + } + return _colorComponentCells[indexPath.row]; +} + +#pragma mark - _FBColorComponentCellDelegate + +- (void)colorComponentCell:(_FBColorComponentCell*)cell didChangeValue:(CGFloat)value +{ + [self _setValue:cell.value / cell.maximumValue forColorComponent:[_colorComponentCells indexOfObject:cell]]; + [self _reloadData]; +} + +#pragma mark - Private methods + +- (void)_reloadData +{ + _colorSampleCell.backgroundColor = self.value; + NSArray *components = [self _colorComponentsWithRGB:_colorComponents]; + for (int i = 0; i < _FBRGBAColorComponentsSize; ++i) { + _FBColorComponentCell *cell = _colorComponentCells[i]; + cell.value = [components[i] floatValue] * [_maxValues[i] floatValue]; + if (i < _FBRGBAColorComponentsSize - 1) { + cell.colors = [self _colorsWithComponents:components colorIndex:i]; + } + } +} + +- (void)_createCells +{ + NSMutableArray *tmp = [NSMutableArray array]; + NSArray *components = [self _colorComponentsWithRGB:_colorComponents]; + for (int i = 0; i < _FBRGBAColorComponentsSize; ++i) { + _FBColorComponentCell *cell = [[_FBColorComponentCell alloc] init]; + if (i < _FBRGBAColorComponentsSize - 1) { + cell.colors = [self _colorsWithComponents:components colorIndex:i]; + } + cell.value = [components[i] floatValue] * [_maxValues[i] floatValue]; + cell.title = _titles[i]; + cell.maximumValue = [_maxValues[i] floatValue]; + cell.delegate = self; + [tmp addObject:cell]; + } + _colorComponentCells = [tmp copy]; + _colorSampleCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + _colorSampleCell.backgroundColor = self.value; +} + +- (NSArray*)_colorComponentsWithRGB:(RGB)rgb +{ + return @[@(rgb.red), @(rgb.green), @(rgb.blue), @(rgb.alpha)]; +} + +- (NSArray*)_colorsWithComponents:(NSArray*)colorComponents colorIndex:(NSUInteger)colorIndex +{ + CGFloat currentColorValue = [colorComponents[colorIndex] floatValue]; + CGFloat colors[12]; + for (NSUInteger i = 0; i < _FBRGBAColorComponentsSize; i++) + { + colors[i] = [colorComponents[i] floatValue]; + colors[i + 4] = [colorComponents[i] floatValue]; + colors[i + 8] = [colorComponents[i] floatValue]; + } + colors[colorIndex] = 0; + colors[colorIndex + 4] = currentColorValue; + colors[colorIndex + 8] = 1.0; + UIColor *start = [UIColor colorWithRed:colors[0] green:colors[1] blue:colors[2] alpha:1.0f]; + UIColor *middle = [UIColor colorWithRed:colors[4] green:colors[5] blue:colors[6] alpha:1.0f]; + UIColor *end = [UIColor colorWithRed:colors[8] green:colors[9] blue:colors[10] alpha:1.0f]; + return @[(id)start.CGColor, (id)middle.CGColor, (id)end.CGColor]; +} + +- (void)_setValue:(CGFloat)value forColorComponent:(_FBRGBColorComponent)colorComponent +{ + [self willChangeValueForKey:NSStringFromSelector(@selector(value))]; + switch (colorComponent) { + case _FBRGBColorComponentRed: + _colorComponents.red = value; + break; + case _FBRGBColorComponentGreed: + _colorComponents.green = value; + break; + case _FBRGBColorComponentBlue: + _colorComponents.blue = value; + break; + case _FBRGBColorComponentAlpha: + _colorComponents.alpha = value; + break; + } + [self didChangeValueForKey:NSStringFromSelector(@selector(value))]; +} + +@end diff --git a/FBTweak/_FBTweakTableViewCell.m b/FBTweak/_FBTweakTableViewCell.m index b344999b..71f5c938 100644 --- a/FBTweak/_FBTweakTableViewCell.m +++ b/FBTweak/_FBTweakTableViewCell.m @@ -10,6 +10,16 @@ #import "FBTweak.h" #import "_FBTweakTableViewCell.h" +static UIImage *_FBCreateColorCellsThumbnail(UIColor *color, CGSize size) { + UIGraphicsBeginImageContext(size); + UIBezierPath *rPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, size.width, size.height)]; + [color setFill]; + [rPath fill]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + typedef NS_ENUM(NSUInteger, _FBTweakTableViewCellMode) { _FBTweakTableViewCellModeNone = 0, _FBTweakTableViewCellModeBoolean, @@ -19,6 +29,7 @@ typedef NS_ENUM(NSUInteger, _FBTweakTableViewCellMode) { _FBTweakTableViewCellModeAction, _FBTweakTableViewCellModeDictionary, _FBTweakTableViewCellModeArray, + _FBTweakTableViewCellModeColor, }; @interface _FBTweakTableViewCell () @@ -33,7 +44,7 @@ @implementation _FBTweakTableViewCell { UIStepper *_stepper; } -- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier; +- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier { if ((self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier])) { _accessoryView = [[UIView alloc] init]; @@ -89,6 +100,10 @@ - (void)layoutSubviews CGRect textBounds = CGRectMake(0, 0, textFieldWidth, self.bounds.size.height); _textField.frame = CGRectIntegral(textBounds); _accessoryView.bounds = CGRectIntegral(textBounds); + } else if (_mode == _FBTweakTableViewCellModeColor) { + CGRect textBounds = CGRectMake(0, 0, self.bounds.size.width / 3, self.bounds.size.height); + _textField.frame = CGRectIntegral(textBounds); + _accessoryView.bounds = CGRectIntegral(textBounds); } else if (_mode == _FBTweakTableViewCellModeAction) { _accessoryView.bounds = CGRectZero; } @@ -112,6 +127,8 @@ - (void)setTweak:(FBTweak *)tweak mode = _FBTweakTableViewCellModeDictionary; } else if ([tweak.possibleValues isKindOfClass:[NSArray class]]) { mode = _FBTweakTableViewCellModeArray; + } else if ([value isKindOfClass:[UIColor class]]) { + mode = _FBTweakTableViewCellModeColor; } else if ([value isKindOfClass:[NSString class]]) { mode = _FBTweakTableViewCellModeString; } else if ([value isKindOfClass:[NSNumber class]]) { @@ -229,6 +246,13 @@ - (void)_updateMode:(_FBTweakTableViewCellMode)mode self.accessoryView = nil; self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; self.selectionStyle = UITableViewCellSelectionStyleBlue; + } else if (_mode == _FBTweakTableViewCellModeColor) { + _switch.hidden = YES; + _textField.hidden = YES; + _stepper.hidden = YES; + self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + self.accessoryView = nil; + self.imageView.hidden = NO; } else { _switch.hidden = YES; _textField.hidden = YES; @@ -270,7 +294,7 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField - (void)textFieldDidEndEditing:(UITextField *)textField { - if (_mode == _FBTweakTableViewCellModeString) { + if (_mode == _FBTweakTableViewCellModeString || _mode == _FBTweakTableViewCellModeColor) { [self _updateValue:_textField.text primary:NO write:YES]; } else if (_mode == _FBTweakTableViewCellModeInteger) { NSNumber *number = @([_textField.text longLongValue]); @@ -334,6 +358,8 @@ - (void)_updateValue:(FBTweakValue)value primary:(BOOL)primary write:(BOOL)write if (primary) { self.detailTextLabel.text = [value description]; } + } else if (_mode == _FBTweakTableViewCellModeColor) { + [self.imageView setImage:_FBCreateColorCellsThumbnail(value, CGSizeMake(30, 30))]; } } diff --git a/FBTweakExample.xcodeproj/project.pbxproj b/FBTweakExample.xcodeproj/project.pbxproj index 691b6884..4ca6f8fc 100644 --- a/FBTweakExample.xcodeproj/project.pbxproj +++ b/FBTweakExample.xcodeproj/project.pbxproj @@ -276,6 +276,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "FBTweakExample/FBTweakExample-Prefix.pch"; INFOPLIST_FILE = "FBTweakExample/FBTweakExample-Info.plist"; + OTHER_LDFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -289,6 +290,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "FBTweakExample/FBTweakExample-Prefix.pch"; INFOPLIST_FILE = "FBTweakExample/FBTweakExample-Info.plist"; + OTHER_LDFLAGS = ""; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; diff --git a/FBTweakExample/FBAppDelegate.m b/FBTweakExample/FBAppDelegate.m index 1ae62f1b..f313c551 100644 --- a/FBTweakExample/FBAppDelegate.m +++ b/FBTweakExample/FBAppDelegate.m @@ -59,9 +59,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( _label.textColor = [UIColor blackColor]; _label.font = [UIFont systemFontOfSize:FBTweakValue(@"Content", @"Text", @"Size", 60.0)]; FBTweakBind(_label, text, @"Content", @"Text", @"String", @"Tweaks"); + FBTweakBind(_label, textColor, @"Content", @"Text", @"Color", [UIColor redColor]); FBTweakBind(_label, alpha, @"Content", @"Text", @"Alpha", 0.5, 0.0, 1.0); [_rootViewController.view addSubview:_label]; - + + FBTweakBind(_rootViewController.view, backgroundColor, @"Content", @"Background", @"Color", [UIColor whiteColor]); + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(labelTapped)]; [_label addGestureRecognizer:tapRecognizer]; diff --git a/FBTweakTests/FBTweakInlineTestsARC.m b/FBTweakTests/FBTweakInlineTestsARC.m index 839848da..f52e9f0a 100644 --- a/FBTweakTests/FBTweakInlineTestsARC.m +++ b/FBTweakTests/FBTweakInlineTestsARC.m @@ -8,6 +8,7 @@ */ #import +#import #import "FBTweakInline.h" @@ -83,6 +84,9 @@ - (void)testValueTypes __attribute__((unused)) NSString *testNSString = FBTweakValue(@"NSString", @"NSString", @"NSString", @"one"); XCTAssertEqualObjects(testNSString, @"one", @"NSString %@", testNSString); + __attribute__((unused)) UIColor *testUIColor = FBTweakValue(@"UIColor", @"UIColor", @"UIColor", [UIColor redColor], [UIColor redColor]); + XCTAssertEqualObjects(testUIColor, [UIColor redColor], @"UIColor %@", testUIColor); + __attribute__((unused)) NSString *testNSArray = FBTweakValue(@"NSArray", @"NSArray", @"NSArray", @"two", (@[@"one", @"two", @"three"])); XCTAssertEqualObjects(testNSArray, @"two", @"NSArray %@", testNSArray); diff --git a/FBTweakTests/FBTweakInlineTestsMRR.m b/FBTweakTests/FBTweakInlineTestsMRR.m index 2c6d5a7c..df7b11ff 100644 --- a/FBTweakTests/FBTweakInlineTestsMRR.m +++ b/FBTweakTests/FBTweakInlineTestsMRR.m @@ -8,6 +8,7 @@ */ #import +#import #import "FBTweakInline.h" @@ -58,6 +59,9 @@ - (void)testValueTypes __attribute__((unused)) NSString *testNSString = FBTweakValue(@"NSString", @"NSString", @"NSString", @"one"); XCTAssertEqualObjects(testNSString, @"one", @"NSString %@", testNSString); + __attribute__((unused)) UIColor *testUIColor = FBTweakValue(@"UIColor", @"UIColor", @"UIColor", [UIColor redColor], [UIColor redColor]); + XCTAssertEqualObjects([UIColor redColor], [UIColor redColor], @"UIColor %@", testUIColor); + __attribute__((unused)) NSString *testNSArray = FBTweakValue(@"NSArray", @"NSArray", @"NSArray", @"two", (@[@"one", @"two", @"three"])); XCTAssertEqualObjects(testNSArray, @"two", @"NSArray %@", testNSArray);