├── .gitignore ├── .swift-version ├── CircleColorPicker.podspec ├── CircleColorPicker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── laszlopinter.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── CircleColorPicker.xcscheme └── xcuserdata │ ├── laszlopinter.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── pinterlaszlo.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── CircleColorPicker ├── Assets.xcassets │ ├── Contents.json │ └── ringMask.imageset │ │ ├── Contents.json │ │ ├── ringMask.png │ │ ├── ringMask@2x.png │ │ └── ringMask@3x.png ├── CGFloat+Extensions.swift ├── CGPoint+Extensions.swift ├── CGVector+Extensions.swift ├── CircleColorPicker.h ├── ColorPicker │ ├── ColorSampleCircleView.swift │ ├── RainbowCircleView.swift │ └── Views │ │ ├── CircleColorPickerView │ │ ├── CircleColorPickerView+TouchHandling.swift │ │ ├── CircleColorPickerView.swift │ │ ├── CircleColorPickerView.xib │ │ └── CircleColorPickerViewDelegate.swift │ │ ├── ColorBubbleView │ │ ├── ColorBubbleView.swift │ │ └── ColorBubbleView.xib │ │ └── LinearPickers │ │ ├── BrightnessPickerView.swift │ │ ├── LinearPickerView+TouchHandling.swift │ │ ├── LinearPickerView.swift │ │ ├── LinearPickerView.xib │ │ └── SaturationPickerView.swift ├── Info.plist └── UIView+Extension.swift ├── CircleColorPickerTests ├── CircleColorPickerTests.swift └── Info.plist ├── LICENSE ├── README.md └── screenshots ├── screenshot1.png └── screenshot2.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | screenshots/\.DS_Store 3 | 4 | \.DS_Store 5 | 6 | *.xcuserstate 7 | 8 | CircleColorPicker\.xcodeproj/project\.xcworkspace/xcuserdata/laszlopinter\.xcuserdatad/ 9 | 10 | CircleColorPicker\.xcodeproj/xcuserdata/laszlopinter\.xcuserdatad/xcdebugger/ 11 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 -------------------------------------------------------------------------------- /CircleColorPicker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CircleColorPicker" 3 | s.version = "1.2.0" 4 | s.summary = "Fancy round color picker for iOS in Swift" 5 | 6 | 7 | s.description = <<-DESC 8 | This is a highly customizable color picker view written in Swift. 9 | DESC 10 | 11 | s.homepage = "https://github.com/LaszloPinter/CircleColorPicker" 12 | s.screenshots = "https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/master/screenshots/screenshot1.png", "https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/master/screenshots/screenshot2.png" 13 | 14 | 15 | s.license = "MIT" 16 | s.author = { "Laszlo Pinter" => "pinter.laci@gmail.com" } 17 | 18 | s.source = { :git => "https://github.com/LaszloPinter/CircleColorPicker.git", :tag => "#{s.version}" } 19 | s.platform = :ios, '10.0' 20 | s.source_files = "CircleColorPicker/**/*.{h,m,swift}" 21 | s.resource_bundle = { 'CircleColorPicker' => 'CircleColorPicker/**/*.{storyboard,xib,png,xcassets}' } 22 | 23 | end 24 | -------------------------------------------------------------------------------- /CircleColorPicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1731970A1F67FE42003ACD1C /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173196FD1F67FE41003ACD1C /* CGFloat+Extensions.swift */; }; 11 | 1731970B1F67FE42003ACD1C /* CGPoint+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173196FE1F67FE42003ACD1C /* CGPoint+Extensions.swift */; }; 12 | 1731970C1F67FE42003ACD1C /* CGVector+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173196FF1F67FE42003ACD1C /* CGVector+Extensions.swift */; }; 13 | 1731970D1F67FE5B003ACD1C /* CircleColorPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173197011F67FE42003ACD1C /* CircleColorPickerView.swift */; }; 14 | 1731970E1F67FE5B003ACD1C /* CircleColorPickerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 173197021F67FE42003ACD1C /* CircleColorPickerView.xib */; }; 15 | 1731970F1F67FE5B003ACD1C /* CircleColorPickerViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173197031F67FE42003ACD1C /* CircleColorPickerViewDelegate.swift */; }; 16 | 173197101F67FE5B003ACD1C /* CircleColorPickerView+TouchHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173197041F67FE42003ACD1C /* CircleColorPickerView+TouchHandling.swift */; }; 17 | 173197111F67FE5B003ACD1C /* ColorBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173197051F67FE42003ACD1C /* ColorBubbleView.swift */; }; 18 | 173197121F67FE5B003ACD1C /* ColorBubbleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 173197061F67FE42003ACD1C /* ColorBubbleView.xib */; }; 19 | 173197131F67FE5B003ACD1C /* ColorSampleCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173197071F67FE42003ACD1C /* ColorSampleCircleView.swift */; }; 20 | 173197141F67FE5B003ACD1C /* RainbowCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173197081F67FE42003ACD1C /* RainbowCircleView.swift */; }; 21 | 173197151F67FE5B003ACD1C /* LinearPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173197091F67FE42003ACD1C /* LinearPickerView.swift */; }; 22 | 173197281F6803C8003ACD1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 173197161F67FEEF003ACD1C /* Assets.xcassets */; }; 23 | 176AD9C21FB4898A00C719A4 /* LinearPickerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 176AD9C11FB4898A00C719A4 /* LinearPickerView.xib */; }; 24 | 176AD9C41FB4959C00C719A4 /* LinearPickerView+TouchHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176AD9C31FB4959C00C719A4 /* LinearPickerView+TouchHandling.swift */; }; 25 | 17819D9B1F6191BC007AD1C8 /* CircleColorPicker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17819D911F6191BC007AD1C8 /* CircleColorPicker.framework */; }; 26 | 17819DA01F6191BC007AD1C8 /* CircleColorPickerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17819D9F1F6191BC007AD1C8 /* CircleColorPickerTests.swift */; }; 27 | 17819DA21F6191BC007AD1C8 /* CircleColorPicker.h in Headers */ = {isa = PBXBuildFile; fileRef = 17819D941F6191BC007AD1C8 /* CircleColorPicker.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28 | 1791D8D41F7BCB9400F7396E /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1791D8D31F7BCB9400F7396E /* UIView+Extension.swift */; }; 29 | 17C2FB8E1FBF07ED00EB29C2 /* SaturationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C2FB8D1FBF07ED00EB29C2 /* SaturationPickerView.swift */; }; 30 | 17C2FB901FBF0F1300EB29C2 /* BrightnessPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C2FB8F1FBF0F1300EB29C2 /* BrightnessPickerView.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | 17819D9C1F6191BC007AD1C8 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 17819D881F6191BC007AD1C8 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 17819D901F6191BC007AD1C8; 39 | remoteInfo = CircleColorPicker; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 173196FD1F67FE41003ACD1C /* CGFloat+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Extensions.swift"; sourceTree = ""; }; 45 | 173196FE1F67FE42003ACD1C /* CGPoint+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint+Extensions.swift"; sourceTree = ""; }; 46 | 173196FF1F67FE42003ACD1C /* CGVector+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGVector+Extensions.swift"; sourceTree = ""; }; 47 | 173197011F67FE42003ACD1C /* CircleColorPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleColorPickerView.swift; sourceTree = ""; }; 48 | 173197021F67FE42003ACD1C /* CircleColorPickerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CircleColorPickerView.xib; sourceTree = ""; }; 49 | 173197031F67FE42003ACD1C /* CircleColorPickerViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleColorPickerViewDelegate.swift; sourceTree = ""; }; 50 | 173197041F67FE42003ACD1C /* CircleColorPickerView+TouchHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CircleColorPickerView+TouchHandling.swift"; sourceTree = ""; }; 51 | 173197051F67FE42003ACD1C /* ColorBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorBubbleView.swift; sourceTree = ""; }; 52 | 173197061F67FE42003ACD1C /* ColorBubbleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ColorBubbleView.xib; sourceTree = ""; }; 53 | 173197071F67FE42003ACD1C /* ColorSampleCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSampleCircleView.swift; sourceTree = ""; }; 54 | 173197081F67FE42003ACD1C /* RainbowCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RainbowCircleView.swift; sourceTree = ""; }; 55 | 173197091F67FE42003ACD1C /* LinearPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearPickerView.swift; sourceTree = ""; }; 56 | 173197161F67FEEF003ACD1C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 57 | 176AD9C11FB4898A00C719A4 /* LinearPickerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LinearPickerView.xib; sourceTree = ""; }; 58 | 176AD9C31FB4959C00C719A4 /* LinearPickerView+TouchHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LinearPickerView+TouchHandling.swift"; sourceTree = ""; }; 59 | 17819D911F6191BC007AD1C8 /* CircleColorPicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CircleColorPicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 17819D941F6191BC007AD1C8 /* CircleColorPicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CircleColorPicker.h; sourceTree = ""; }; 61 | 17819D951F6191BC007AD1C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 17819D9A1F6191BC007AD1C8 /* CircleColorPickerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CircleColorPickerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 17819D9F1F6191BC007AD1C8 /* CircleColorPickerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleColorPickerTests.swift; sourceTree = ""; }; 64 | 17819DA11F6191BC007AD1C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 1791D8D31F7BCB9400F7396E /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; 66 | 17C2FB8D1FBF07ED00EB29C2 /* SaturationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaturationPickerView.swift; sourceTree = ""; }; 67 | 17C2FB8F1FBF0F1300EB29C2 /* BrightnessPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrightnessPickerView.swift; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 17819D8D1F6191BC007AD1C8 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 17819D971F6191BC007AD1C8 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 17819D9B1F6191BC007AD1C8 /* CircleColorPicker.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 173197001F67FE42003ACD1C /* ColorPicker */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 176AD9C51FB4970F00C719A4 /* Views */, 93 | 173197071F67FE42003ACD1C /* ColorSampleCircleView.swift */, 94 | 173197081F67FE42003ACD1C /* RainbowCircleView.swift */, 95 | ); 96 | path = ColorPicker; 97 | sourceTree = ""; 98 | }; 99 | 176AD9C01FB4894B00C719A4 /* LinearPickers */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 173197091F67FE42003ACD1C /* LinearPickerView.swift */, 103 | 176AD9C31FB4959C00C719A4 /* LinearPickerView+TouchHandling.swift */, 104 | 176AD9C11FB4898A00C719A4 /* LinearPickerView.xib */, 105 | 17C2FB8D1FBF07ED00EB29C2 /* SaturationPickerView.swift */, 106 | 17C2FB8F1FBF0F1300EB29C2 /* BrightnessPickerView.swift */, 107 | ); 108 | path = LinearPickers; 109 | sourceTree = ""; 110 | }; 111 | 176AD9C51FB4970F00C719A4 /* Views */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 176AD9C71FB4975400C719A4 /* ColorBubbleView */, 115 | 176AD9C61FB4972C00C719A4 /* CircleColorPickerView */, 116 | 176AD9C01FB4894B00C719A4 /* LinearPickers */, 117 | ); 118 | path = Views; 119 | sourceTree = ""; 120 | }; 121 | 176AD9C61FB4972C00C719A4 /* CircleColorPickerView */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 173197011F67FE42003ACD1C /* CircleColorPickerView.swift */, 125 | 173197041F67FE42003ACD1C /* CircleColorPickerView+TouchHandling.swift */, 126 | 173197021F67FE42003ACD1C /* CircleColorPickerView.xib */, 127 | 173197031F67FE42003ACD1C /* CircleColorPickerViewDelegate.swift */, 128 | ); 129 | path = CircleColorPickerView; 130 | sourceTree = ""; 131 | }; 132 | 176AD9C71FB4975400C719A4 /* ColorBubbleView */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 173197051F67FE42003ACD1C /* ColorBubbleView.swift */, 136 | 173197061F67FE42003ACD1C /* ColorBubbleView.xib */, 137 | ); 138 | path = ColorBubbleView; 139 | sourceTree = ""; 140 | }; 141 | 17819D871F6191BC007AD1C8 = { 142 | isa = PBXGroup; 143 | children = ( 144 | 17819D931F6191BC007AD1C8 /* CircleColorPicker */, 145 | 17819D9E1F6191BC007AD1C8 /* CircleColorPickerTests */, 146 | 17819D921F6191BC007AD1C8 /* Products */, 147 | ); 148 | sourceTree = ""; 149 | }; 150 | 17819D921F6191BC007AD1C8 /* Products */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 17819D911F6191BC007AD1C8 /* CircleColorPicker.framework */, 154 | 17819D9A1F6191BC007AD1C8 /* CircleColorPickerTests.xctest */, 155 | ); 156 | name = Products; 157 | sourceTree = ""; 158 | }; 159 | 17819D931F6191BC007AD1C8 /* CircleColorPicker */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 173196FD1F67FE41003ACD1C /* CGFloat+Extensions.swift */, 163 | 173196FE1F67FE42003ACD1C /* CGPoint+Extensions.swift */, 164 | 173196FF1F67FE42003ACD1C /* CGVector+Extensions.swift */, 165 | 1791D8D31F7BCB9400F7396E /* UIView+Extension.swift */, 166 | 173197161F67FEEF003ACD1C /* Assets.xcassets */, 167 | 173197001F67FE42003ACD1C /* ColorPicker */, 168 | 17819D941F6191BC007AD1C8 /* CircleColorPicker.h */, 169 | 17819D951F6191BC007AD1C8 /* Info.plist */, 170 | ); 171 | path = CircleColorPicker; 172 | sourceTree = ""; 173 | }; 174 | 17819D9E1F6191BC007AD1C8 /* CircleColorPickerTests */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 17819D9F1F6191BC007AD1C8 /* CircleColorPickerTests.swift */, 178 | 17819DA11F6191BC007AD1C8 /* Info.plist */, 179 | ); 180 | path = CircleColorPickerTests; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXGroup section */ 184 | 185 | /* Begin PBXHeadersBuildPhase section */ 186 | 17819D8E1F6191BC007AD1C8 /* Headers */ = { 187 | isa = PBXHeadersBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 17819DA21F6191BC007AD1C8 /* CircleColorPicker.h in Headers */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXHeadersBuildPhase section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | 17819D901F6191BC007AD1C8 /* CircleColorPicker */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = 17819DA51F6191BC007AD1C8 /* Build configuration list for PBXNativeTarget "CircleColorPicker" */; 200 | buildPhases = ( 201 | 17819D8C1F6191BC007AD1C8 /* Sources */, 202 | 17819D8D1F6191BC007AD1C8 /* Frameworks */, 203 | 17819D8E1F6191BC007AD1C8 /* Headers */, 204 | 17819D8F1F6191BC007AD1C8 /* Resources */, 205 | ); 206 | buildRules = ( 207 | ); 208 | dependencies = ( 209 | ); 210 | name = CircleColorPicker; 211 | productName = CircleColorPicker; 212 | productReference = 17819D911F6191BC007AD1C8 /* CircleColorPicker.framework */; 213 | productType = "com.apple.product-type.framework"; 214 | }; 215 | 17819D991F6191BC007AD1C8 /* CircleColorPickerTests */ = { 216 | isa = PBXNativeTarget; 217 | buildConfigurationList = 17819DA81F6191BC007AD1C8 /* Build configuration list for PBXNativeTarget "CircleColorPickerTests" */; 218 | buildPhases = ( 219 | 17819D961F6191BC007AD1C8 /* Sources */, 220 | 17819D971F6191BC007AD1C8 /* Frameworks */, 221 | 17819D981F6191BC007AD1C8 /* Resources */, 222 | ); 223 | buildRules = ( 224 | ); 225 | dependencies = ( 226 | 17819D9D1F6191BC007AD1C8 /* PBXTargetDependency */, 227 | ); 228 | name = CircleColorPickerTests; 229 | productName = CircleColorPickerTests; 230 | productReference = 17819D9A1F6191BC007AD1C8 /* CircleColorPickerTests.xctest */; 231 | productType = "com.apple.product-type.bundle.unit-test"; 232 | }; 233 | /* End PBXNativeTarget section */ 234 | 235 | /* Begin PBXProject section */ 236 | 17819D881F6191BC007AD1C8 /* Project object */ = { 237 | isa = PBXProject; 238 | attributes = { 239 | LastSwiftUpdateCheck = 0900; 240 | LastUpgradeCheck = 0900; 241 | ORGANIZATIONNAME = "Laszlo Pinter"; 242 | TargetAttributes = { 243 | 17819D901F6191BC007AD1C8 = { 244 | CreatedOnToolsVersion = 9.0; 245 | LastSwiftMigration = 0900; 246 | ProvisioningStyle = Automatic; 247 | }; 248 | 17819D991F6191BC007AD1C8 = { 249 | CreatedOnToolsVersion = 9.0; 250 | ProvisioningStyle = Automatic; 251 | }; 252 | }; 253 | }; 254 | buildConfigurationList = 17819D8B1F6191BC007AD1C8 /* Build configuration list for PBXProject "CircleColorPicker" */; 255 | compatibilityVersion = "Xcode 8.0"; 256 | developmentRegion = en; 257 | hasScannedForEncodings = 0; 258 | knownRegions = ( 259 | en, 260 | ); 261 | mainGroup = 17819D871F6191BC007AD1C8; 262 | productRefGroup = 17819D921F6191BC007AD1C8 /* Products */; 263 | projectDirPath = ""; 264 | projectRoot = ""; 265 | targets = ( 266 | 17819D901F6191BC007AD1C8 /* CircleColorPicker */, 267 | 17819D991F6191BC007AD1C8 /* CircleColorPickerTests */, 268 | ); 269 | }; 270 | /* End PBXProject section */ 271 | 272 | /* Begin PBXResourcesBuildPhase section */ 273 | 17819D8F1F6191BC007AD1C8 /* Resources */ = { 274 | isa = PBXResourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 173197121F67FE5B003ACD1C /* ColorBubbleView.xib in Resources */, 278 | 176AD9C21FB4898A00C719A4 /* LinearPickerView.xib in Resources */, 279 | 173197281F6803C8003ACD1C /* Assets.xcassets in Resources */, 280 | 1731970E1F67FE5B003ACD1C /* CircleColorPickerView.xib in Resources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | 17819D981F6191BC007AD1C8 /* Resources */ = { 285 | isa = PBXResourcesBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | /* End PBXResourcesBuildPhase section */ 292 | 293 | /* Begin PBXSourcesBuildPhase section */ 294 | 17819D8C1F6191BC007AD1C8 /* Sources */ = { 295 | isa = PBXSourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | 1791D8D41F7BCB9400F7396E /* UIView+Extension.swift in Sources */, 299 | 1731970D1F67FE5B003ACD1C /* CircleColorPickerView.swift in Sources */, 300 | 1731970B1F67FE42003ACD1C /* CGPoint+Extensions.swift in Sources */, 301 | 173197151F67FE5B003ACD1C /* LinearPickerView.swift in Sources */, 302 | 17C2FB8E1FBF07ED00EB29C2 /* SaturationPickerView.swift in Sources */, 303 | 173197101F67FE5B003ACD1C /* CircleColorPickerView+TouchHandling.swift in Sources */, 304 | 173197111F67FE5B003ACD1C /* ColorBubbleView.swift in Sources */, 305 | 176AD9C41FB4959C00C719A4 /* LinearPickerView+TouchHandling.swift in Sources */, 306 | 173197131F67FE5B003ACD1C /* ColorSampleCircleView.swift in Sources */, 307 | 17C2FB901FBF0F1300EB29C2 /* BrightnessPickerView.swift in Sources */, 308 | 1731970F1F67FE5B003ACD1C /* CircleColorPickerViewDelegate.swift in Sources */, 309 | 1731970A1F67FE42003ACD1C /* CGFloat+Extensions.swift in Sources */, 310 | 173197141F67FE5B003ACD1C /* RainbowCircleView.swift in Sources */, 311 | 1731970C1F67FE42003ACD1C /* CGVector+Extensions.swift in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | 17819D961F6191BC007AD1C8 /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 17819DA01F6191BC007AD1C8 /* CircleColorPickerTests.swift in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | /* End PBXSourcesBuildPhase section */ 324 | 325 | /* Begin PBXTargetDependency section */ 326 | 17819D9D1F6191BC007AD1C8 /* PBXTargetDependency */ = { 327 | isa = PBXTargetDependency; 328 | target = 17819D901F6191BC007AD1C8 /* CircleColorPicker */; 329 | targetProxy = 17819D9C1F6191BC007AD1C8 /* PBXContainerItemProxy */; 330 | }; 331 | /* End PBXTargetDependency section */ 332 | 333 | /* Begin XCBuildConfiguration section */ 334 | 17819DA31F6191BC007AD1C8 /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_NONNULL = YES; 339 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 341 | CLANG_CXX_LIBRARY = "libc++"; 342 | CLANG_ENABLE_MODULES = YES; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 345 | CLANG_WARN_BOOL_CONVERSION = YES; 346 | CLANG_WARN_COMMA = YES; 347 | CLANG_WARN_CONSTANT_CONVERSION = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 350 | CLANG_WARN_EMPTY_BODY = YES; 351 | CLANG_WARN_ENUM_CONVERSION = YES; 352 | CLANG_WARN_INFINITE_RECURSION = YES; 353 | CLANG_WARN_INT_CONVERSION = YES; 354 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 357 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 358 | CLANG_WARN_STRICT_PROTOTYPES = YES; 359 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 360 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 361 | CLANG_WARN_UNREACHABLE_CODE = YES; 362 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 363 | CODE_SIGN_IDENTITY = "iPhone Developer"; 364 | COPY_PHASE_STRIP = NO; 365 | CURRENT_PROJECT_VERSION = 1; 366 | DEBUG_INFORMATION_FORMAT = dwarf; 367 | ENABLE_STRICT_OBJC_MSGSEND = YES; 368 | ENABLE_TESTABILITY = YES; 369 | GCC_C_LANGUAGE_STANDARD = gnu11; 370 | GCC_DYNAMIC_NO_PIC = NO; 371 | GCC_NO_COMMON_BLOCKS = YES; 372 | GCC_OPTIMIZATION_LEVEL = 0; 373 | GCC_PREPROCESSOR_DEFINITIONS = ( 374 | "DEBUG=1", 375 | "$(inherited)", 376 | ); 377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 379 | GCC_WARN_UNDECLARED_SELECTOR = YES; 380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 381 | GCC_WARN_UNUSED_FUNCTION = YES; 382 | GCC_WARN_UNUSED_VARIABLE = YES; 383 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 384 | MTL_ENABLE_DEBUG_INFO = YES; 385 | ONLY_ACTIVE_ARCH = YES; 386 | SDKROOT = iphoneos; 387 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 388 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 389 | VERSIONING_SYSTEM = "apple-generic"; 390 | VERSION_INFO_PREFIX = ""; 391 | }; 392 | name = Debug; 393 | }; 394 | 17819DA41F6191BC007AD1C8 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | CLANG_ANALYZER_NONNULL = YES; 399 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 401 | CLANG_CXX_LIBRARY = "libc++"; 402 | CLANG_ENABLE_MODULES = YES; 403 | CLANG_ENABLE_OBJC_ARC = YES; 404 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 405 | CLANG_WARN_BOOL_CONVERSION = YES; 406 | CLANG_WARN_COMMA = YES; 407 | CLANG_WARN_CONSTANT_CONVERSION = YES; 408 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 409 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 410 | CLANG_WARN_EMPTY_BODY = YES; 411 | CLANG_WARN_ENUM_CONVERSION = YES; 412 | CLANG_WARN_INFINITE_RECURSION = YES; 413 | CLANG_WARN_INT_CONVERSION = YES; 414 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 421 | CLANG_WARN_UNREACHABLE_CODE = YES; 422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 423 | CODE_SIGN_IDENTITY = "iPhone Developer"; 424 | COPY_PHASE_STRIP = NO; 425 | CURRENT_PROJECT_VERSION = 1; 426 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 427 | ENABLE_NS_ASSERTIONS = NO; 428 | ENABLE_STRICT_OBJC_MSGSEND = YES; 429 | GCC_C_LANGUAGE_STANDARD = gnu11; 430 | GCC_NO_COMMON_BLOCKS = YES; 431 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 432 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 433 | GCC_WARN_UNDECLARED_SELECTOR = YES; 434 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 435 | GCC_WARN_UNUSED_FUNCTION = YES; 436 | GCC_WARN_UNUSED_VARIABLE = YES; 437 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 438 | MTL_ENABLE_DEBUG_INFO = NO; 439 | SDKROOT = iphoneos; 440 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 441 | VALIDATE_PRODUCT = YES; 442 | VERSIONING_SYSTEM = "apple-generic"; 443 | VERSION_INFO_PREFIX = ""; 444 | }; 445 | name = Release; 446 | }; 447 | 17819DA61F6191BC007AD1C8 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | CLANG_ENABLE_MODULES = YES; 451 | CODE_SIGN_IDENTITY = ""; 452 | CODE_SIGN_STYLE = Automatic; 453 | DEFINES_MODULE = YES; 454 | DEVELOPMENT_TEAM = 74WC96E6CP; 455 | DYLIB_COMPATIBILITY_VERSION = 1; 456 | DYLIB_CURRENT_VERSION = 1; 457 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 458 | INFOPLIST_FILE = CircleColorPicker/Info.plist; 459 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 460 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 462 | PRODUCT_BUNDLE_IDENTIFIER = com.laszlopinter.CircleColorPicker; 463 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 464 | SKIP_INSTALL = YES; 465 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 466 | SWIFT_VERSION = 4.0; 467 | TARGETED_DEVICE_FAMILY = "1,2"; 468 | }; 469 | name = Debug; 470 | }; 471 | 17819DA71F6191BC007AD1C8 /* Release */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | CLANG_ENABLE_MODULES = YES; 475 | CODE_SIGN_IDENTITY = ""; 476 | CODE_SIGN_STYLE = Automatic; 477 | DEFINES_MODULE = YES; 478 | DEVELOPMENT_TEAM = 74WC96E6CP; 479 | DYLIB_COMPATIBILITY_VERSION = 1; 480 | DYLIB_CURRENT_VERSION = 1; 481 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 482 | INFOPLIST_FILE = CircleColorPicker/Info.plist; 483 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 484 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.laszlopinter.CircleColorPicker; 487 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 488 | SKIP_INSTALL = YES; 489 | SWIFT_VERSION = 4.0; 490 | TARGETED_DEVICE_FAMILY = "1,2"; 491 | }; 492 | name = Release; 493 | }; 494 | 17819DA91F6191BC007AD1C8 /* Debug */ = { 495 | isa = XCBuildConfiguration; 496 | buildSettings = { 497 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 498 | CODE_SIGN_STYLE = Automatic; 499 | DEVELOPMENT_TEAM = 74WC96E6CP; 500 | INFOPLIST_FILE = CircleColorPickerTests/Info.plist; 501 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 502 | PRODUCT_BUNDLE_IDENTIFIER = com.laszlopinter.CircleColorPickerTests; 503 | PRODUCT_NAME = "$(TARGET_NAME)"; 504 | SWIFT_VERSION = 4.0; 505 | TARGETED_DEVICE_FAMILY = "1,2"; 506 | }; 507 | name = Debug; 508 | }; 509 | 17819DAA1F6191BC007AD1C8 /* Release */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 513 | CODE_SIGN_STYLE = Automatic; 514 | DEVELOPMENT_TEAM = 74WC96E6CP; 515 | INFOPLIST_FILE = CircleColorPickerTests/Info.plist; 516 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 517 | PRODUCT_BUNDLE_IDENTIFIER = com.laszlopinter.CircleColorPickerTests; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_VERSION = 4.0; 520 | TARGETED_DEVICE_FAMILY = "1,2"; 521 | }; 522 | name = Release; 523 | }; 524 | /* End XCBuildConfiguration section */ 525 | 526 | /* Begin XCConfigurationList section */ 527 | 17819D8B1F6191BC007AD1C8 /* Build configuration list for PBXProject "CircleColorPicker" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | 17819DA31F6191BC007AD1C8 /* Debug */, 531 | 17819DA41F6191BC007AD1C8 /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | 17819DA51F6191BC007AD1C8 /* Build configuration list for PBXNativeTarget "CircleColorPicker" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | 17819DA61F6191BC007AD1C8 /* Debug */, 540 | 17819DA71F6191BC007AD1C8 /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | 17819DA81F6191BC007AD1C8 /* Build configuration list for PBXNativeTarget "CircleColorPickerTests" */ = { 546 | isa = XCConfigurationList; 547 | buildConfigurations = ( 548 | 17819DA91F6191BC007AD1C8 /* Debug */, 549 | 17819DAA1F6191BC007AD1C8 /* Release */, 550 | ); 551 | defaultConfigurationIsVisible = 0; 552 | defaultConfigurationName = Release; 553 | }; 554 | /* End XCConfigurationList section */ 555 | }; 556 | rootObject = 17819D881F6191BC007AD1C8 /* Project object */; 557 | } 558 | -------------------------------------------------------------------------------- /CircleColorPicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CircleColorPicker.xcodeproj/project.xcworkspace/xcuserdata/laszlopinter.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/a2e96c2d25be0b3f77ad4b7d1d06350a86729208/CircleColorPicker.xcodeproj/project.xcworkspace/xcuserdata/laszlopinter.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CircleColorPicker.xcodeproj/xcshareddata/xcschemes/CircleColorPicker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /CircleColorPicker.xcodeproj/xcuserdata/laszlopinter.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CircleColorPicker.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CircleColorPicker.xcodeproj/xcuserdata/pinterlaszlo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CircleColorPicker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 17819D901F6191BC007AD1C8 16 | 17 | primary 18 | 19 | 20 | 17819D991F6191BC007AD1C8 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CircleColorPicker/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CircleColorPicker/Assets.xcassets/ringMask.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ringMask.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ringMask@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ringMask@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CircleColorPicker/Assets.xcassets/ringMask.imageset/ringMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/a2e96c2d25be0b3f77ad4b7d1d06350a86729208/CircleColorPicker/Assets.xcassets/ringMask.imageset/ringMask.png -------------------------------------------------------------------------------- /CircleColorPicker/Assets.xcassets/ringMask.imageset/ringMask@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/a2e96c2d25be0b3f77ad4b7d1d06350a86729208/CircleColorPicker/Assets.xcassets/ringMask.imageset/ringMask@2x.png -------------------------------------------------------------------------------- /CircleColorPicker/Assets.xcassets/ringMask.imageset/ringMask@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/a2e96c2d25be0b3f77ad4b7d1d06350a86729208/CircleColorPicker/Assets.xcassets/ringMask.imageset/ringMask@3x.png -------------------------------------------------------------------------------- /CircleColorPicker/CGFloat+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | 25 | /** The value of π as a CGFloat */ 26 | let π = CGFloat(Double.pi) 27 | 28 | public extension CGFloat { 29 | /** 30 | * Converts an angle in degrees to radians. 31 | */ 32 | public func degreesToRadians() -> CGFloat { 33 | return π * self / 180.0 34 | } 35 | 36 | /** 37 | * Converts an angle in radians to degrees. 38 | */ 39 | public func radiansToDegrees() -> CGFloat { 40 | return self * 180.0 / π 41 | } 42 | 43 | /** 44 | * Ensures that the float value stays between the given values, inclusive. 45 | */ 46 | public func clamped(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 47 | let min = v1 < v2 ? v1 : v2 48 | let max = v1 > v2 ? v1 : v2 49 | return self < min ? min : (self > max ? max : self) 50 | } 51 | 52 | /** 53 | * Ensures that the float value stays between the given values, inclusive. 54 | */ 55 | public mutating func clamp(_ v1: CGFloat, _ v2: CGFloat) -> CGFloat { 56 | self = clamped(v1, v2) 57 | return self 58 | } 59 | 60 | /** 61 | * Returns 1.0 if a floating point value is positive; -1.0 if it is negative. 62 | */ 63 | public func sign() -> CGFloat { 64 | return (self >= 0.0) ? 1.0 : -1.0 65 | } 66 | 67 | /** 68 | * Returns a random floating point number between 0.0 and 1.0, inclusive. 69 | */ 70 | public static func random() -> CGFloat { 71 | return CGFloat(Float(arc4random()) / 0xFFFFFFFF) 72 | } 73 | 74 | /** 75 | * Returns a random floating point number in the range min...max, inclusive. 76 | */ 77 | public static func random(min: CGFloat, max: CGFloat) -> CGFloat { 78 | assert(min < max) 79 | return CGFloat.random() * (max - min) + min 80 | } 81 | 82 | /** 83 | * Randomly returns either 1.0 or -1.0. 84 | */ 85 | public static func randomSign() -> CGFloat { 86 | return (arc4random_uniform(2) == 0) ? 1.0 : -1.0 87 | } 88 | } 89 | 90 | /** 91 | * Returns the shortest angle between two angles. The result is always between 92 | * -π and π. 93 | */ 94 | public func shortestAngleBetween(_ angle1: CGFloat, angle2: CGFloat) -> CGFloat { 95 | let twoπ = π * 2.0 96 | var angle = (angle2 - angle1).truncatingRemainder(dividingBy: twoπ) 97 | if (angle >= π) { 98 | angle = angle - twoπ 99 | } 100 | if (angle <= -π) { 101 | angle = angle + twoπ 102 | } 103 | return angle 104 | } 105 | -------------------------------------------------------------------------------- /CircleColorPicker/CGPoint+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | import SpriteKit 25 | 26 | public extension CGPoint { 27 | /** 28 | * Creates a new CGPoint given a CGVector. 29 | */ 30 | public init(vector: CGVector) { 31 | self.init(x: vector.dx, y: vector.dy) 32 | } 33 | 34 | /** 35 | * Given an angle in radians, creates a vector of length 1.0 and returns the 36 | * result as a new CGPoint. An angle of 0 is assumed to point to the right. 37 | */ 38 | public init(angle: CGFloat) { 39 | self.init(x: cos(angle), y: sin(angle)) 40 | } 41 | 42 | /** 43 | * Adds (dx, dy) to the point. 44 | */ 45 | public mutating func offset(dx: CGFloat, dy: CGFloat) -> CGPoint { 46 | x += dx 47 | y += dy 48 | return self 49 | } 50 | 51 | /** 52 | * Returns the length (magnitude) of the vector described by the CGPoint. 53 | */ 54 | public func length() -> CGFloat { 55 | return sqrt(x*x + y*y) 56 | } 57 | 58 | /** 59 | * Returns the squared length of the vector described by the CGPoint. 60 | */ 61 | public func lengthSquared() -> CGFloat { 62 | return x*x + y*y 63 | } 64 | 65 | /** 66 | * Normalizes the vector described by the CGPoint to length 1.0 and returns 67 | * the result as a new CGPoint. 68 | */ 69 | func normalized() -> CGPoint { 70 | let len = length() 71 | return len>0 ? self / len : CGPoint.zero 72 | } 73 | 74 | /** 75 | * Normalizes the vector described by the CGPoint to length 1.0. 76 | */ 77 | public mutating func normalize() -> CGPoint { 78 | self = normalized() 79 | return self 80 | } 81 | 82 | /** 83 | * Calculates the distance between two CGPoints. Pythagoras! 84 | */ 85 | public func distanceTo(_ point: CGPoint) -> CGFloat { 86 | return (self - point).length() 87 | } 88 | 89 | /** 90 | * Returns the angle in radians of the vector described by the CGPoint. 91 | * The range of the angle is -π to π; an angle of 0 points to the right. 92 | */ 93 | public var angle: CGFloat { 94 | return atan2(y, x) 95 | } 96 | } 97 | 98 | /** 99 | * Adds two CGPoint values and returns the result as a new CGPoint. 100 | */ 101 | public func + (left: CGPoint, right: CGPoint) -> CGPoint { 102 | return CGPoint(x: left.x + right.x, y: left.y + right.y) 103 | } 104 | 105 | /** 106 | * Increments a CGPoint with the value of another. 107 | */ 108 | public func += (left: inout CGPoint, right: CGPoint) { 109 | left = left + right 110 | } 111 | 112 | /** 113 | * Adds a CGVector to this CGPoint and returns the result as a new CGPoint. 114 | */ 115 | public func + (left: CGPoint, right: CGVector) -> CGPoint { 116 | return CGPoint(x: left.x + right.dx, y: left.y + right.dy) 117 | } 118 | 119 | /** 120 | * Increments a CGPoint with the value of a CGVector. 121 | */ 122 | public func += (left: inout CGPoint, right: CGVector) { 123 | left = left + right 124 | } 125 | 126 | /** 127 | * Subtracts two CGPoint values and returns the result as a new CGPoint. 128 | */ 129 | public func - (left: CGPoint, right: CGPoint) -> CGPoint { 130 | return CGPoint(x: left.x - right.x, y: left.y - right.y) 131 | } 132 | 133 | /** 134 | * Decrements a CGPoint with the value of another. 135 | */ 136 | public func -= (left: inout CGPoint, right: CGPoint) { 137 | left = left - right 138 | } 139 | 140 | /** 141 | * Subtracts a CGVector from a CGPoint and returns the result as a new CGPoint. 142 | */ 143 | public func - (left: CGPoint, right: CGVector) -> CGPoint { 144 | return CGPoint(x: left.x - right.dx, y: left.y - right.dy) 145 | } 146 | 147 | /** 148 | * Decrements a CGPoint with the value of a CGVector. 149 | */ 150 | public func -= (left: inout CGPoint, right: CGVector) { 151 | left = left - right 152 | } 153 | 154 | /** 155 | * Multiplies two CGPoint values and returns the result as a new CGPoint. 156 | */ 157 | public func * (left: CGPoint, right: CGPoint) -> CGPoint { 158 | return CGPoint(x: left.x * right.x, y: left.y * right.y) 159 | } 160 | 161 | /** 162 | * Multiplies a CGPoint with another. 163 | */ 164 | public func *= (left: inout CGPoint, right: CGPoint) { 165 | left = left * right 166 | } 167 | 168 | /** 169 | * Multiplies the x and y fields of a CGPoint with the same scalar value and 170 | * returns the result as a new CGPoint. 171 | */ 172 | public func * (point: CGPoint, scalar: CGFloat) -> CGPoint { 173 | return CGPoint(x: point.x * scalar, y: point.y * scalar) 174 | } 175 | 176 | /** 177 | * Multiplies the x and y fields of a CGPoint with the same scalar value. 178 | */ 179 | public func *= (point: inout CGPoint, scalar: CGFloat) { 180 | point = point * scalar 181 | } 182 | 183 | /** 184 | * Multiplies a CGPoint with a CGVector and returns the result as a new CGPoint. 185 | */ 186 | public func * (left: CGPoint, right: CGVector) -> CGPoint { 187 | return CGPoint(x: left.x * right.dx, y: left.y * right.dy) 188 | } 189 | 190 | /** 191 | * Multiplies a CGPoint with a CGVector. 192 | */ 193 | public func *= (left: inout CGPoint, right: CGVector) { 194 | left = left * right 195 | } 196 | 197 | /** 198 | * Divides two CGPoint values and returns the result as a new CGPoint. 199 | */ 200 | public func / (left: CGPoint, right: CGPoint) -> CGPoint { 201 | return CGPoint(x: left.x / right.x, y: left.y / right.y) 202 | } 203 | 204 | /** 205 | * Divides a CGPoint by another. 206 | */ 207 | public func /= (left: inout CGPoint, right: CGPoint) { 208 | left = left / right 209 | } 210 | 211 | /** 212 | * Divides the x and y fields of a CGPoint by the same scalar value and returns 213 | * the result as a new CGPoint. 214 | */ 215 | public func / (point: CGPoint, scalar: CGFloat) -> CGPoint { 216 | return CGPoint(x: point.x / scalar, y: point.y / scalar) 217 | } 218 | 219 | /** 220 | * Divides the x and y fields of a CGPoint by the same scalar value. 221 | */ 222 | public func /= (point: inout CGPoint, scalar: CGFloat) { 223 | point = point / scalar 224 | } 225 | 226 | /** 227 | * Divides a CGPoint by a CGVector and returns the result as a new CGPoint. 228 | */ 229 | public func / (left: CGPoint, right: CGVector) -> CGPoint { 230 | return CGPoint(x: left.x / right.dx, y: left.y / right.dy) 231 | } 232 | 233 | /** 234 | * Divides a CGPoint by a CGVector. 235 | */ 236 | public func /= (left: inout CGPoint, right: CGVector) { 237 | left = left / right 238 | } 239 | 240 | /** 241 | * Performs a linear interpolation between two CGPoint values. 242 | */ 243 | public func lerp(start: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint { 244 | return start + (end - start) * t 245 | } 246 | -------------------------------------------------------------------------------- /CircleColorPicker/CGVector+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Razeware LLC 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import CoreGraphics 24 | import SpriteKit 25 | 26 | public extension CGVector { 27 | /** 28 | * Creates a new CGVector given a CGPoint. 29 | */ 30 | public init(point: CGPoint) { 31 | self.init(dx: point.x, dy: point.y) 32 | } 33 | 34 | /** 35 | * Given an angle in radians, creates a vector of length 1.0 and returns the 36 | * result as a new CGVector. An angle of 0 is assumed to point to the right. 37 | */ 38 | public init(angle: CGFloat) { 39 | self.init(dx: cos(angle), dy: sin(angle)) 40 | } 41 | 42 | /** 43 | * Adds (dx, dy) to the vector. 44 | */ 45 | public mutating func offset(dx: CGFloat, dy: CGFloat) -> CGVector { 46 | self.dx += dx 47 | self.dy += dy 48 | return self 49 | } 50 | 51 | /** 52 | * Returns the length (magnitude) of the vector described by the CGVector. 53 | */ 54 | public func length() -> CGFloat { 55 | return sqrt(dx*dx + dy*dy) 56 | } 57 | 58 | /** 59 | * Returns the squared length of the vector described by the CGVector. 60 | */ 61 | public func lengthSquared() -> CGFloat { 62 | return dx*dx + dy*dy 63 | } 64 | 65 | /** 66 | * Normalizes the vector described by the CGVector to length 1.0 and returns 67 | * the result as a new CGVector. 68 | public */ 69 | func normalized() -> CGVector { 70 | let len = length() 71 | return len>0 ? self / len : CGVector.zero 72 | } 73 | 74 | /** 75 | * Normalizes the vector described by the CGVector to length 1.0. 76 | */ 77 | public mutating func normalize() -> CGVector { 78 | self = normalized() 79 | return self 80 | } 81 | 82 | /** 83 | * Calculates the distance between two CGVectors. Pythagoras! 84 | */ 85 | public func distanceTo(_ vector: CGVector) -> CGFloat { 86 | return (self - vector).length() 87 | } 88 | 89 | /** 90 | * Returns the angle in radians of the vector described by the CGVector. 91 | * The range of the angle is -π to π; an angle of 0 points to the right. 92 | */ 93 | public var angle: CGFloat { 94 | return atan2(dy, dx) 95 | } 96 | } 97 | 98 | /** 99 | * Adds two CGVector values and returns the result as a new CGVector. 100 | */ 101 | public func + (left: CGVector, right: CGVector) -> CGVector { 102 | return CGVector(dx: left.dx + right.dx, dy: left.dy + right.dy) 103 | } 104 | 105 | /** 106 | * Increments a CGVector with the value of another. 107 | */ 108 | public func += (left: inout CGVector, right: CGVector) { 109 | left = left + right 110 | } 111 | 112 | /** 113 | * Subtracts two CGVector values and returns the result as a new CGVector. 114 | */ 115 | public func - (left: CGVector, right: CGVector) -> CGVector { 116 | return CGVector(dx: left.dx - right.dx, dy: left.dy - right.dy) 117 | } 118 | 119 | /** 120 | * Decrements a CGVector with the value of another. 121 | */ 122 | public func -= (left: inout CGVector, right: CGVector) { 123 | left = left - right 124 | } 125 | 126 | /** 127 | * Multiplies two CGVector values and returns the result as a new CGVector. 128 | */ 129 | public func * (left: CGVector, right: CGVector) -> CGVector { 130 | return CGVector(dx: left.dx * right.dx, dy: left.dy * right.dy) 131 | } 132 | 133 | /** 134 | * Multiplies a CGVector with another. 135 | */ 136 | public func *= (left: inout CGVector, right: CGVector) { 137 | left = left * right 138 | } 139 | 140 | /** 141 | * Multiplies the x and y fields of a CGVector with the same scalar value and 142 | * returns the result as a new CGVector. 143 | */ 144 | public func * (vector: CGVector, scalar: CGFloat) -> CGVector { 145 | return CGVector(dx: vector.dx * scalar, dy: vector.dy * scalar) 146 | } 147 | 148 | /** 149 | * Multiplies the x and y fields of a CGVector with the same scalar value. 150 | */ 151 | public func *= (vector: inout CGVector, scalar: CGFloat) { 152 | vector = vector * scalar 153 | } 154 | 155 | /** 156 | * Divides two CGVector values and returns the result as a new CGVector. 157 | */ 158 | public func / (left: CGVector, right: CGVector) -> CGVector { 159 | return CGVector(dx: left.dx / right.dx, dy: left.dy / right.dy) 160 | } 161 | 162 | /** 163 | * Divides a CGVector by another. 164 | */ 165 | public func /= (left: inout CGVector, right: CGVector) { 166 | left = left / right 167 | } 168 | 169 | /** 170 | * Divides the dx and dy fields of a CGVector by the same scalar value and 171 | * returns the result as a new CGVector. 172 | */ 173 | public func / (vector: CGVector, scalar: CGFloat) -> CGVector { 174 | return CGVector(dx: vector.dx / scalar, dy: vector.dy / scalar) 175 | } 176 | 177 | /** 178 | * Divides the dx and dy fields of a CGVector by the same scalar value. 179 | */ 180 | public func /= (vector: inout CGVector, scalar: CGFloat) { 181 | vector = vector / scalar 182 | } 183 | 184 | /** 185 | * Performs a linear interpolation between two CGVector values. 186 | */ 187 | public func lerp(start: CGVector, end: CGVector, t: CGFloat) -> CGVector { 188 | return start + (end - start) * t 189 | } 190 | -------------------------------------------------------------------------------- /CircleColorPicker/CircleColorPicker.h: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | #import 23 | 24 | //! Project version number for CircleColorPicker. 25 | FOUNDATION_EXPORT double CircleColorPickerVersionNumber; 26 | 27 | //! Project version string for CircleColorPicker. 28 | FOUNDATION_EXPORT const unsigned char CircleColorPickerVersionString[]; 29 | 30 | // In this header, you should import all the public headers of your framework using statements like #import 31 | 32 | 33 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/ColorSampleCircleView.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import UIKit 23 | 24 | internal class ColorSampleCircleView: UIView { 25 | 26 | internal var ringWidth: CGFloat = 8.0 { 27 | didSet { 28 | resizeInnerCircle() 29 | } 30 | } 31 | 32 | internal var innerCircle: UIView! 33 | 34 | override init(frame: CGRect) { 35 | super.init(frame: frame) 36 | setupInnerCircle() 37 | 38 | } 39 | 40 | required public init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | setupInnerCircle() 43 | 44 | } 45 | 46 | func setupInnerCircle() { 47 | innerCircle = UIView() 48 | self.addSubview(innerCircle) 49 | } 50 | 51 | 52 | override func layoutSubviews() { 53 | super.layoutSubviews() 54 | resizeInnerCircle() 55 | self.layer.cornerRadius = self.bounds.width * 0.5 56 | } 57 | 58 | func setSampleColor(color: UIColor){ 59 | innerCircle.backgroundColor = color 60 | self.backgroundColor = color.withAlphaComponent(0.5) 61 | } 62 | 63 | func resizeInnerCircle() { 64 | 65 | innerCircle.frame = innerCircleRect 66 | innerCircle.layer.cornerRadius = innerCircleRadius 67 | } 68 | 69 | private var fullRadius: CGFloat { 70 | get{ 71 | let smaller = min(self.frame.size.width, self.frame.size.height) 72 | return smaller * 0.5 73 | } 74 | } 75 | 76 | private var innerCircleRadius: CGFloat { 77 | get{ 78 | let smaller = min(self.frame.size.width, self.frame.size.height) 79 | return smaller * 0.5 - ringWidth 80 | } 81 | } 82 | 83 | private var innerCircleRect: CGRect { 84 | get{ 85 | return CGRect.init(origin: origo - CGVector(dx: innerCircleRadius, dy: innerCircleRadius), size: CGSize.init(width: innerCircleRadius * 2, height: innerCircleRadius * 2)) 86 | } 87 | 88 | } 89 | 90 | private var origo: CGPoint { 91 | get{ 92 | return CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2) 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/RainbowCircleView.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import UIKit 23 | 24 | internal class RainbowCircleView: UIView { 25 | var rainbowImage: CGImage! 26 | 27 | var rainbowRadius: CGFloat = 100.0 { 28 | didSet{ 29 | setNeedsDisplay() 30 | } 31 | } 32 | 33 | var rainbowWidth: CGFloat = 8.0 { 34 | didSet{ 35 | setNeedsDisplay() 36 | } 37 | } 38 | 39 | override func layoutSubviews() { 40 | super.layoutSubviews() 41 | rainbowImage = drawColorCircleImage(withDiameter: Int(min(self.bounds.size.width, self.bounds.size.height))) 42 | } 43 | 44 | func drawColorCircleImage(withDiameter diameter: Int) -> CGImage { 45 | let bufferLength: Int = Int(diameter * diameter * 4) 46 | 47 | let bitmapData: CFMutableData = CFDataCreateMutable(nil, 0) 48 | CFDataSetLength(bitmapData, CFIndex(bufferLength)) 49 | let bitmap = CFDataGetMutableBytePtr(bitmapData) 50 | 51 | for y in 0 ... diameter { 52 | for x in 0 ... diameter { 53 | let angle = CGVector(point: CGPoint(x: x, y: diameter-y) - origo).angle 54 | let rgbComponents = UIColor.init(hue: getHue(at: angle), saturation: 1, brightness: 1, alpha: 1).cgColor.components! 55 | 56 | let offset = Int(4 * (x + y * diameter)) 57 | bitmap?[offset] = UInt8(rgbComponents[0]*255) 58 | bitmap?[offset + 1] = UInt8(rgbComponents[1]*255) 59 | bitmap?[offset + 2] = UInt8(rgbComponents[2]*255) 60 | bitmap?[offset + 3] = UInt8(255) 61 | } 62 | } 63 | 64 | let colorSpace: CGColorSpace? = CGColorSpaceCreateDeviceRGB() 65 | let dataProvider: CGDataProvider? = CGDataProvider(data: bitmapData) 66 | let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.last.rawValue) 67 | let imageRef: CGImage? = CGImage(width: diameter, height: diameter, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: diameter * 4, space: colorSpace!, bitmapInfo: bitmapInfo, provider: dataProvider!, decode: nil, shouldInterpolate: false, intent: CGColorRenderingIntent.defaultIntent) 68 | return imageRef! 69 | } 70 | 71 | override func draw(_ rect: CGRect) { 72 | super.draw(rect) 73 | guard let context = UIGraphicsGetCurrentContext() else { 74 | return 75 | } 76 | 77 | let circle = UIBezierPath(ovalIn: CGRect(x: origo.x - rainbowRadius, y: origo.y - rainbowRadius , width: rainbowRadius*2, height: rainbowRadius*2)) 78 | let shapeCopyPath = circle.cgPath.copy(strokingWithWidth: rainbowWidth, lineCap: .butt, lineJoin: .bevel, miterLimit: 0) 79 | context.addPath(shapeCopyPath) 80 | 81 | context.clip(using: .winding) 82 | context.draw(rainbowImage, in: self.bounds) 83 | context.resetClip() 84 | } 85 | 86 | var origo: CGPoint { 87 | get{ 88 | return CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2) 89 | } 90 | } 91 | 92 | func getHue(at radians: CGFloat) -> CGFloat { 93 | return ((radians.radiansToDegrees()+360).truncatingRemainder(dividingBy: 360))/360 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/CircleColorPickerView/CircleColorPickerView+TouchHandling.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import UIKit 23 | 24 | extension CircleColorPickerView { //Touch handling 25 | 26 | override open func touchesBegan(_ touches: Set, with event: UIEvent?) { 27 | guard let touch = touches.first else{ 28 | return 29 | } 30 | 31 | let coordinate = touch.location(in: self) 32 | if abs(coordinate.distanceTo(self.origo) - rainbowRadius) < bubbleRadius + rainbowWidth { 33 | isBubbleDragged = true 34 | onShouldMoveBubble(dragCoordinate: coordinate) 35 | } 36 | } 37 | 38 | override open func touchesMoved(_ touches: Set, with event: UIEvent?) { 39 | guard let touch = touches.first else{ 40 | return 41 | } 42 | let coordinate = touch.location(in: self) 43 | 44 | if isBubbleDragged { 45 | onShouldMoveBubble(dragCoordinate: coordinate) 46 | } 47 | } 48 | 49 | override open func touchesEnded(_ touches: Set, with event: UIEvent?) { 50 | guard touches.first != nil else{ 51 | return 52 | } 53 | 54 | isBubbleDragged = false 55 | } 56 | 57 | private func onShouldMoveBubble(dragCoordinate: CGPoint) { 58 | let dragAngle = calculateDesiredAngleFor(dragCoordinate: dragCoordinate) 59 | UIView.animate(withDuration: animationTimeInSeconds, animations: { 60 | self.colorBubbleView.transform = CGAffineTransform(rotationAngle: dragAngle) 61 | let currentRads:CGFloat = CGFloat(atan2f(Float(self.colorBubbleView.transform.b), Float(self.colorBubbleView.transform.a))) 62 | self.hue = self.rainbowCircleView.getHue(at: currentRads) 63 | self.delegate?.onColorChanged(newColor: self.color) 64 | }) 65 | } 66 | 67 | private func calculateDesiredAngleFor(dragCoordinate: CGPoint) -> CGFloat{ 68 | let deltaInDegrees:Float = 5 69 | let sectorSizeInDegrees:Float = 60 70 | var dragAngle = CGVector(point: dragCoordinate - origo).angle.radiansToDegrees() 71 | if dragAngle < 0 { 72 | dragAngle = dragAngle + 360 73 | } 74 | 75 | let mod = fmodf(Float(dragAngle), sectorSizeInDegrees) 76 | print(mod) 77 | let partition = Int(Float(dragAngle) / sectorSizeInDegrees) 78 | if mod < deltaInDegrees { 79 | dragAngle = CGFloat(partition) * CGFloat(sectorSizeInDegrees) 80 | }else if mod > 60 - deltaInDegrees { 81 | dragAngle = CGFloat(partition+1) * CGFloat(sectorSizeInDegrees) 82 | } 83 | dragAngle = dragAngle.degreesToRadians() 84 | return dragAngle 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/CircleColorPickerView/CircleColorPickerView.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import UIKit 23 | 24 | @IBDesignable 25 | open class CircleColorPickerView: UIView { 26 | var contentView : UIView? 27 | public weak var delegate: CircleColorPickerViewDelegate? 28 | 29 | open var animationTimeInSeconds:Double = 0.2 30 | open var shouldBrightnessAffectColorSample = true 31 | 32 | public var saturation: CGFloat { 33 | get { 34 | return saturationPickerView?.value ?? 1 35 | } 36 | set { 37 | saturationPickerView?.value = newValue 38 | updateAllViews() 39 | } 40 | } 41 | 42 | public var brightness: CGFloat { 43 | get { 44 | return brightnessPickerView?.value ?? 1 45 | } 46 | set { 47 | brightnessPickerView?.value = newValue 48 | updateAllViews() 49 | } 50 | } 51 | 52 | public var hue: CGFloat = 0 { 53 | didSet{ 54 | updateAllViews() 55 | } 56 | } 57 | 58 | @IBInspectable 59 | public var rainbowWidth: CGFloat = 8.0 { 60 | didSet { 61 | updateRainbowMetrics() 62 | updateBubbleMetrics() 63 | } 64 | } 65 | 66 | @IBInspectable 67 | public var bubbleRadius:CGFloat = 18.0 { 68 | didSet{ 69 | colorBubbleView.bubbleRadius = bubbleRadius 70 | } 71 | } 72 | 73 | @IBInspectable 74 | public var color: CGColor { 75 | get { 76 | return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1).cgColor 77 | } 78 | 79 | set { 80 | let color = UIColor.init(cgColor: newValue) 81 | var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 82 | color.getHue(&h, saturation: &s, brightness: &b, alpha: &a) 83 | self.hue = h 84 | self.saturation = s 85 | self.brightness = b 86 | setBubbleAngleForCurrentHue() 87 | updateAllViews() 88 | } 89 | } 90 | 91 | @IBInspectable 92 | public var centerDiameter: CGFloat = 80.0 { 93 | didSet { 94 | sampleViewRadius.constant = centerDiameter 95 | setNeedsLayout() 96 | } 97 | } 98 | 99 | @IBInspectable 100 | public var centerRingWidth: CGFloat = 8.0 { 101 | didSet { 102 | colorSampleView.ringWidth = centerRingWidth 103 | colorSampleView.setNeedsDisplay() 104 | } 105 | } 106 | 107 | @IBOutlet weak var rainbowCircleView: RainbowCircleView! 108 | @IBOutlet weak var colorBubbleView: ColorBubbleView! 109 | open weak var saturationPickerView: SaturationPickerView? { 110 | didSet { 111 | saturationPickerView?.onValueChange = { _ in 112 | self.updateAllViews() 113 | self.delegate?.onColorChanged(newColor: self.color) 114 | } 115 | updateAllViews() 116 | } 117 | } 118 | 119 | open weak var brightnessPickerView: BrightnessPickerView? { 120 | didSet { 121 | brightnessPickerView?.onValueChange = { _ in 122 | self.updateAllViews() 123 | self.delegate?.onColorChanged(newColor: self.color) 124 | } 125 | updateAllViews() 126 | } 127 | } 128 | 129 | @IBOutlet weak var colorSampleView: ColorSampleCircleView! 130 | 131 | @IBOutlet weak var sampleViewRadius: NSLayoutConstraint! 132 | 133 | internal var isBubbleDragged = false 134 | 135 | override public init(frame: CGRect) { 136 | super.init(frame: frame) 137 | xibSetup() 138 | setupMaskImages() 139 | onStart() 140 | } 141 | 142 | required public init?(coder aDecoder: NSCoder) { 143 | super.init(coder: aDecoder) 144 | xibSetup() 145 | setupMaskImages() 146 | onStart() 147 | } 148 | 149 | open override func prepareForInterfaceBuilder() { 150 | super.prepareForInterfaceBuilder() 151 | setupMaskImages() 152 | } 153 | 154 | open override func willMove(toWindow newWindow: UIWindow?) { 155 | super.willMove(toWindow: newWindow) 156 | if newWindow != nil { 157 | updateAllViews() 158 | } 159 | } 160 | 161 | open override func layoutSubviews() { 162 | super.layoutSubviews() 163 | updateBubbleMetrics() 164 | updateRainbowMetrics() 165 | } 166 | 167 | public func setupMaskImages(image: UIImage? = Optional.none) { 168 | if let image = image { 169 | colorBubbleView.ringMaskImageView.image = image 170 | }else { 171 | let podBundle = Bundle(for: CircleColorPickerView.self) 172 | if let bundleUrl = podBundle.url(forResource: "CircleColorPicker", withExtension: "bundle"), 173 | let bundle = Bundle(url: bundleUrl) { 174 | let retrievedImage = UIImage(named: "ringMask", in: bundle, compatibleWith: nil) 175 | colorBubbleView.ringMaskImageView.image = retrievedImage 176 | } 177 | } 178 | } 179 | 180 | private func xibSetup() { 181 | contentView = UIView.fromNib(named: String(describing: CircleColorPickerView.self), 182 | bundle: Bundle(for: CircleColorPickerView.self), owner: self)! 183 | contentView!.frame = bounds 184 | 185 | contentView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] 186 | addSubview(contentView!) 187 | } 188 | 189 | private func onStart() { 190 | updateBubbleMetrics() 191 | updateRainbowMetrics() 192 | } 193 | 194 | private func updateBubbleMetrics() { 195 | colorBubbleView.bubbleRadius = bubbleRadius 196 | colorBubbleView.rainbowRadius = self.rainbowRadius 197 | } 198 | 199 | private func updateRainbowMetrics() { 200 | rainbowCircleView.rainbowWidth = rainbowWidth 201 | rainbowCircleView.rainbowRadius = self.rainbowRadius 202 | } 203 | 204 | private func updateAllViews() { 205 | let color = UIColor.init(cgColor: self.color) 206 | let colorWithOnlyHue = UIColor.init(hue: self.hue, saturation: 1, brightness: 1, alpha: 1) 207 | let colorWithSaturation = UIColor.init(hue: self.hue, saturation: saturation, brightness: 1, alpha: 1) 208 | let colorWithBrightness = UIColor.init(hue: self.hue, saturation: 1, brightness: brightness, alpha: 1) 209 | 210 | if shouldBrightnessAffectColorSample { 211 | colorSampleView.setSampleColor(color: color) 212 | }else { 213 | colorSampleView.setSampleColor(color: colorWithSaturation) 214 | } 215 | 216 | colorBubbleView.setBubbleColor(color: colorWithOnlyHue) 217 | saturationPickerView?.backgroundColor = colorWithOnlyHue 218 | brightnessPickerView?.backgroundColor = colorWithOnlyHue 219 | saturationPickerView?.bubbleView.backgroundColor = colorWithSaturation 220 | brightnessPickerView?.bubbleView.backgroundColor = colorWithBrightness 221 | } 222 | 223 | private func setBubbleAngleForCurrentHue(){ 224 | self.colorBubbleView.transform = CGAffineTransform(rotationAngle: getRadians(for: hue)) 225 | } 226 | } 227 | 228 | extension CircleColorPickerView { 229 | var origo: CGPoint { 230 | get{ 231 | return CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2) 232 | } 233 | } 234 | 235 | var rainbowRadius: CGFloat { 236 | get{ 237 | let smaller = min(self.frame.size.width, self.frame.size.height) 238 | 239 | return smaller * 0.9 * 0.5 240 | } 241 | } 242 | 243 | func getRadians(for hue: CGFloat) -> CGFloat { 244 | return hue * 2 * CGFloat.pi 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/CircleColorPickerView/CircleColorPickerView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/CircleColorPickerView/CircleColorPickerViewDelegate.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import UIKit 23 | 24 | public protocol CircleColorPickerViewDelegate: class { 25 | func onColorChanged(newColor: CGColor) 26 | } 27 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/ColorBubbleView/ColorBubbleView.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import UIKit 23 | 24 | class ColorBubbleView: UIView { 25 | var contentView : UIView? 26 | 27 | var bubbleRadius: CGFloat = 18.0 { 28 | didSet{ 29 | bubbleWidth.constant = bubbleRadius * 2 30 | bubbleBackgroundView.layer.cornerRadius = bubbleRadius 31 | setNeedsLayout() 32 | } 33 | } 34 | 35 | var rainbowRadius: CGFloat = 8.0 { 36 | didSet{ 37 | bubblePosition.constant = rainbowRadius 38 | setNeedsLayout() 39 | } 40 | } 41 | 42 | @IBOutlet weak var bubblePosition: NSLayoutConstraint! 43 | 44 | @IBOutlet weak var bubbleBackgroundView: UIView! 45 | @IBOutlet weak var ringMaskImageView: UIImageView! 46 | @IBOutlet weak var bubbleWidth: NSLayoutConstraint! 47 | @IBOutlet weak var connectStringView: UIView! 48 | 49 | override public init(frame: CGRect) { 50 | super.init(frame: frame) 51 | xibSetup() 52 | } 53 | 54 | required public init?(coder aDecoder: NSCoder) { 55 | super.init(coder: aDecoder) 56 | xibSetup() 57 | } 58 | 59 | func setBubbleColor(color: UIColor){ 60 | bubbleBackgroundView.backgroundColor = color 61 | connectStringView.backgroundColor = color.withAlphaComponent(0.5) 62 | } 63 | 64 | private func xibSetup() { 65 | contentView = UIView.fromNib(named: String(describing: ColorBubbleView.self), 66 | bundle: Bundle(for: ColorBubbleView.self), owner: self)! 67 | contentView!.frame = bounds 68 | 69 | contentView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] 70 | addSubview(contentView!) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/ColorBubbleView/ColorBubbleView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/LinearPickers/BrightnessPickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BrightnessPickerView.swift 3 | // CircleColorPicker 4 | // 5 | // Created by Laszlo Pinter on 11/17/17. 6 | // Copyright © 2017 Laszlo Pinter. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class BrightnessPickerView: LinearPickerView { 12 | 13 | open override func handleOrientationChange() { 14 | (frontLayerView as! BrightnessMask).isVertical = isVertical 15 | } 16 | 17 | open override func createFrontLayerView() -> UIView{ 18 | let frontLayer = BrightnessMask(frame: CGRect.init(origin: CGPoint.zero, size: self.bounds.size)) 19 | frontLayer.isVertical = isVertical 20 | return frontLayer 21 | } 22 | 23 | class BrightnessMask: UIView { 24 | public var isVertical = false 25 | 26 | func drawScale(context: CGContext){ 27 | 28 | let startColor = UIColor.init(hue: 1, saturation: 0, brightness: 0, alpha: 1).cgColor 29 | let endColor = UIColor.init(hue: 1, saturation: 0, brightness: 0, alpha: 0).cgColor 30 | 31 | let colorSpace = CGColorSpaceCreateDeviceRGB() 32 | let colors = [startColor, endColor] as CFArray 33 | 34 | if let gradient = CGGradient.init(colorsSpace: colorSpace, colors: colors, locations: nil) { 35 | var startPoint: CGPoint! 36 | var endPoint: CGPoint! 37 | 38 | if isVertical { 39 | startPoint = CGPoint(x: self.bounds.width, y: self.bounds.height) 40 | endPoint = CGPoint(x: self.bounds.width, y: 0) 41 | }else { 42 | startPoint = CGPoint(x: 0, y: self.bounds.height) 43 | endPoint = CGPoint(x: self.bounds.width, y: self.bounds.height) 44 | } 45 | 46 | context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: .drawsBeforeStartLocation) 47 | } 48 | } 49 | 50 | override func draw(_ rect: CGRect) { 51 | super.draw(rect) 52 | guard let context = UIGraphicsGetCurrentContext() else { 53 | return 54 | } 55 | drawScale(context: context) 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/LinearPickers/LinearPickerView+TouchHandling.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import Foundation 23 | 24 | extension LinearPickerView { 25 | private var bubbleDragRadius: CGFloat { 26 | get{ 27 | return bubbleSize * 0.5 + 4 28 | } 29 | } 30 | 31 | override open func touchesBegan(_ touches: Set, with event: UIEvent?) { 32 | guard let touch = touches.first else{ 33 | return 34 | } 35 | 36 | let coordinate = touch.location(in: self) 37 | if shouldDragBubble(at: coordinate) { 38 | isBubbleDragged = true 39 | onShouldMoveBubble(dragCoordinate: coordinate) 40 | } 41 | } 42 | 43 | override open func touchesMoved(_ touches: Set, with event: UIEvent?) { 44 | guard let touch = touches.first else{ 45 | return 46 | } 47 | let coordinate = touch.location(in: self) 48 | 49 | if isBubbleDragged { 50 | onShouldMoveBubble(dragCoordinate: coordinate) 51 | } 52 | } 53 | 54 | override open func touchesEnded(_ touches: Set, with event: UIEvent?) { 55 | guard touches.first != nil else{ 56 | return 57 | } 58 | 59 | isBubbleDragged = false 60 | } 61 | 62 | private func shouldDragBubble(at point: CGPoint) -> Bool{ 63 | return hitTest(point, with: nil) != nil 64 | } 65 | 66 | private func onShouldMoveBubble(dragCoordinate: CGPoint) { 67 | let bounds = self.bounds 68 | 69 | if isVertical { 70 | let bubblePosition = calculateBubbleYPosition(forDragPosition: dragCoordinate) 71 | bubbleCenterY.constant = bubblePosition - bounds.midY 72 | }else { 73 | let bubblePosition = calculateBubbleXPosition(forDragPosition: dragCoordinate) 74 | bubbleCenterX.constant = bubblePosition - bounds.midX 75 | } 76 | 77 | UIView.animate(withDuration: animationTimeInSeconds, animations: { 78 | self.layoutIfNeeded() 79 | var percentage: CGFloat 80 | if self.isVertical { 81 | percentage = (-(self.bubbleView.frame.midY - bounds.midY) / bounds.height) + 0.5 82 | }else { 83 | percentage = ((self.bubbleView.frame.midX - bounds.midX) / bounds.width) + 0.5 84 | } 85 | self.storedValue = percentage 86 | self.onValueChange?(self.storedValue) 87 | }) 88 | } 89 | 90 | private func calculateBubbleXPosition(forDragPosition: CGPoint) -> CGFloat { 91 | let bounds = self.bounds 92 | 93 | var bubblePosition = forDragPosition.x 94 | if bubblePosition < bounds.minX { 95 | bubblePosition = bounds.minX 96 | }else if bubblePosition > bounds.maxX { 97 | bubblePosition = bounds.maxX 98 | } 99 | 100 | return bubblePosition 101 | } 102 | 103 | private func calculateBubbleYPosition(forDragPosition: CGPoint) -> CGFloat { 104 | let bounds = self.bounds 105 | 106 | var bubblePosition = forDragPosition.y 107 | if bubblePosition < bounds.minY { 108 | bubblePosition = bounds.minY 109 | }else if bubblePosition > bounds.maxY { 110 | bubblePosition = bounds.maxY 111 | } 112 | 113 | return bubblePosition 114 | } 115 | 116 | override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 117 | 118 | if isHidden || alpha == 0 { 119 | return nil 120 | } 121 | 122 | let frame = self.bounds 123 | let range = bubbleWidth.constant * 0.5 124 | let rect = CGRect(x: frame.minX-range , y: frame.minY-range, width: frame.width+range*2, height: frame.height+range*2) 125 | 126 | if rect.contains(point) { 127 | return self 128 | } 129 | return nil 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/LinearPickers/LinearPickerView.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import UIKit 23 | 24 | @IBDesignable 25 | open class LinearPickerView: UIView { 26 | open var animationTimeInSeconds:Double = 0.2 27 | open var onValueChange: ((CGFloat)->Void)? 28 | 29 | internal var storedValue: CGFloat = 0.5 30 | open var value: CGFloat { 31 | get{ 32 | return storedValue 33 | } 34 | set{ 35 | self.storedValue = newValue 36 | if isVertical { 37 | bubbleCenterY.constant = -(storedValue-0.5) * (self.bounds.size.height) 38 | }else { 39 | bubbleCenterX.constant = (storedValue-0.5) * (self.bounds.size.width) 40 | } 41 | } 42 | 43 | } 44 | 45 | @IBOutlet var contentView: UIView! 46 | @IBOutlet open weak var bubbleView: UIImageView! 47 | @IBOutlet weak var bubbleWidth: NSLayoutConstraint! 48 | @IBOutlet weak var bubbleCenterX: NSLayoutConstraint! 49 | @IBOutlet weak var bubbleCenterY: NSLayoutConstraint! 50 | 51 | @IBInspectable var isVertical: Bool = false { 52 | didSet { 53 | handleOrientationChange() 54 | if isVertical { 55 | bubbleCenterX.constant = 0 56 | }else { 57 | bubbleCenterY.constant = 0 58 | } 59 | } 60 | } 61 | 62 | @IBInspectable 63 | public var bubbleSize: CGFloat = 20.0 { 64 | didSet { 65 | bubbleWidth.constant = bubbleSize 66 | bubbleView.layer.cornerRadius = bubbleSize * 0.5 67 | setNeedsLayout() 68 | bubbleView.setNeedsDisplay() 69 | } 70 | } 71 | 72 | internal var isBubbleDragged = false 73 | open var frontLayerView: UIView! 74 | 75 | override init(frame: CGRect) { 76 | super.init(frame: frame) 77 | setupInitialState() 78 | } 79 | 80 | required public init?(coder aDecoder: NSCoder) { 81 | super.init(coder: aDecoder) 82 | setupInitialState() 83 | } 84 | 85 | private func setupInitialState() { 86 | xibSetup() 87 | setupFrontLayerView() 88 | setupBubbleMaskImage() 89 | 90 | } 91 | 92 | private func xibSetup() { 93 | contentView = UIView.fromNib(named: String(describing: LinearPickerView.self), 94 | bundle: Bundle(for: LinearPickerView.self), owner: self)! 95 | contentView!.frame = bounds 96 | 97 | contentView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] 98 | addSubview(contentView!) 99 | } 100 | 101 | open func handleOrientationChange() { 102 | print("Subclasses of LinearPickerView should override handleOrientationChange() function.") 103 | } 104 | 105 | open func createFrontLayerView() -> UIView{ 106 | print("Subclasses of LinearPickerView should override createFrontLayerView() function.") 107 | return UIView(frame: CGRect.init(origin: CGPoint.zero, size: self.bounds.size)) 108 | } 109 | 110 | private func setupFrontLayerView() { 111 | frontLayerView = createFrontLayerView() 112 | frontLayerView.backgroundColor = UIColor.clear 113 | frontLayerView.setNeedsDisplay() 114 | contentView.insertSubview(frontLayerView, belowSubview: bubbleView) 115 | } 116 | 117 | public func setupBubbleMaskImage(image: UIImage? = Optional.none) { 118 | if let image = image { 119 | bubbleView.image = image 120 | }else { 121 | let podBundle = Bundle(for: LinearPickerView.self) 122 | if let bundleUrl = podBundle.url(forResource: "CircleColorPicker", withExtension: "bundle"), 123 | let bundle = Bundle(url: bundleUrl) { 124 | let retrievedImage = UIImage(named: "ringMask", in: bundle, compatibleWith: nil) 125 | bubbleView.image = retrievedImage 126 | } 127 | } 128 | } 129 | 130 | override open func layoutSubviews() { 131 | frontLayerView.frame = CGRect.init(origin: CGPoint.zero, size: self.bounds.size) 132 | let cornerRadius = min(self.bounds.size.width, self.bounds.size.height) * 0.5 133 | contentView.layer.cornerRadius = cornerRadius 134 | self.layer.cornerRadius = cornerRadius 135 | frontLayerView.layer.cornerRadius = cornerRadius 136 | frontLayerView.clipsToBounds = true 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/LinearPickers/LinearPickerView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CircleColorPicker/ColorPicker/Views/LinearPickers/SaturationPickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SaturationPickerView.swift 3 | // CircleColorPicker 4 | // 5 | // Created by Laszlo Pinter on 11/17/17. 6 | // Copyright © 2017 Laszlo Pinter. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class SaturationPickerView: LinearPickerView { 12 | 13 | open override func handleOrientationChange() { 14 | (frontLayerView as! SaturationMask).isVertical = isVertical 15 | } 16 | 17 | open override func createFrontLayerView() -> UIView{ 18 | let frontLayer = SaturationMask(frame: CGRect.init(origin: CGPoint.zero, size: self.bounds.size)) 19 | frontLayer.isVertical = isVertical 20 | return frontLayer 21 | } 22 | 23 | class SaturationMask: UIView { 24 | public var isVertical = false 25 | 26 | func drawScale(context: CGContext){ 27 | 28 | let startColor = UIColor.init(hue: 1, saturation: 0, brightness: 1, alpha: 1).cgColor 29 | let endColor = UIColor.init(hue: 1, saturation: 0, brightness: 1, alpha: 0).cgColor 30 | 31 | let colorSpace = CGColorSpaceCreateDeviceRGB() 32 | let colors = [startColor, endColor] as CFArray 33 | 34 | if let gradient = CGGradient.init(colorsSpace: colorSpace, colors: colors, locations: nil) { 35 | var startPoint: CGPoint! 36 | var endPoint: CGPoint! 37 | 38 | if isVertical { 39 | startPoint = CGPoint(x: self.bounds.width, y: self.bounds.height) 40 | endPoint = CGPoint(x: self.bounds.width, y: 0) 41 | }else { 42 | startPoint = CGPoint(x: 0, y: self.bounds.height) 43 | endPoint = CGPoint(x: self.bounds.width, y: self.bounds.height) 44 | } 45 | 46 | context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: .drawsBeforeStartLocation) 47 | } 48 | } 49 | 50 | override func draw(_ rect: CGRect) { 51 | super.draw(rect) 52 | guard let context = UIGraphicsGetCurrentContext() else { 53 | return 54 | } 55 | drawScale(context: context) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CircleColorPicker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | CircleColorPicker 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 0.9 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CircleColorPicker/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import Foundation 23 | 24 | extension UIView { 25 | 26 | static func fromNib(named nibName: String, bundle podBundle: Bundle, owner: UIView) -> UIView? { 27 | 28 | if let bundleUrl = podBundle.url(forResource: "CircleColorPicker", withExtension: "bundle"), 29 | let bundle = Bundle(url: bundleUrl) { 30 | let nib = UINib(nibName: nibName, bundle: bundle) 31 | let view = nib.instantiate(withOwner: owner, options: nil).first as! UIView 32 | return view 33 | } 34 | 35 | return nil 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CircleColorPickerTests/CircleColorPickerTests.swift: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2017 Laszlo Pinter 2 | // 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy 4 | //of this software and associated documentation files (the "Software"), to deal 5 | //in the Software without restriction, including without limitation the rights 6 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | //copies of the Software, and to permit persons to whom the Software is 8 | //furnished to do so, subject to the following conditions: 9 | // 10 | //The above copyright notice and this permission notice shall be included in all 11 | //copies or substantial portions of the Software. 12 | // 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | //SOFTWARE. 20 | // 21 | 22 | import XCTest 23 | @testable import CircleColorPicker 24 | 25 | class CircleColorPickerTests: XCTestCase { 26 | 27 | override func setUp() { 28 | super.setUp() 29 | } 30 | 31 | override func tearDown() { 32 | super.tearDown() 33 | } 34 | 35 | func testExample() { 36 | 37 | } 38 | 39 | func testPerformanceExample() { 40 | self.measure { 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /CircleColorPickerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Laszlo Pinter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Circle Color Picker 2 | 3 | ![Version](https://img.shields.io/cocoapods/v/CircleColorPicker.svg?style=flat) 4 | ![License](https://img.shields.io/cocoapods/l/CircleColorPicker.svg?style=flat) 5 | ![Platform](https://img.shields.io/cocoapods/p/CircleColorPicker.svg?style=flat) 6 | 7 | 8 | This is a highly customizable color picker view written in Swift. 9 | 10 | This is a working product but still an ongoing project with enhancement and refactor ideas. You can find a list at the bottom of this page. Please feel free to contribute or star the project if you like it. 11 | 12 | ## Screenshots 13 | 14 | 15 | 16 | 17 | 18 | ## Installation 19 | 20 | ### CocoaPods 21 | 22 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 23 | 24 | ```bash 25 | $ gem install cocoapods 26 | ``` 27 | 28 | 29 | To integrate CircleColorPicker into your Xcode project using CocoaPods, specify it in your `Podfile`: 30 | 31 | ```ruby 32 | platform :ios, '11.0' 33 | 34 | use_frameworks! 35 | 36 | target 'WiledMoods' do 37 | pod 'CircleColorPicker', '~> 1.0.0' 38 | 39 | end 40 | ``` 41 | 42 | Then, run the following command: 43 | 44 | ```bash 45 | $ pod install 46 | ``` 47 | 48 | ## How to use 49 | 50 | Add a UIView to one of your ViewControllers on interface builder and set it's class to *CircleColorPickerView*. Alternatively you can instantiate and add it programmatically. 51 | 52 | ### IBDesignable properties 53 | 54 | 55 | - *Rainbow Width* is the width of the color ring. Default value is 8.0. 56 | - *Bubble Radius* is the radius of the color bubble on the ring. (18 by default) 57 | - *Center Diameter* is the diameter of the color sample at the center of the picker. (80 by default) 58 | - *Center Ring Width* is the width translucent edge of the color sample view. (8 by default) 59 | 60 | ### Public properties 61 | 62 | These values have both getters and setters: 63 | 64 | - color: *(CGColor)* is the current color of the picker 65 | - hue: *(CGFloat)* is the current hue value of the selected color 66 | - animation time: *(Double)* is the duration of the animation when user selects a new color. 67 | - delegate : *(weak CircleColorPickerViewDelegate?)* is the delegate of your picker. 68 | - saturationPickerView: *(weak SaturationPickerView?)* is an optional saturation picker that works with the 69 | color circle. Enables saturation selection. 70 | - saturation: *(CGFloat)* is the current saturation value of the selected color. (Only works if saturationPickerView is assigned otherwise always returns 1.) 71 | 72 | This means that you can make animations faster or slower as you prefer. 73 | 74 | You can also change the images of the bubbles using: 75 | 76 | ```swift 77 | colorPickerView.setupMaskImages(image: bubbleImage) 78 | ``` 79 | 80 | where bubbleImage should be the UIImage of your choice. 81 | 82 | 83 | 84 | 85 | ### Delegation of color change 86 | 87 | 88 | To get notified when the user selects a new color on the picker implement the: 89 | 90 | ```swift 91 | func onColorChanged(newColor: CGColor) 92 | ``` 93 | function of the `CircleColorPickerViewDelegate` interface and set the delegate of your CircleColorPickerView. 94 | 95 | 96 | ## Whats next? 97 | 98 | 99 | ### TODOs 100 | - Carthage support 101 | - Swift package manager support 102 | - More detailed description 103 | - Tests 104 | 105 | ### Ideas 106 | - Should be a function the set the color of the picker with animation (just as when user picks a color) 107 | 108 | ## Apps using this library 109 | Thanks for using **CircleColorPicker**. If you have an app that is using this library and want it listed here just drop a message. 110 | -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/a2e96c2d25be0b3f77ad4b7d1d06350a86729208/screenshots/screenshot1.png -------------------------------------------------------------------------------- /screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaszloPinter/CircleColorPicker/a2e96c2d25be0b3f77ad4b7d1d06350a86729208/screenshots/screenshot2.png --------------------------------------------------------------------------------