mirror of
https://github.com/joncardasis/ChromaColorPicker.git
synced 2024-11-25 03:32:05 +03:00
Deintegrated cocoapods as XCTest will be used. Temporarily remove legacy source from build target.
This commit is contained in:
parent
0713349ed7
commit
5a8f85fbcd
@ -15,16 +15,10 @@
|
||||
35C376D61D5CF5300069D7A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D41D5CF5300069D7A1 /* Main.storyboard */; };
|
||||
35C376D81D5CF5300069D7A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D71D5CF5300069D7A1 /* Assets.xcassets */; };
|
||||
35C376DB1D5CF5300069D7A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D91D5CF5300069D7A1 /* LaunchScreen.storyboard */; };
|
||||
5F1C3CC9B7FD9F7F4B9C94BA /* Pods_ChromaColorPicker_Tests_ChromaColorPickerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9389AEF26357BC9EC5DB8488 /* Pods_ChromaColorPicker_Tests_ChromaColorPickerTests.framework */; };
|
||||
B229F68E819E318E9A4D9B22 /* Pods_ChromaColorPicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2B0C16EA18555BA1F96C07DB /* Pods_ChromaColorPicker.framework */; };
|
||||
FC1BD8C02207D7B700817AF3 /* ChromaColorPickerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerSpec.swift */; };
|
||||
FC1BD8C02207D7B700817AF3 /* ChromaColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerTests.swift */; };
|
||||
FC1BD8C22207D7B700817AF3 /* ChromaColorPicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3503B82F1F2689BC00750356 /* ChromaColorPicker.framework */; };
|
||||
FC1BD8CE2207FCE100817AF3 /* ChromaHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8C82207FCE100817AF3 /* ChromaHandle.swift */; };
|
||||
FC1BD8CF2207FCE100817AF3 /* ChromaShadeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8C92207FCE100817AF3 /* ChromaShadeSlider.swift */; };
|
||||
FC1BD8D02207FCE100817AF3 /* ChromaColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8CA2207FCE100817AF3 /* ChromaColorPicker.swift */; };
|
||||
FC1BD8D12207FCE100817AF3 /* UIColor+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8CB2207FCE100817AF3 /* UIColor+Utilities.swift */; };
|
||||
FC1BD8D22207FCE100817AF3 /* ChromaAddButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8CC2207FCE100817AF3 /* ChromaAddButton.swift */; };
|
||||
FC1BD8D32207FCE100817AF3 /* ColorModeToggleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1BD8CD2207FCE100817AF3 /* ColorModeToggleButton.swift */; };
|
||||
FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */; };
|
||||
FCEA4E272235AAA200C0A1B6 /* ChromaColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -59,12 +53,6 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0722B34CBD5489ADC94F7B91 /* Pods-ChromaColorPickerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChromaColorPickerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ChromaColorPickerTests/Pods-ChromaColorPickerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
097AE9DDB2DC9AF89E2EDE5A /* Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
0A7B83C96B02042BB70821D7 /* Pods-ChromaColorPicker.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChromaColorPicker.release.xcconfig"; path = "Pods/Target Support Files/Pods-ChromaColorPicker/Pods-ChromaColorPicker.release.xcconfig"; sourceTree = "<group>"; };
|
||||
0A7F02F440BE6423B319B578 /* Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
2B0C16EA18555BA1F96C07DB /* Pods_ChromaColorPicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ChromaColorPicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
2E61508E4135F0CC9078125D /* Pods-ChromaColorPicker.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChromaColorPicker.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ChromaColorPicker/Pods-ChromaColorPicker.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
3503B82F1F2689BC00750356 /* ChromaColorPicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ChromaColorPicker.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3503B8311F2689BC00750356 /* ChromaColorPicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ChromaColorPicker.h; sourceTree = "<group>"; };
|
||||
3503B8321F2689BC00750356 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -75,11 +63,8 @@
|
||||
35C376D71D5CF5300069D7A1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
35C376DA1D5CF5300069D7A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
35C376DC1D5CF5300069D7A1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
91485B6B5C58777417441794 /* Pods_ChromaColorPickerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ChromaColorPickerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9389AEF26357BC9EC5DB8488 /* Pods_ChromaColorPicker_Tests_ChromaColorPickerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ChromaColorPicker_Tests_ChromaColorPickerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E0C887E58E73DFD65E9DEB81 /* Pods-ChromaColorPickerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ChromaColorPickerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ChromaColorPickerTests/Pods-ChromaColorPickerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FC1BD8BD2207D7B700817AF3 /* ChromaColorPickerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChromaColorPickerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorPickerSpec.swift; sourceTree = "<group>"; };
|
||||
FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorPickerTests.swift; sourceTree = "<group>"; };
|
||||
FC1BD8C12207D7B700817AF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
FC1BD8C82207FCE100817AF3 /* ChromaHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChromaHandle.swift; sourceTree = "<group>"; };
|
||||
FC1BD8C92207FCE100817AF3 /* ChromaShadeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChromaShadeSlider.swift; sourceTree = "<group>"; };
|
||||
@ -87,6 +72,8 @@
|
||||
FC1BD8CB2207FCE100817AF3 /* UIColor+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FC1BD8CC2207FCE100817AF3 /* ChromaAddButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChromaAddButton.swift; sourceTree = "<group>"; };
|
||||
FC1BD8CD2207FCE100817AF3 /* ColorModeToggleButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorModeToggleButton.swift; sourceTree = "<group>"; };
|
||||
FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWheelView.swift; sourceTree = "<group>"; };
|
||||
FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorPicker.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -94,7 +81,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B229F68E819E318E9A4D9B22 /* Pods_ChromaColorPicker.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -111,7 +97,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FC1BD8C22207D7B700817AF3 /* ChromaColorPicker.framework in Frameworks */,
|
||||
5F1C3CC9B7FD9F7F4B9C94BA /* Pods_ChromaColorPicker_Tests_ChromaColorPickerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -121,14 +106,11 @@
|
||||
3503B8301F2689BC00750356 /* Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FC1BD8CC2207FCE100817AF3 /* ChromaAddButton.swift */,
|
||||
FC1BD8CA2207FCE100817AF3 /* ChromaColorPicker.swift */,
|
||||
FC1BD8C82207FCE100817AF3 /* ChromaHandle.swift */,
|
||||
FC1BD8C92207FCE100817AF3 /* ChromaShadeSlider.swift */,
|
||||
FC1BD8CD2207FCE100817AF3 /* ColorModeToggleButton.swift */,
|
||||
FC1BD8CB2207FCE100817AF3 /* UIColor+Utilities.swift */,
|
||||
FCCA427822601F8800BE2FF9 /* Legacy */,
|
||||
3503B8311F2689BC00750356 /* ChromaColorPicker.h */,
|
||||
3503B8321F2689BC00750356 /* Info.plist */,
|
||||
FCEA4E262235AAA200C0A1B6 /* ChromaColorPicker.swift */,
|
||||
FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */,
|
||||
);
|
||||
path = Source;
|
||||
sourceTree = "<group>";
|
||||
@ -140,8 +122,6 @@
|
||||
3503B8301F2689BC00750356 /* Source */,
|
||||
FC1BD8BE2207D7B700817AF3 /* Tests */,
|
||||
35C376CE1D5CF5300069D7A1 /* Products */,
|
||||
A890856BC0D0DD14D667AEA1 /* Pods */,
|
||||
A18D889CBAEF3E0FD0A65ADB /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -168,38 +148,28 @@
|
||||
path = Example;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A18D889CBAEF3E0FD0A65ADB /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
91485B6B5C58777417441794 /* Pods_ChromaColorPickerTests.framework */,
|
||||
2B0C16EA18555BA1F96C07DB /* Pods_ChromaColorPicker.framework */,
|
||||
9389AEF26357BC9EC5DB8488 /* Pods_ChromaColorPicker_Tests_ChromaColorPickerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A890856BC0D0DD14D667AEA1 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E0C887E58E73DFD65E9DEB81 /* Pods-ChromaColorPickerTests.debug.xcconfig */,
|
||||
0722B34CBD5489ADC94F7B91 /* Pods-ChromaColorPickerTests.release.xcconfig */,
|
||||
2E61508E4135F0CC9078125D /* Pods-ChromaColorPicker.debug.xcconfig */,
|
||||
0A7B83C96B02042BB70821D7 /* Pods-ChromaColorPicker.release.xcconfig */,
|
||||
097AE9DDB2DC9AF89E2EDE5A /* Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.debug.xcconfig */,
|
||||
0A7F02F440BE6423B319B578 /* Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FC1BD8BE2207D7B700817AF3 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerSpec.swift */,
|
||||
FC1BD8BF2207D7B700817AF3 /* ChromaColorPickerTests.swift */,
|
||||
FC1BD8C12207D7B700817AF3 /* Info.plist */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FCCA427822601F8800BE2FF9 /* Legacy */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FC1BD8CC2207FCE100817AF3 /* ChromaAddButton.swift */,
|
||||
FC1BD8CA2207FCE100817AF3 /* ChromaColorPicker.swift */,
|
||||
FC1BD8C82207FCE100817AF3 /* ChromaHandle.swift */,
|
||||
FC1BD8C92207FCE100817AF3 /* ChromaShadeSlider.swift */,
|
||||
FC1BD8CD2207FCE100817AF3 /* ColorModeToggleButton.swift */,
|
||||
FC1BD8CB2207FCE100817AF3 /* UIColor+Utilities.swift */,
|
||||
);
|
||||
path = Legacy;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@ -218,12 +188,10 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3503B83A1F2689BC00750356 /* Build configuration list for PBXNativeTarget "ChromaColorPicker" */;
|
||||
buildPhases = (
|
||||
15254D6200BBBBE8871C2B25 /* [CP] Check Pods Manifest.lock */,
|
||||
3503B82A1F2689BC00750356 /* Sources */,
|
||||
3503B82B1F2689BC00750356 /* Frameworks */,
|
||||
3503B82C1F2689BC00750356 /* Headers */,
|
||||
3503B82D1F2689BC00750356 /* Resources */,
|
||||
F26B6B5B0BB0F852318ADC06 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -257,12 +225,9 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = FC1BD8C52207D7B700817AF3 /* Build configuration list for PBXNativeTarget "ChromaColorPickerTests" */;
|
||||
buildPhases = (
|
||||
BE626E9F9FF56ED9E5B4A858 /* [CP] Check Pods Manifest.lock */,
|
||||
FC1BD8B92207D7B700817AF3 /* Sources */,
|
||||
FC1BD8BA2207D7B700817AF3 /* Frameworks */,
|
||||
FC1BD8BB2207D7B700817AF3 /* Resources */,
|
||||
77B1C2C1CC97939E1F7DB18E /* [CP] Embed Pods Frameworks */,
|
||||
D1F2D465C9F2D952C7B15251 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -291,7 +256,7 @@
|
||||
};
|
||||
35C376CC1D5CF5300069D7A1 = {
|
||||
CreatedOnToolsVersion = 7.3;
|
||||
DevelopmentTeam = 43DKZUY8C6;
|
||||
DevelopmentTeam = 9H97MWKJ22;
|
||||
LastSwiftMigration = 1000;
|
||||
};
|
||||
FC1BD8BC2207D7B700817AF3 = {
|
||||
@ -348,115 +313,13 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
15254D6200BBBBE8871C2B25 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
77B1C2C1CC97939E1F7DB18E /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
BE626E9F9FF56ED9E5B4A858 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
D1F2D465C9F2D952C7B15251 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests/Pods-ChromaColorPicker-Tests-ChromaColorPickerTests-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
F26B6B5B0BB0F852318ADC06 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ChromaColorPicker/Pods-ChromaColorPicker-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
3503B82A1F2689BC00750356 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FC1BD8CF2207FCE100817AF3 /* ChromaShadeSlider.swift in Sources */,
|
||||
FC1BD8CE2207FCE100817AF3 /* ChromaHandle.swift in Sources */,
|
||||
FC1BD8D32207FCE100817AF3 /* ColorModeToggleButton.swift in Sources */,
|
||||
FC1BD8D12207FCE100817AF3 /* UIColor+Utilities.swift in Sources */,
|
||||
FC1BD8D22207FCE100817AF3 /* ChromaAddButton.swift in Sources */,
|
||||
FC1BD8D02207FCE100817AF3 /* ChromaColorPicker.swift in Sources */,
|
||||
FCEA4E272235AAA200C0A1B6 /* ChromaColorPicker.swift in Sources */,
|
||||
FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -473,7 +336,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FC1BD8C02207D7B700817AF3 /* ChromaColorPickerSpec.swift in Sources */,
|
||||
FC1BD8C02207D7B700817AF3 /* ChromaColorPickerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -514,7 +377,6 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
3503B8381F2689BC00750356 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 2E61508E4135F0CC9078125D /* Pods-ChromaColorPicker.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
@ -543,7 +405,6 @@
|
||||
};
|
||||
3503B8391F2689BC00750356 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 0A7B83C96B02042BB70821D7 /* Pods-ChromaColorPicker.release.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
@ -681,7 +542,9 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
DEFINES_MODULE = NO;
|
||||
DEVELOPMENT_TEAM = 9H97MWKJ22;
|
||||
INFOPLIST_FILE = Example/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jonathancardasis.ChromaColorPickerExample;
|
||||
PRODUCT_NAME = ChromaColorPickerExample;
|
||||
@ -696,7 +559,9 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
DEFINES_MODULE = NO;
|
||||
DEVELOPMENT_TEAM = 9H97MWKJ22;
|
||||
INFOPLIST_FILE = Example/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jonathancardasis.ChromaColorPickerExample;
|
||||
PRODUCT_NAME = ChromaColorPickerExample;
|
||||
@ -706,7 +571,6 @@
|
||||
};
|
||||
FC1BD8C62207D7B700817AF3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 097AE9DDB2DC9AF89E2EDE5A /* Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
@ -733,7 +597,6 @@
|
||||
};
|
||||
FC1BD8C72207D7B700817AF3 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 0A7F02F440BE6423B319B578 /* Pods-ChromaColorPicker-Tests-ChromaColorPickerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:ChromaColorPicker.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -9,6 +9,30 @@
|
||||
import UIKit
|
||||
import ChromaColorPicker
|
||||
|
||||
class ViewController: UIViewController {
|
||||
@IBOutlet weak var colorDisplayView: UIView!
|
||||
|
||||
let colorPicker = ChromaColorPicker2()
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
||||
colorPicker.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(colorPicker)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
colorPicker.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
colorPicker.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
colorPicker.widthAnchor.constraint(equalToConstant: 400),
|
||||
colorPicker.heightAnchor.constraint(equalToConstant: 400)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
class ViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var colorDisplayView: UIView!
|
||||
@ -61,3 +85,4 @@ extension ViewController: ChromaColorPickerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
11
Podfile
11
Podfile
@ -1,11 +0,0 @@
|
||||
use_frameworks!
|
||||
|
||||
target "ChromaColorPicker" do
|
||||
|
||||
abstract_target 'Tests' do
|
||||
target "ChromaColorPickerTests"
|
||||
|
||||
pod 'Quick'
|
||||
pod 'Nimble'
|
||||
end
|
||||
end
|
15
Podfile.lock
15
Podfile.lock
@ -1,15 +0,0 @@
|
||||
PODS:
|
||||
- Nimble (7.3.1)
|
||||
- Quick (1.3.2)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Nimble
|
||||
- Quick
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae
|
||||
Quick: 2623cb30d7a7f41ca62f684f679586558f483d46
|
||||
|
||||
PODFILE CHECKSUM: d1da65bf2e6463426e9b04a5b850dc7caffbc72b
|
||||
|
||||
COCOAPODS: 1.2.1
|
@ -1,69 +1,45 @@
|
||||
//
|
||||
// ChromaColorPicker.swift
|
||||
// ChromaColorPicker2.swift
|
||||
// ChromaColorPicker
|
||||
//
|
||||
// Copyright © 2016 Jonathan Cardasis. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
// Created by Jon Cardasis on 3/10/19.
|
||||
// Copyright © 2019 Jonathan Cardasis. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Accelerate
|
||||
|
||||
public protocol ChromaColorPickerDelegate {
|
||||
/* Called when the user taps the add button in the center */
|
||||
/// When the control has changed
|
||||
func colorPickerDidChooseColor(_ colorPicker: ChromaColorPicker, color: UIColor)
|
||||
}
|
||||
|
||||
open class ChromaColorPicker: UIControl {
|
||||
open var hexLabel: UILabel!
|
||||
open var shadeSlider: ChromaShadeSlider!
|
||||
open var handleView: ChromaHandle!
|
||||
open var handleLine: CAShapeLayer!
|
||||
open var addButton: ChromaAddButton!
|
||||
open var colorToggleButton: ColorModeToggleButton!
|
||||
|
||||
private var modeIsGrayscale: Bool {
|
||||
return colorToggleButton.colorState == .grayscale
|
||||
}
|
||||
private enum ColorSpace {
|
||||
case rainbow
|
||||
case grayscale
|
||||
func timeElapsedInSecondsWhenRunningCode(operation: ()->()) -> Double { //TEMP - DEBUG
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
operation()
|
||||
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
|
||||
return Double(timeElapsed)
|
||||
}
|
||||
|
||||
open private(set) var currentColor = UIColor.red
|
||||
open var supportsShadesOfGray: Bool = false {
|
||||
didSet {
|
||||
if supportsShadesOfGray {
|
||||
colorToggleButton.isHidden = false
|
||||
}
|
||||
else {
|
||||
colorToggleButton.isHidden = true
|
||||
|
||||
public class ChromaColorHandle {
|
||||
/// Current selected color of the handle.
|
||||
fileprivate(set) var color: UIColor
|
||||
|
||||
/// An image to display above the handle.
|
||||
var popoverImage: UIImage?
|
||||
|
||||
/// A view to display above the handle. Overrides any provided `popoverImage`.
|
||||
var popoverView: UIView?
|
||||
|
||||
init(color: UIColor) {
|
||||
self.color = color
|
||||
}
|
||||
}
|
||||
}
|
||||
open var delegate: ChromaColorPickerDelegate?
|
||||
open var currentAngle: Float = 0
|
||||
open private(set) var radius: CGFloat = 0
|
||||
open var stroke: CGFloat = 1
|
||||
open var padding: CGFloat = 15
|
||||
open var handleSize: CGSize{
|
||||
get{ return CGSize(width: self.bounds.width * 0.1, height: self.bounds.height * 0.1) }
|
||||
}
|
||||
|
||||
@IBDesignable
|
||||
public class ChromaColorPicker: UIControl {
|
||||
|
||||
|
||||
//MARK: - Initialization
|
||||
override public init(frame: CGRect) {
|
||||
@ -76,445 +52,250 @@ open class ChromaColorPicker: UIControl {
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit(){
|
||||
public override func setNeedsDisplay() {
|
||||
super.setNeedsDisplay()
|
||||
print("called")
|
||||
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let minDimensionSize = min(bounds.width, bounds.height)
|
||||
let colorWheelImage = makeColorWheel(radius: minDimensionSize * 3.0) // TEMP?
|
||||
colorWheelImageView.image = colorWheelImage
|
||||
}
|
||||
|
||||
public func addHandle(at color: UIColor? = nil) -> ChromaColorHandle {
|
||||
return ChromaColorHandle(color: color ?? defaultHandleColorPosition)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
internal var colorWheelImageView: UIImageView!
|
||||
|
||||
internal func commonInit() {
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.masksToBounds = false
|
||||
setupColorWheel()
|
||||
applySmoothingMaskToColorWheel()
|
||||
setupGestures()
|
||||
|
||||
let minDimension = min(self.bounds.size.width, self.bounds.size.height)
|
||||
radius = minDimension/2 - handleSize.width/2
|
||||
|
||||
/* Setup Handle */
|
||||
handleView = ChromaHandle(frame: CGRect(x: 0,y: 0, width: handleSize.width, height: handleSize.height))
|
||||
handleView.shadowOffset = CGSize(width: 0,height: 2)
|
||||
|
||||
/* Setup pan gesture for handle */
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ChromaColorPicker.handleWasMoved(_:)))
|
||||
handleView.addGestureRecognizer(panRecognizer)
|
||||
|
||||
/* Setup Add Button */
|
||||
addButton = ChromaAddButton()
|
||||
self.layoutAddButton() //layout frame
|
||||
addButton.addTarget(self, action: #selector(ChromaColorPicker.addButtonPressed(_:)), for: .touchUpInside)
|
||||
|
||||
/* Setup Handle Line */
|
||||
handleLine = CAShapeLayer()
|
||||
handleLine.lineWidth = 2
|
||||
handleLine.strokeColor = UIColor.white.withAlphaComponent(0.2).cgColor
|
||||
|
||||
/* Setup Color Hex Label */
|
||||
hexLabel = UILabel()
|
||||
self.layoutHexLabel() //layout frame
|
||||
hexLabel.layer.cornerRadius = 2
|
||||
hexLabel.adjustsFontSizeToFitWidth = true
|
||||
hexLabel.textAlignment = .center
|
||||
hexLabel.textColor = UIColor(red: 51/255.0, green:51/255.0, blue: 51/255.0, alpha: 0.65)
|
||||
|
||||
/* Setup Shade Slider */
|
||||
shadeSlider = ChromaShadeSlider()
|
||||
shadeSlider.delegate = self
|
||||
self.layoutShadeSlider()
|
||||
shadeSlider.addTarget(self, action: #selector(ChromaColorPicker.sliderEditingDidEnd(_:)), for: .editingDidEnd)
|
||||
|
||||
/* Setup Color Mode Toggle Button */
|
||||
colorToggleButton = ColorModeToggleButton()
|
||||
self.layoutColorToggleButton() //layout frame
|
||||
colorToggleButton.colorState = .hue // Default as starting state is hue
|
||||
colorToggleButton.addTarget(self, action: #selector(togglePickerColorMode), for: .touchUpInside)
|
||||
colorToggleButton.isHidden = !supportsShadesOfGray // default to hiding if not supported
|
||||
|
||||
/* Add components to view */
|
||||
self.layer.addSublayer(handleLine)
|
||||
self.addSubview(shadeSlider)
|
||||
self.addSubview(hexLabel)
|
||||
self.addSubview(handleView)
|
||||
self.addSubview(addButton)
|
||||
self.addSubview(colorToggleButton)
|
||||
// DEBUG - BENCHMARK
|
||||
func timeElapsedInSecondsWhenRunningCode(operation: ()->()) -> Double {
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
operation()
|
||||
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
|
||||
return Double(timeElapsed)
|
||||
}
|
||||
|
||||
override open func willMove(toSuperview newSuperview: UIView?) {
|
||||
/* Get the starting color */
|
||||
currentColor = colorOnWheelFromAngle(currentAngle)
|
||||
handleView.center = positionOnWheelFromAngle(currentAngle) //update pos for angle
|
||||
self.layoutHandleLine() //layout the lines positioning
|
||||
|
||||
handleView.color = currentColor
|
||||
addButton.color = currentColor
|
||||
shadeSlider.primaryColor = currentColor
|
||||
self.updateHexLabel() //update for hex value
|
||||
var times = [Double]()
|
||||
for _ in 0..<10 {
|
||||
let time = timeElapsedInSecondsWhenRunningCode {
|
||||
_ = makeColorWheel(radius: 1400)
|
||||
}
|
||||
times.append(time)
|
||||
print(time)
|
||||
}
|
||||
|
||||
open func adjustToColor(_ color: UIColor){
|
||||
/* Apply saturation and brightness from previous color to current one */
|
||||
var saturation: CGFloat = 0.0
|
||||
var brightness: CGFloat = 0.0
|
||||
var hue: CGFloat = 0.0
|
||||
var alpha: CGFloat = 0.0
|
||||
let avgTime = times.reduce(0, +) / Double(times.count)
|
||||
print("\n\nAvgTime: \(avgTime)")
|
||||
// END DEBUG
|
||||
}
|
||||
|
||||
color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
|
||||
let newColor = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
||||
internal func setupColorWheel() {
|
||||
colorWheelImageView = UIImageView(image: nil)
|
||||
colorWheelImageView.contentMode = .scaleAspectFit
|
||||
|
||||
/* Set the slider value for the new color and update addButton */
|
||||
shadeSlider.primaryColor = UIColor(hue: hue, saturation: 1, brightness: 1, alpha: 1) //Set a color recognzied on the color wheel
|
||||
colorWheelImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(colorWheelImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
colorWheelImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
||||
colorWheelImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
colorWheelImageView.widthAnchor.constraint(equalTo: self.widthAnchor),
|
||||
colorWheelImageView.heightAnchor.constraint(equalTo: colorWheelImageView.widthAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
/* Update the angle and currentColor */
|
||||
currentAngle = angleForColor(newColor)
|
||||
currentColor = newColor
|
||||
if brightness < 1.0 && saturation < 1.0 {
|
||||
/* Modifies the Shade Slider to handle adjusting to colors outside of the Chroma scope */
|
||||
shadeSlider.primaryColor = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1)
|
||||
shadeSlider.currentValue = 0
|
||||
} else if brightness < 1.0 { //currentValue is on the left side of the slider
|
||||
shadeSlider.currentValue = brightness-1
|
||||
/// Applys a smoothing mask to the color wheel to account for CIFilter's image dithering at the edges.
|
||||
internal func applySmoothingMaskToColorWheel() {
|
||||
|
||||
}
|
||||
|
||||
internal func setupGestures() {
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(colorWheelTapped(_:)))
|
||||
colorWheelImageView.isUserInteractionEnabled = true
|
||||
colorWheelImageView.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
@objc
|
||||
internal func colorWheelTapped(_ gesture: UITapGestureRecognizer) {
|
||||
let location = gesture.location(in: colorWheelImageView)
|
||||
let pixelColor = colorWheelImageView.getPixelColor(at: location)
|
||||
print()
|
||||
}
|
||||
|
||||
/**
|
||||
Generates a color wheel image from a given radius.
|
||||
- Parameters:
|
||||
- radius: The radius of the wheel in points. A radius of 100 would generate an
|
||||
image of 200x200 (400x400 pixels on a device with 2x scaling.)
|
||||
*/
|
||||
internal func makeColorWheel(radius: CGFloat) -> UIImage {
|
||||
let filter = CIFilter(name: "CIHueSaturationValueGradient", parameters: [
|
||||
"inputColorSpace": CGColorSpaceCreateDeviceRGB(),
|
||||
"inputDither": 0,
|
||||
"inputRadius": radius,
|
||||
"inputSoftness": 0,
|
||||
"inputValue": 1
|
||||
])!
|
||||
return UIImage(ciImage: filter.outputImage!)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
internal lazy var context = makeMetalContext()
|
||||
|
||||
internal func makeMetalContext() -> CIContext {
|
||||
let mtlDevice = MTLCreateSystemDefaultDevice()
|
||||
if let device = mtlDevice, device.supportsFeatureSet(.iOS_GPUFamily1_v1) {
|
||||
return CIContext(mtlDevice: device, options: [CIContextOption.useSoftwareRenderer: false])
|
||||
} else if let eaglContext = EAGLContext(api: .openGLES2) {
|
||||
return CIContext(eaglContext: eaglContext)
|
||||
} else {
|
||||
shadeSlider.currentValue = -(saturation-1)
|
||||
}
|
||||
shadeSlider.updateHandleLocation() //update the handle location now that the value is set
|
||||
addButton.color = newColor
|
||||
|
||||
/* Will layout based on new angle */
|
||||
self.layoutHandle()
|
||||
self.layoutHandleLine()
|
||||
self.updateHexLabel()
|
||||
}
|
||||
|
||||
//MARK: - Handle Touches
|
||||
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?){
|
||||
//Overriden to prevent uicontrolevents being called from the super
|
||||
}
|
||||
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){
|
||||
let touchPoint = touches.first!.location(in: self)
|
||||
if handleView.frame.contains(touchPoint) {
|
||||
self.sendActions(for: .touchDown)
|
||||
|
||||
/* Enlarge Animation */
|
||||
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseIn, animations: { () -> Void in
|
||||
self.handleView.transform = CGAffineTransform(scaleX: 1.45, y: 1.45)
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
//Run this animation after a pan or here if touches are released
|
||||
if handleView.transform.d > 1 { //if scale is larger than 1 (already animated)
|
||||
self.executeHandleShrinkAnimation()
|
||||
return CIContext()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleWasMoved(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch(recognizer.state){
|
||||
internal lazy var lookupImage: CGImage = {
|
||||
let colorWheelCIImage = makeColorWheel(radius: 50).ciImage!
|
||||
return context.createCGImage(colorWheelCIImage, from: colorWheelCIImage.extent)!
|
||||
}()
|
||||
|
||||
case UIGestureRecognizer.State.changed:
|
||||
let touchPosition = recognizer.location(in: self)
|
||||
self.moveHandleTowardPoint(touchPosition)
|
||||
self.sendActions(for: .touchDragInside)
|
||||
break
|
||||
internal func location(of color: UIColor) -> CGPoint? {
|
||||
// guard let image = colorWheelImageView.image else { return nil }
|
||||
//
|
||||
// let ci = image.ciImage!
|
||||
// let cgImage = context.createCGImage(ci, from: ci.extent)!
|
||||
let cgImage = self.lookupImage
|
||||
|
||||
case UIGestureRecognizer.State.ended:
|
||||
/* Shrink Animation */
|
||||
self.executeHandleShrinkAnimation()
|
||||
break
|
||||
var red : CGFloat = 0
|
||||
var green : CGFloat = 0
|
||||
var blue : CGFloat = 0
|
||||
color.getRed(&red, green: &green, blue: &blue, alpha: nil)
|
||||
|
||||
default:
|
||||
break
|
||||
let r = UInt8(red * 255.0)
|
||||
let g = UInt8(green * 255.0)
|
||||
let b = UInt8(blue * 255.0)
|
||||
|
||||
let width = 100 //Int(image.size.width)
|
||||
let height = 100 //Int(image.size.height)
|
||||
if let cfData = cgImage.dataProvider?.data, let pointer = CFDataGetBytePtr(cfData) {
|
||||
for x in 0..<width {
|
||||
for y in 0..<height {
|
||||
// TODO: do a DISTANCE calc and find closest distance
|
||||
let pixelAddress = x * 4 + y * width * 4
|
||||
if pointer.advanced(by: pixelAddress).pointee == r && //Red
|
||||
pointer.advanced(by: pixelAddress + 1).pointee == g && //Green
|
||||
pointer.advanced(by: pixelAddress + 2).pointee == b { //Blue
|
||||
print(CGPoint(x: x, y: y)) //temp
|
||||
return CGPoint(x: x, y: y)
|
||||
}
|
||||
}
|
||||
|
||||
private func executeHandleShrinkAnimation(){
|
||||
self.sendActions(for: .touchUpInside)
|
||||
self.sendActions(for: .editingDidEnd)
|
||||
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseOut, animations: { () -> Void in
|
||||
self.handleView.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
private func moveHandleTowardPoint(_ point: CGPoint){
|
||||
currentAngle = angleToCenterFromPoint(point) //Find the angle of point to the frames center
|
||||
|
||||
//Layout Handle
|
||||
self.layoutHandle()
|
||||
|
||||
//Layout Line
|
||||
self.layoutHandleLine()
|
||||
|
||||
if modeIsGrayscale {
|
||||
// If mode is grayscale do not update colors and end early
|
||||
return
|
||||
}
|
||||
|
||||
//Update color for shade slider
|
||||
shadeSlider.primaryColor = handleView.color//currentColor
|
||||
|
||||
//Update color for add button if a shade isnt selected
|
||||
if shadeSlider.currentValue == 0 {
|
||||
self.updateCurrentColor(shadeSlider.currentColor)
|
||||
return nil
|
||||
}
|
||||
|
||||
//Update Text Field display value
|
||||
self.updateHexLabel()
|
||||
}
|
||||
|
||||
@objc func addButtonPressed(_ sender: ChromaAddButton){
|
||||
//Do a 'bob' animation
|
||||
UIView.animate(withDuration: 0.2,
|
||||
delay: 0,
|
||||
options: .curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
sender.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
|
||||
}, completion: { (done) -> Void in
|
||||
UIView.animate(withDuration: 0.1, animations: { () -> Void in
|
||||
sender.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
})
|
||||
})
|
||||
|
||||
delegate?.colorPickerDidChooseColor(self, color: sender.color) //Delegate call
|
||||
}
|
||||
|
||||
@objc func sliderEditingDidEnd(_ sender: ChromaShadeSlider){
|
||||
self.sendActions(for: .editingDidEnd)
|
||||
}
|
||||
|
||||
//MARK: - Drawing
|
||||
override open func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
let ctx = UIGraphicsGetCurrentContext()
|
||||
let colorSpace: ColorSpace = (modeIsGrayscale) ? .grayscale : .rainbow
|
||||
|
||||
drawCircleRing(in: ctx, outerRadius: radius - padding, innerRadius: radius - stroke - padding, resolution: 1, colorSpace: colorSpace)
|
||||
}
|
||||
|
||||
/*
|
||||
Resolution should be between 0.1 and 1
|
||||
colorSpace - either rainbow or grayscale
|
||||
*/
|
||||
private func drawCircleRing(in context: CGContext?, outerRadius: CGFloat, innerRadius: CGFloat, resolution: Float, colorSpace: ColorSpace){
|
||||
context?.saveGState()
|
||||
context?.translateBy(x: self.bounds.midX, y: self.bounds.midY) //Move context to center
|
||||
|
||||
let subdivisions:CGFloat = CGFloat(resolution * 512) //Max subdivisions of 512
|
||||
|
||||
let innerHeight = (CGFloat.pi*innerRadius)/subdivisions //height of the inner wall for each segment
|
||||
let outterHeight = (CGFloat.pi*outerRadius)/subdivisions
|
||||
|
||||
let segment = UIBezierPath()
|
||||
segment.move(to: CGPoint(x: innerRadius, y: -innerHeight/2))
|
||||
segment.addLine(to: CGPoint(x: innerRadius, y: innerHeight/2))
|
||||
segment.addLine(to: CGPoint(x: outerRadius, y: outterHeight/2))
|
||||
segment.addLine(to: CGPoint(x: outerRadius, y: -outterHeight/2))
|
||||
segment.close()
|
||||
|
||||
|
||||
//Draw each segment and rotate around the center
|
||||
for i in 0 ..< Int(ceil(subdivisions)) {
|
||||
if modeIsGrayscale {
|
||||
UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1).set() //Gray
|
||||
}
|
||||
else { // Draw rainbow
|
||||
UIColor(hue: CGFloat(i)/subdivisions, saturation: 1, brightness: 1, alpha: 1).set()
|
||||
}
|
||||
|
||||
segment.fill()
|
||||
let lineTailSpace = (CGFloat.pi*2)*outerRadius/subdivisions //The amount of space between the tails of each segment
|
||||
segment.lineWidth = lineTailSpace //allows for seemless scaling
|
||||
segment.stroke()
|
||||
private let defaultHandleColorPosition: UIColor = .black
|
||||
|
||||
//Rotate to correct location
|
||||
let rotate = CGAffineTransform(rotationAngle: -((CGFloat.pi*2)/subdivisions)) //rotates each segment
|
||||
segment.apply(rotate)
|
||||
|
||||
internal extension UIImageView {
|
||||
internal func getPixelColor(at point: CGPoint) -> UIColor {
|
||||
let pixel = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: 4)
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
|
||||
guard let context = CGContext(
|
||||
data: pixel,
|
||||
width: 1,
|
||||
height: 1,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: 4,
|
||||
space: colorSpace,
|
||||
bitmapInfo: bitmapInfo.rawValue
|
||||
) else {
|
||||
return UIColor.white
|
||||
}
|
||||
|
||||
context?.translateBy(x: -self.bounds.midX, y: -self.bounds.midY) //Move context back to original position
|
||||
context?.restoreGState()
|
||||
}
|
||||
context.translateBy(x: -point.x, y: -point.y)
|
||||
layer.render(in: context)
|
||||
let color = UIColor(
|
||||
red: CGFloat(pixel[0]) / 255.0,
|
||||
green: CGFloat(pixel[1]) / 255.0,
|
||||
blue: CGFloat(pixel[2]) / 255.0,
|
||||
alpha: CGFloat(pixel[3]) / 255.0
|
||||
)
|
||||
|
||||
|
||||
//MARK: - Layout Updates
|
||||
/* Re-layout view and all its subview and drawings */
|
||||
open func layout() {
|
||||
self.setNeedsDisplay() //mark view as dirty
|
||||
|
||||
let minDimension = min(self.bounds.size.width, self.bounds.size.height)
|
||||
radius = minDimension/2 - handleSize.width/2 //create radius for new size
|
||||
|
||||
self.layoutAddButton()
|
||||
|
||||
//Update handle's size
|
||||
handleView.frame = CGRect(origin: .zero, size: handleSize)
|
||||
self.layoutHandle()
|
||||
|
||||
//Ensure colors are updated
|
||||
self.updateCurrentColor(handleView.color)
|
||||
shadeSlider.primaryColor = handleView.color
|
||||
|
||||
self.layoutShadeSlider()
|
||||
self.layoutHandleLine()
|
||||
self.layoutHexLabel()
|
||||
self.layoutColorToggleButton()
|
||||
}
|
||||
|
||||
open func layoutAddButton(){
|
||||
let addButtonSize = CGSize(width: self.bounds.width/5, height: self.bounds.height/5)
|
||||
addButton.frame = CGRect(x: self.bounds.midX - addButtonSize.width/2, y: self.bounds.midY - addButtonSize.height/2, width: addButtonSize.width, height: addButtonSize.height)
|
||||
}
|
||||
|
||||
/*
|
||||
Update the handleView's position and color for the currentAngle
|
||||
*/
|
||||
func layoutHandle(){
|
||||
let angle = currentAngle //Preserve value in case it changes
|
||||
let newPosition = positionOnWheelFromAngle(angle) //find the correct position on the color wheel
|
||||
|
||||
//Update handle position
|
||||
handleView.center = newPosition
|
||||
|
||||
if !modeIsGrayscale {
|
||||
//Update color for the movement when color mode is hue
|
||||
handleView.color = colorOnWheelFromAngle(angle)
|
||||
pixel.deallocate()
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Updates the line view's position for the current angle
|
||||
Pre: dependant on addButtons position & current angle
|
||||
*/
|
||||
func layoutHandleLine(){
|
||||
let linePath = UIBezierPath()
|
||||
linePath.move(to: addButton.center)
|
||||
linePath.addLine(to: positionOnWheelFromAngle(currentAngle))
|
||||
handleLine.path = linePath.cgPath
|
||||
}
|
||||
extension UIImage {
|
||||
|
||||
/*
|
||||
Pre: dependant on addButtons position
|
||||
*/
|
||||
func layoutHexLabel(){
|
||||
hexLabel.frame = CGRect(x: 0, y: 0, width: addButton.bounds.width*1.5, height: addButton.bounds.height/3)
|
||||
hexLabel.center = CGPoint(x: self.bounds.midX, y: (addButton.frame.origin.y + (padding + handleView.frame.height/2 + stroke/2))/1.75) //Divided by 1.75 not 2 to make it a bit lower
|
||||
hexLabel.font = UIFont(name: "Menlo-Regular", size: hexLabel.bounds.height)
|
||||
}
|
||||
func resized(newWidth: CGFloat, context: CIContext) -> UIImage? {
|
||||
let scale = newWidth / self.size.width
|
||||
let newHeight = self.size.height * scale
|
||||
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
|
||||
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
|
||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
/*
|
||||
Pre: dependant on radius
|
||||
*/
|
||||
func layoutShadeSlider(){
|
||||
/* Calculate proper length for slider */
|
||||
let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
let insideRadius = radius - padding
|
||||
|
||||
let pointLeft = CGPoint(x: centerPoint.x + insideRadius*CGFloat(cos(7*Double.pi/6)), y: centerPoint.y - insideRadius*CGFloat(sin(7*Double.pi/6)))
|
||||
let pointRight = CGPoint(x: centerPoint.x + insideRadius*CGFloat(cos(11*Double.pi/6)), y: centerPoint.y - insideRadius*CGFloat(sin(11*Double.pi/6)))
|
||||
let deltaX = pointRight.x - pointLeft.x //distance on circle between points at 7pi/6 and 11pi/6
|
||||
|
||||
|
||||
let sliderSize = CGSize(width: deltaX * 0.75, height: 0.08 * (bounds.height - padding*2))//bounds.height
|
||||
shadeSlider.frame = CGRect(x: bounds.midX - sliderSize.width/2, y: pointLeft.y - sliderSize.height/2, width: sliderSize.width, height: sliderSize.height)
|
||||
shadeSlider.handleCenterX = shadeSlider.bounds.width/2 //set handle starting position
|
||||
shadeSlider.layoutLayerFrames() //call sliders' layout function
|
||||
}
|
||||
|
||||
/*
|
||||
Pre: dependant on addButton
|
||||
*/
|
||||
func layoutColorToggleButton() {
|
||||
let inset = bounds.height/16
|
||||
colorToggleButton.frame = CGRect(x: inset, y: inset, width: addButton.frame.width/2.5, height: addButton.frame.width/2.5)
|
||||
colorToggleButton.layoutSubviews()
|
||||
}
|
||||
|
||||
func updateHexLabel(){
|
||||
hexLabel.text = "#" + currentColor.hexCode
|
||||
}
|
||||
|
||||
func updateCurrentColor(_ color: UIColor){
|
||||
currentColor = color
|
||||
addButton.color = color
|
||||
self.sendActions(for: .valueChanged)
|
||||
}
|
||||
|
||||
@objc open func togglePickerColorMode() {
|
||||
colorToggleButton.isEnabled = false // Lock
|
||||
|
||||
// Redraw Assets (i.e. Large circle ring)
|
||||
setNeedsDisplay()
|
||||
|
||||
// Update subcomponents for color change
|
||||
if modeIsGrayscale {
|
||||
//Change color of colorToggleButton to the last handle color
|
||||
let lightColor = handleView.color
|
||||
let shadedColor = handleView.color.darkerColor(0.25)
|
||||
colorToggleButton.hueColorGradientLayer.colors = [lightColor.cgColor, shadedColor.cgColor]
|
||||
|
||||
let gray = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1)
|
||||
self.handleView.color = gray
|
||||
self.updateCurrentColor(gray)
|
||||
self.updateHexLabel()
|
||||
|
||||
//Update color for shade slider
|
||||
shadeSlider.primaryColor = gray
|
||||
}
|
||||
else {
|
||||
// Update for normal rainbow
|
||||
|
||||
// Use color stored in toggle button (set above), or else default to the angle it is at
|
||||
var hueColor: UIColor
|
||||
if let storedColor = colorToggleButton.hueColorGradientLayer.colors?[0] {
|
||||
hueColor = UIColor(cgColor: (storedColor as! CGColor))
|
||||
|
||||
currentAngle = angleForColor(hueColor)
|
||||
self.layoutHandleLine()
|
||||
self.layoutHandle()
|
||||
}
|
||||
else {
|
||||
let currentAngle = self.angleToCenterFromPoint(self.handleView.center)
|
||||
hueColor = self.colorOnWheelFromAngle(currentAngle)
|
||||
}
|
||||
|
||||
self.handleView.color = hueColor
|
||||
self.updateCurrentColor(hueColor)
|
||||
self.updateHexLabel()
|
||||
|
||||
//Update color for shade slider
|
||||
shadeSlider.primaryColor = hueColor
|
||||
}
|
||||
|
||||
colorToggleButton.isEnabled = true // Unlock
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Helper Methods
|
||||
private func angleToCenterFromPoint(_ point: CGPoint) -> Float {
|
||||
let deltaX = Float(self.bounds.midX - point.x)
|
||||
let deltaY = Float(self.bounds.midY - point.y)
|
||||
let angle = atan2f(deltaX, deltaY)
|
||||
|
||||
// Convert the angle to be between 0 and 2PI
|
||||
var adjustedAngle = angle + Float.pi/2
|
||||
if (adjustedAngle < 0){ //Left side (Q2 and Q3)
|
||||
adjustedAngle += Float.pi*2
|
||||
}
|
||||
|
||||
return adjustedAngle
|
||||
}
|
||||
|
||||
/* Find the angle relative to the center of the frame and uses the angle to find what color lies there */
|
||||
private func colorOnWheelFromAngle(_ angle: Float) -> UIColor {
|
||||
return UIColor(hue: CGFloat(Double(angle)/(2*Double.pi)), saturation: 1, brightness: 1, alpha: 1)
|
||||
}
|
||||
|
||||
private func angleForColor(_ color: UIColor) -> Float {
|
||||
var hue: CGFloat = 0
|
||||
color.getHue(&hue, saturation: nil, brightness: nil, alpha: nil)
|
||||
return Float(hue * CGFloat.pi * 2)
|
||||
}
|
||||
|
||||
/* Returns a position centered on the wheel for a given angle */
|
||||
private func positionOnWheelFromAngle(_ angle: Float) -> CGPoint{
|
||||
let buffer = padding + stroke/2
|
||||
return CGPoint(x: self.bounds.midX + ((radius - buffer) * CGFloat(cos(-angle))), y: self.bounds.midY + ((radius - buffer) * CGFloat(sin(-angle))))
|
||||
return newImage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ChromaColorPicker: ChromaShadeSliderDelegate{
|
||||
public func shadeSliderChoseColor(_ slider: ChromaShadeSlider, color: UIColor) {
|
||||
self.updateCurrentColor(color) //update main controller for selected color
|
||||
self.updateHexLabel()
|
||||
extension CGImage {
|
||||
func resizeImageUsingVImage(size:CGSize) -> CGImage? {
|
||||
let cgImage = self
|
||||
var format = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: nil, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue), version: 0, decode: nil, renderingIntent: CGColorRenderingIntent.defaultIntent)
|
||||
var sourceBuffer = vImage_Buffer()
|
||||
defer {
|
||||
free(sourceBuffer.data)
|
||||
}
|
||||
var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, numericCast(kvImageNoFlags))
|
||||
guard error == kvImageNoError else { return nil }
|
||||
// create a destination buffer
|
||||
//let scale = self.scale
|
||||
let destWidth = Int(size.width)
|
||||
let destHeight = Int(size.height)
|
||||
let bytesPerPixel = self.bitsPerPixel/8
|
||||
let destBytesPerRow = destWidth * bytesPerPixel
|
||||
let destData = UnsafeMutablePointer<UInt8>.allocate(capacity: destHeight * destBytesPerRow)
|
||||
defer {
|
||||
destData.deallocate(capacity: destHeight * destBytesPerRow)
|
||||
}
|
||||
var destBuffer = vImage_Buffer(data: destData, height: vImagePixelCount(destHeight), width: vImagePixelCount(destWidth), rowBytes: destBytesPerRow)
|
||||
// scale the image
|
||||
error = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, numericCast(kvImageHighQualityResampling))
|
||||
guard error == kvImageNoError else { return nil }
|
||||
// create a CGImage from vImage_Buffer
|
||||
var destCGImage = vImageCreateCGImageFromBuffer(&destBuffer, &format, nil, nil, numericCast(kvImageNoFlags), &error)?.takeRetainedValue()
|
||||
guard error == kvImageNoError else { return nil }
|
||||
// create a UIImage
|
||||
// defer {
|
||||
// destCGImage = nil
|
||||
// }
|
||||
return destCGImage
|
||||
|
||||
// let resizedImage = destCGImage.flatMap { UIImage(cgImage: $0, scale: 0.0, orientation: self.imageOrientation) }
|
||||
// destCGImage = nil
|
||||
// return resizedImage
|
||||
}
|
||||
}
|
||||
|
13
Source/ColorWheelView.swift
Normal file
13
Source/ColorWheelView.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// ColorWheelView.swift
|
||||
// ChromaColorPicker
|
||||
//
|
||||
// Created by Jon Cardasis on 4/11/19.
|
||||
// Copyright © 2019 Jonathan Cardasis. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public class ColorWheelView: UIView {
|
||||
|
||||
}
|
520
Source/Legacy/ChromaColorPicker.swift
Normal file
520
Source/Legacy/ChromaColorPicker.swift
Normal file
@ -0,0 +1,520 @@
|
||||
//
|
||||
// ChromaColorPicker.swift
|
||||
//
|
||||
// Copyright © 2016 Jonathan Cardasis. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
public protocol ChromaColorPickerDelegate {
|
||||
/* Called when the user taps the add button in the center */
|
||||
func colorPickerDidChooseColor(_ colorPicker: ChromaColorPicker, color: UIColor)
|
||||
}
|
||||
|
||||
open class ChromaColorPicker: UIControl {
|
||||
open var hexLabel: UILabel!
|
||||
open var shadeSlider: ChromaShadeSlider!
|
||||
open var handleView: ChromaHandle!
|
||||
open var handleLine: CAShapeLayer!
|
||||
open var addButton: ChromaAddButton!
|
||||
open var colorToggleButton: ColorModeToggleButton!
|
||||
|
||||
private var modeIsGrayscale: Bool {
|
||||
return colorToggleButton.colorState == .grayscale
|
||||
}
|
||||
private enum ColorSpace {
|
||||
case rainbow
|
||||
case grayscale
|
||||
}
|
||||
|
||||
open private(set) var currentColor = UIColor.red
|
||||
open var supportsShadesOfGray: Bool = false {
|
||||
didSet {
|
||||
if supportsShadesOfGray {
|
||||
colorToggleButton.isHidden = false
|
||||
}
|
||||
else {
|
||||
colorToggleButton.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
open var delegate: ChromaColorPickerDelegate?
|
||||
open var currentAngle: Float = 0
|
||||
open private(set) var radius: CGFloat = 0
|
||||
open var stroke: CGFloat = 1
|
||||
open var padding: CGFloat = 15
|
||||
open var handleSize: CGSize{
|
||||
get{ return CGSize(width: self.bounds.width * 0.1, height: self.bounds.height * 0.1) }
|
||||
}
|
||||
|
||||
//MARK: - Initialization
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.commonInit()
|
||||
}
|
||||
|
||||
private func commonInit(){
|
||||
self.backgroundColor = UIColor.clear
|
||||
|
||||
let minDimension = min(self.bounds.size.width, self.bounds.size.height)
|
||||
radius = minDimension/2 - handleSize.width/2
|
||||
|
||||
/* Setup Handle */
|
||||
handleView = ChromaHandle(frame: CGRect(x: 0,y: 0, width: handleSize.width, height: handleSize.height))
|
||||
handleView.shadowOffset = CGSize(width: 0,height: 2)
|
||||
|
||||
/* Setup pan gesture for handle */
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ChromaColorPicker.handleWasMoved(_:)))
|
||||
handleView.addGestureRecognizer(panRecognizer)
|
||||
|
||||
/* Setup Add Button */
|
||||
addButton = ChromaAddButton()
|
||||
self.layoutAddButton() //layout frame
|
||||
addButton.addTarget(self, action: #selector(ChromaColorPicker.addButtonPressed(_:)), for: .touchUpInside)
|
||||
|
||||
/* Setup Handle Line */
|
||||
handleLine = CAShapeLayer()
|
||||
handleLine.lineWidth = 2
|
||||
handleLine.strokeColor = UIColor.white.withAlphaComponent(0.2).cgColor
|
||||
|
||||
/* Setup Color Hex Label */
|
||||
hexLabel = UILabel()
|
||||
self.layoutHexLabel() //layout frame
|
||||
hexLabel.layer.cornerRadius = 2
|
||||
hexLabel.adjustsFontSizeToFitWidth = true
|
||||
hexLabel.textAlignment = .center
|
||||
hexLabel.textColor = UIColor(red: 51/255.0, green:51/255.0, blue: 51/255.0, alpha: 0.65)
|
||||
|
||||
/* Setup Shade Slider */
|
||||
shadeSlider = ChromaShadeSlider()
|
||||
shadeSlider.delegate = self
|
||||
self.layoutShadeSlider()
|
||||
shadeSlider.addTarget(self, action: #selector(ChromaColorPicker.sliderEditingDidEnd(_:)), for: .editingDidEnd)
|
||||
|
||||
/* Setup Color Mode Toggle Button */
|
||||
colorToggleButton = ColorModeToggleButton()
|
||||
self.layoutColorToggleButton() //layout frame
|
||||
colorToggleButton.colorState = .hue // Default as starting state is hue
|
||||
colorToggleButton.addTarget(self, action: #selector(togglePickerColorMode), for: .touchUpInside)
|
||||
colorToggleButton.isHidden = !supportsShadesOfGray // default to hiding if not supported
|
||||
|
||||
/* Add components to view */
|
||||
self.layer.addSublayer(handleLine)
|
||||
self.addSubview(shadeSlider)
|
||||
self.addSubview(hexLabel)
|
||||
self.addSubview(handleView)
|
||||
self.addSubview(addButton)
|
||||
self.addSubview(colorToggleButton)
|
||||
}
|
||||
|
||||
override open func willMove(toSuperview newSuperview: UIView?) {
|
||||
/* Get the starting color */
|
||||
currentColor = colorOnWheelFromAngle(currentAngle)
|
||||
handleView.center = positionOnWheelFromAngle(currentAngle) //update pos for angle
|
||||
self.layoutHandleLine() //layout the lines positioning
|
||||
|
||||
handleView.color = currentColor
|
||||
addButton.color = currentColor
|
||||
shadeSlider.primaryColor = currentColor
|
||||
self.updateHexLabel() //update for hex value
|
||||
}
|
||||
|
||||
open func adjustToColor(_ color: UIColor){
|
||||
/* Apply saturation and brightness from previous color to current one */
|
||||
var saturation: CGFloat = 0.0
|
||||
var brightness: CGFloat = 0.0
|
||||
var hue: CGFloat = 0.0
|
||||
var alpha: CGFloat = 0.0
|
||||
|
||||
color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
|
||||
let newColor = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
||||
|
||||
/* Set the slider value for the new color and update addButton */
|
||||
shadeSlider.primaryColor = UIColor(hue: hue, saturation: 1, brightness: 1, alpha: 1) //Set a color recognzied on the color wheel
|
||||
|
||||
/* Update the angle and currentColor */
|
||||
currentAngle = angleForColor(newColor)
|
||||
currentColor = newColor
|
||||
if brightness < 1.0 && saturation < 1.0 {
|
||||
/* Modifies the Shade Slider to handle adjusting to colors outside of the Chroma scope */
|
||||
shadeSlider.primaryColor = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1)
|
||||
shadeSlider.currentValue = 0
|
||||
} else if brightness < 1.0 { //currentValue is on the left side of the slider
|
||||
shadeSlider.currentValue = brightness-1
|
||||
}else{
|
||||
shadeSlider.currentValue = -(saturation-1)
|
||||
}
|
||||
shadeSlider.updateHandleLocation() //update the handle location now that the value is set
|
||||
addButton.color = newColor
|
||||
|
||||
/* Will layout based on new angle */
|
||||
self.layoutHandle()
|
||||
self.layoutHandleLine()
|
||||
self.updateHexLabel()
|
||||
}
|
||||
|
||||
//MARK: - Handle Touches
|
||||
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?){
|
||||
//Overriden to prevent uicontrolevents being called from the super
|
||||
}
|
||||
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){
|
||||
let touchPoint = touches.first!.location(in: self)
|
||||
if handleView.frame.contains(touchPoint) {
|
||||
self.sendActions(for: .touchDown)
|
||||
|
||||
/* Enlarge Animation */
|
||||
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseIn, animations: { () -> Void in
|
||||
self.handleView.transform = CGAffineTransform(scaleX: 1.45, y: 1.45)
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
//Run this animation after a pan or here if touches are released
|
||||
if handleView.transform.d > 1 { //if scale is larger than 1 (already animated)
|
||||
self.executeHandleShrinkAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleWasMoved(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch(recognizer.state){
|
||||
|
||||
case UIGestureRecognizer.State.changed:
|
||||
let touchPosition = recognizer.location(in: self)
|
||||
self.moveHandleTowardPoint(touchPosition)
|
||||
self.sendActions(for: .touchDragInside)
|
||||
break
|
||||
|
||||
case UIGestureRecognizer.State.ended:
|
||||
/* Shrink Animation */
|
||||
self.executeHandleShrinkAnimation()
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func executeHandleShrinkAnimation(){
|
||||
self.sendActions(for: .touchUpInside)
|
||||
self.sendActions(for: .editingDidEnd)
|
||||
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseOut, animations: { () -> Void in
|
||||
self.handleView.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
}, completion: nil)
|
||||
}
|
||||
|
||||
private func moveHandleTowardPoint(_ point: CGPoint){
|
||||
currentAngle = angleToCenterFromPoint(point) //Find the angle of point to the frames center
|
||||
|
||||
//Layout Handle
|
||||
self.layoutHandle()
|
||||
|
||||
//Layout Line
|
||||
self.layoutHandleLine()
|
||||
|
||||
if modeIsGrayscale {
|
||||
// If mode is grayscale do not update colors and end early
|
||||
return
|
||||
}
|
||||
|
||||
//Update color for shade slider
|
||||
shadeSlider.primaryColor = handleView.color//currentColor
|
||||
|
||||
//Update color for add button if a shade isnt selected
|
||||
if shadeSlider.currentValue == 0 {
|
||||
self.updateCurrentColor(shadeSlider.currentColor)
|
||||
}
|
||||
|
||||
//Update Text Field display value
|
||||
self.updateHexLabel()
|
||||
}
|
||||
|
||||
@objc func addButtonPressed(_ sender: ChromaAddButton){
|
||||
//Do a 'bob' animation
|
||||
UIView.animate(withDuration: 0.2,
|
||||
delay: 0,
|
||||
options: .curveEaseIn,
|
||||
animations: { () -> Void in
|
||||
sender.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
|
||||
}, completion: { (done) -> Void in
|
||||
UIView.animate(withDuration: 0.1, animations: { () -> Void in
|
||||
sender.transform = CGAffineTransform(scaleX: 1, y: 1)
|
||||
})
|
||||
})
|
||||
|
||||
delegate?.colorPickerDidChooseColor(self, color: sender.color) //Delegate call
|
||||
}
|
||||
|
||||
@objc func sliderEditingDidEnd(_ sender: ChromaShadeSlider){
|
||||
self.sendActions(for: .editingDidEnd)
|
||||
}
|
||||
|
||||
//MARK: - Drawing
|
||||
override open func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
let ctx = UIGraphicsGetCurrentContext()
|
||||
let colorSpace: ColorSpace = (modeIsGrayscale) ? .grayscale : .rainbow
|
||||
|
||||
drawCircleRing(in: ctx, outerRadius: radius - padding, innerRadius: radius - stroke - padding, resolution: 1, colorSpace: colorSpace)
|
||||
}
|
||||
|
||||
/*
|
||||
Resolution should be between 0.1 and 1
|
||||
colorSpace - either rainbow or grayscale
|
||||
*/
|
||||
private func drawCircleRing(in context: CGContext?, outerRadius: CGFloat, innerRadius: CGFloat, resolution: Float, colorSpace: ColorSpace){
|
||||
context?.saveGState()
|
||||
context?.translateBy(x: self.bounds.midX, y: self.bounds.midY) //Move context to center
|
||||
|
||||
let subdivisions:CGFloat = CGFloat(resolution * 512) //Max subdivisions of 512
|
||||
|
||||
let innerHeight = (CGFloat.pi*innerRadius)/subdivisions //height of the inner wall for each segment
|
||||
let outterHeight = (CGFloat.pi*outerRadius)/subdivisions
|
||||
|
||||
let segment = UIBezierPath()
|
||||
segment.move(to: CGPoint(x: innerRadius, y: -innerHeight/2))
|
||||
segment.addLine(to: CGPoint(x: innerRadius, y: innerHeight/2))
|
||||
segment.addLine(to: CGPoint(x: outerRadius, y: outterHeight/2))
|
||||
segment.addLine(to: CGPoint(x: outerRadius, y: -outterHeight/2))
|
||||
segment.close()
|
||||
|
||||
|
||||
//Draw each segment and rotate around the center
|
||||
for i in 0 ..< Int(ceil(subdivisions)) {
|
||||
if modeIsGrayscale {
|
||||
UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1).set() //Gray
|
||||
}
|
||||
else { // Draw rainbow
|
||||
UIColor(hue: CGFloat(i)/subdivisions, saturation: 1, brightness: 1, alpha: 1).set()
|
||||
}
|
||||
|
||||
segment.fill()
|
||||
let lineTailSpace = (CGFloat.pi*2)*outerRadius/subdivisions //The amount of space between the tails of each segment
|
||||
segment.lineWidth = lineTailSpace //allows for seemless scaling
|
||||
segment.stroke()
|
||||
|
||||
//Rotate to correct location
|
||||
let rotate = CGAffineTransform(rotationAngle: -((CGFloat.pi*2)/subdivisions)) //rotates each segment
|
||||
segment.apply(rotate)
|
||||
}
|
||||
|
||||
context?.translateBy(x: -self.bounds.midX, y: -self.bounds.midY) //Move context back to original position
|
||||
context?.restoreGState()
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Layout Updates
|
||||
/* Re-layout view and all its subview and drawings */
|
||||
open func layout() {
|
||||
self.setNeedsDisplay() //mark view as dirty
|
||||
|
||||
let minDimension = min(self.bounds.size.width, self.bounds.size.height)
|
||||
radius = minDimension/2 - handleSize.width/2 //create radius for new size
|
||||
|
||||
self.layoutAddButton()
|
||||
|
||||
//Update handle's size
|
||||
handleView.frame = CGRect(origin: .zero, size: handleSize)
|
||||
self.layoutHandle()
|
||||
|
||||
//Ensure colors are updated
|
||||
self.updateCurrentColor(handleView.color)
|
||||
shadeSlider.primaryColor = handleView.color
|
||||
|
||||
self.layoutShadeSlider()
|
||||
self.layoutHandleLine()
|
||||
self.layoutHexLabel()
|
||||
self.layoutColorToggleButton()
|
||||
}
|
||||
|
||||
open func layoutAddButton(){
|
||||
let addButtonSize = CGSize(width: self.bounds.width/5, height: self.bounds.height/5)
|
||||
addButton.frame = CGRect(x: self.bounds.midX - addButtonSize.width/2, y: self.bounds.midY - addButtonSize.height/2, width: addButtonSize.width, height: addButtonSize.height)
|
||||
}
|
||||
|
||||
/*
|
||||
Update the handleView's position and color for the currentAngle
|
||||
*/
|
||||
func layoutHandle(){
|
||||
let angle = currentAngle //Preserve value in case it changes
|
||||
let newPosition = positionOnWheelFromAngle(angle) //find the correct position on the color wheel
|
||||
|
||||
//Update handle position
|
||||
handleView.center = newPosition
|
||||
|
||||
if !modeIsGrayscale {
|
||||
//Update color for the movement when color mode is hue
|
||||
handleView.color = colorOnWheelFromAngle(angle)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Updates the line view's position for the current angle
|
||||
Pre: dependant on addButtons position & current angle
|
||||
*/
|
||||
func layoutHandleLine(){
|
||||
let linePath = UIBezierPath()
|
||||
linePath.move(to: addButton.center)
|
||||
linePath.addLine(to: positionOnWheelFromAngle(currentAngle))
|
||||
handleLine.path = linePath.cgPath
|
||||
}
|
||||
|
||||
/*
|
||||
Pre: dependant on addButtons position
|
||||
*/
|
||||
func layoutHexLabel(){
|
||||
hexLabel.frame = CGRect(x: 0, y: 0, width: addButton.bounds.width*1.5, height: addButton.bounds.height/3)
|
||||
hexLabel.center = CGPoint(x: self.bounds.midX, y: (addButton.frame.origin.y + (padding + handleView.frame.height/2 + stroke/2))/1.75) //Divided by 1.75 not 2 to make it a bit lower
|
||||
hexLabel.font = UIFont(name: "Menlo-Regular", size: hexLabel.bounds.height)
|
||||
}
|
||||
|
||||
/*
|
||||
Pre: dependant on radius
|
||||
*/
|
||||
func layoutShadeSlider(){
|
||||
/* Calculate proper length for slider */
|
||||
let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
let insideRadius = radius - padding
|
||||
|
||||
let pointLeft = CGPoint(x: centerPoint.x + insideRadius*CGFloat(cos(7*Double.pi/6)), y: centerPoint.y - insideRadius*CGFloat(sin(7*Double.pi/6)))
|
||||
let pointRight = CGPoint(x: centerPoint.x + insideRadius*CGFloat(cos(11*Double.pi/6)), y: centerPoint.y - insideRadius*CGFloat(sin(11*Double.pi/6)))
|
||||
let deltaX = pointRight.x - pointLeft.x //distance on circle between points at 7pi/6 and 11pi/6
|
||||
|
||||
|
||||
let sliderSize = CGSize(width: deltaX * 0.75, height: 0.08 * (bounds.height - padding*2))//bounds.height
|
||||
shadeSlider.frame = CGRect(x: bounds.midX - sliderSize.width/2, y: pointLeft.y - sliderSize.height/2, width: sliderSize.width, height: sliderSize.height)
|
||||
shadeSlider.handleCenterX = shadeSlider.bounds.width/2 //set handle starting position
|
||||
shadeSlider.layoutLayerFrames() //call sliders' layout function
|
||||
}
|
||||
|
||||
/*
|
||||
Pre: dependant on addButton
|
||||
*/
|
||||
func layoutColorToggleButton() {
|
||||
let inset = bounds.height/16
|
||||
colorToggleButton.frame = CGRect(x: inset, y: inset, width: addButton.frame.width/2.5, height: addButton.frame.width/2.5)
|
||||
colorToggleButton.layoutSubviews()
|
||||
}
|
||||
|
||||
func updateHexLabel(){
|
||||
hexLabel.text = "#" + currentColor.hexCode
|
||||
}
|
||||
|
||||
func updateCurrentColor(_ color: UIColor){
|
||||
currentColor = color
|
||||
addButton.color = color
|
||||
self.sendActions(for: .valueChanged)
|
||||
}
|
||||
|
||||
@objc open func togglePickerColorMode() {
|
||||
colorToggleButton.isEnabled = false // Lock
|
||||
|
||||
// Redraw Assets (i.e. Large circle ring)
|
||||
setNeedsDisplay()
|
||||
|
||||
// Update subcomponents for color change
|
||||
if modeIsGrayscale {
|
||||
//Change color of colorToggleButton to the last handle color
|
||||
let lightColor = handleView.color
|
||||
let shadedColor = handleView.color.darkerColor(0.25)
|
||||
colorToggleButton.hueColorGradientLayer.colors = [lightColor.cgColor, shadedColor.cgColor]
|
||||
|
||||
let gray = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1)
|
||||
self.handleView.color = gray
|
||||
self.updateCurrentColor(gray)
|
||||
self.updateHexLabel()
|
||||
|
||||
//Update color for shade slider
|
||||
shadeSlider.primaryColor = gray
|
||||
}
|
||||
else {
|
||||
// Update for normal rainbow
|
||||
|
||||
// Use color stored in toggle button (set above), or else default to the angle it is at
|
||||
var hueColor: UIColor
|
||||
if let storedColor = colorToggleButton.hueColorGradientLayer.colors?[0] {
|
||||
hueColor = UIColor(cgColor: (storedColor as! CGColor))
|
||||
|
||||
currentAngle = angleForColor(hueColor)
|
||||
self.layoutHandleLine()
|
||||
self.layoutHandle()
|
||||
}
|
||||
else {
|
||||
let currentAngle = self.angleToCenterFromPoint(self.handleView.center)
|
||||
hueColor = self.colorOnWheelFromAngle(currentAngle)
|
||||
}
|
||||
|
||||
self.handleView.color = hueColor
|
||||
self.updateCurrentColor(hueColor)
|
||||
self.updateHexLabel()
|
||||
|
||||
//Update color for shade slider
|
||||
shadeSlider.primaryColor = hueColor
|
||||
}
|
||||
|
||||
colorToggleButton.isEnabled = true // Unlock
|
||||
}
|
||||
|
||||
|
||||
//MARK: - Helper Methods
|
||||
private func angleToCenterFromPoint(_ point: CGPoint) -> Float {
|
||||
let deltaX = Float(self.bounds.midX - point.x)
|
||||
let deltaY = Float(self.bounds.midY - point.y)
|
||||
let angle = atan2f(deltaX, deltaY)
|
||||
|
||||
// Convert the angle to be between 0 and 2PI
|
||||
var adjustedAngle = angle + Float.pi/2
|
||||
if (adjustedAngle < 0){ //Left side (Q2 and Q3)
|
||||
adjustedAngle += Float.pi*2
|
||||
}
|
||||
|
||||
return adjustedAngle
|
||||
}
|
||||
|
||||
/* Find the angle relative to the center of the frame and uses the angle to find what color lies there */
|
||||
private func colorOnWheelFromAngle(_ angle: Float) -> UIColor {
|
||||
return UIColor(hue: CGFloat(Double(angle)/(2*Double.pi)), saturation: 1, brightness: 1, alpha: 1)
|
||||
}
|
||||
|
||||
private func angleForColor(_ color: UIColor) -> Float {
|
||||
var hue: CGFloat = 0
|
||||
color.getHue(&hue, saturation: nil, brightness: nil, alpha: nil)
|
||||
return Float(hue * CGFloat.pi * 2)
|
||||
}
|
||||
|
||||
/* Returns a position centered on the wheel for a given angle */
|
||||
private func positionOnWheelFromAngle(_ angle: Float) -> CGPoint{
|
||||
let buffer = padding + stroke/2
|
||||
return CGPoint(x: self.bounds.midX + ((radius - buffer) * CGFloat(cos(-angle))), y: self.bounds.midY + ((radius - buffer) * CGFloat(sin(-angle))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ChromaColorPicker: ChromaShadeSliderDelegate{
|
||||
public func shadeSliderChoseColor(_ slider: ChromaShadeSlider, color: UIColor) {
|
||||
self.updateCurrentColor(color) //update main controller for selected color
|
||||
self.updateHexLabel()
|
||||
}
|
||||
}
|
@ -1,20 +1,17 @@
|
||||
//
|
||||
// ChromaColorPickerSpec.swift
|
||||
// ChromaColorPickerTest.swift
|
||||
// ChromaColorPickerTests
|
||||
//
|
||||
// Created by Jon Cardasis on 2/3/19.
|
||||
// Copyright © 2019 Jonathan Cardasis. All rights reserved.
|
||||
//
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
import XCTest
|
||||
@testable import ChromaColorPicker
|
||||
|
||||
class ChromaColorPickerSpec: QuickSpec {
|
||||
override func spec() {
|
||||
class ChromaColorPickerTests: XCTestCase {
|
||||
|
||||
it("is an empty test") {
|
||||
func testDefault() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user