Initial commit

This commit is contained in:
Cardasis 2016-08-11 14:40:04 -04:00
parent 5c04c2cb8f
commit 0830687e0f
8 changed files with 859 additions and 5 deletions

View File

@ -12,6 +12,11 @@
35C376D61D5CF5300069D7A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D41D5CF5300069D7A1 /* Main.storyboard */; };
35C376D81D5CF5300069D7A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D71D5CF5300069D7A1 /* Assets.xcassets */; };
35C376DB1D5CF5300069D7A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35C376D91D5CF5300069D7A1 /* LaunchScreen.storyboard */; };
35C376E71D5CF9400069D7A1 /* JCColorAddButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C376E21D5CF9400069D7A1 /* JCColorAddButton.swift */; };
35C376E81D5CF9400069D7A1 /* JCColorHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C376E31D5CF9400069D7A1 /* JCColorHandle.swift */; };
35C376E91D5CF9400069D7A1 /* JCColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C376E41D5CF9400069D7A1 /* JCColorPicker.swift */; };
35C376EA1D5CF9400069D7A1 /* JCShadeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C376E51D5CF9400069D7A1 /* JCShadeSlider.swift */; };
35C376EB1D5CF9400069D7A1 /* UIColor+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C376E61D5CF9400069D7A1 /* UIColor+Utilities.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -22,6 +27,11 @@
35C376D71D5CF5300069D7A1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
35C376DA1D5CF5300069D7A1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
35C376DC1D5CF5300069D7A1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
35C376E21D5CF9400069D7A1 /* JCColorAddButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JCColorAddButton.swift; path = JCColorPicker/JCColorAddButton.swift; sourceTree = SOURCE_ROOT; };
35C376E31D5CF9400069D7A1 /* JCColorHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JCColorHandle.swift; path = JCColorPicker/JCColorHandle.swift; sourceTree = SOURCE_ROOT; };
35C376E41D5CF9400069D7A1 /* JCColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JCColorPicker.swift; path = JCColorPicker/JCColorPicker.swift; sourceTree = SOURCE_ROOT; };
35C376E51D5CF9400069D7A1 /* JCShadeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JCShadeSlider.swift; path = JCColorPicker/JCShadeSlider.swift; sourceTree = SOURCE_ROOT; };
35C376E61D5CF9400069D7A1 /* UIColor+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIColor+Utilities.swift"; path = "JCColorPicker/UIColor+Utilities.swift"; sourceTree = SOURCE_ROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -54,6 +64,7 @@
35C376CF1D5CF5300069D7A1 /* JCColorPicker-Demo */ = {
isa = PBXGroup;
children = (
35C376EC1D5CF9450069D7A1 /* JCColorPicker */,
35C376D01D5CF5300069D7A1 /* AppDelegate.swift */,
35C376D21D5CF5300069D7A1 /* ViewController.swift */,
35C376D41D5CF5300069D7A1 /* Main.storyboard */,
@ -64,6 +75,18 @@
path = "JCColorPicker-Demo";
sourceTree = "<group>";
};
35C376EC1D5CF9450069D7A1 /* JCColorPicker */ = {
isa = PBXGroup;
children = (
35C376E41D5CF9400069D7A1 /* JCColorPicker.swift */,
35C376E31D5CF9400069D7A1 /* JCColorHandle.swift */,
35C376E21D5CF9400069D7A1 /* JCColorAddButton.swift */,
35C376E51D5CF9400069D7A1 /* JCShadeSlider.swift */,
35C376E61D5CF9400069D7A1 /* UIColor+Utilities.swift */,
);
name = JCColorPicker;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -96,6 +119,7 @@
TargetAttributes = {
35C376CC1D5CF5300069D7A1 = {
CreatedOnToolsVersion = 7.3;
DevelopmentTeam = 43DKZUY8C6;
};
};
};
@ -135,8 +159,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
35C376E91D5CF9400069D7A1 /* JCColorPicker.swift in Sources */,
35C376D31D5CF5300069D7A1 /* ViewController.swift in Sources */,
35C376D11D5CF5300069D7A1 /* AppDelegate.swift in Sources */,
35C376E81D5CF9400069D7A1 /* JCColorHandle.swift in Sources */,
35C376EB1D5CF9400069D7A1 /* UIColor+Utilities.swift in Sources */,
35C376E71D5CF9400069D7A1 /* JCColorAddButton.swift in Sources */,
35C376EA1D5CF9400069D7A1 /* JCShadeSlider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -249,6 +278,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = "JCColorPicker-Demo/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.jonathancardasis.JCColorPicker-Demo";
@ -260,6 +290,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = "JCColorPicker-Demo/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.jonathancardasis.JCColorPicker-Demo";
@ -286,6 +317,7 @@
35C376E11D5CF5300069D7A1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};

View File

@ -5,6 +5,22 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "35C376CC1D5CF5300069D7A1"
BuildableName = "JCColorPicker-Demo.app"
BlueprintName = "JCColorPicker-Demo"
ReferencedContainer = "container:JCColorPicker-Demo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
@ -13,6 +29,15 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "35C376CC1D5CF5300069D7A1"
BuildableName = "JCColorPicker-Demo.app"
BlueprintName = "JCColorPicker-Demo"
ReferencedContainer = "container:JCColorPicker-Demo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
@ -26,6 +51,16 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "35C376CC1D5CF5300069D7A1"
BuildableName = "JCColorPicker-Demo.app"
BlueprintName = "JCColorPicker-Demo"
ReferencedContainer = "container:JCColorPicker-Demo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
@ -35,6 +70,16 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "35C376CC1D5CF5300069D7A1"
BuildableName = "JCColorPicker-Demo.app"
BlueprintName = "JCColorPicker-Demo"
ReferencedContainer = "container:JCColorPicker-Demo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -1,25 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="JCColorPicker_Demo" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="736"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="X0b-eI-A9U">
<rect key="frame" x="182" y="656" width="50" height="50"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="50" id="VaL-Xe-gHc"/>
<constraint firstAttribute="width" constant="50" id="oSC-v5-nta"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="6"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="JCColorPicker" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="guL-Cc-TeO">
<rect key="frame" x="104" y="51" width="207" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="PkF-mH-S41"/>
<constraint firstAttribute="width" constant="207" id="XOI-CR-PLS"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="26"/>
<color key="textColor" red="0.92549020051956177" green="0.93725490570068359" blue="0.94509804248809814" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="See ViewController for how JCColorPicker gets added here." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="4" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bWG-XF-nzw">
<rect key="frame" x="94" y="273" width="227" height="189"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="0.62745100259780884" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.37647058823529411" green="0.49019607843137253" blue="0.54509803921568623" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="guL-Cc-TeO" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="NeS-Sh-S22"/>
<constraint firstItem="X0b-eI-A9U" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="j8O-1F-Ipd"/>
<constraint firstItem="guL-Cc-TeO" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="31" id="jcY-2d-hDz"/>
<constraint firstItem="wfy-db-euE" firstAttribute="top" secondItem="X0b-eI-A9U" secondAttribute="bottom" constant="30" id="zjG-2C-dZg"/>
</constraints>
</view>
<simulatedScreenMetrics key="simulatedDestinationMetrics" type="retina55"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="139" y="366"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,77 @@
//
// JCColorAddButton.swift
//
// Created by Jonathan Cardasis on 5/16/16.
// Copyright © 2016 Jonathan Cardasis. All rights reserved.
//
import UIKit
class JCColorAddButton: UIButton {
var color = UIColor.grayColor(){
didSet{
if let layer = circleLayer{
layer.fillColor = color.CGColor
layer.strokeColor = color.darkerColor(0.075).CGColor
}
}
}
override var frame: CGRect{ //update on frame change
didSet{
self.layoutCircleLayer()
self.layoutPlusIconLayer()
}
}
var circleLayer: CAShapeLayer?
var plusIconLayer: CAShapeLayer?
override init(frame: CGRect) {
super.init(frame: frame)
self.createGraphics()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.createGraphics()
}
func createGraphics(){
circleLayer = CAShapeLayer()
self.layoutCircleLayer()
circleLayer!.fillColor = color.CGColor
self.layer.addSublayer(circleLayer!)
/* Create Plus Icon */
let plusPath = UIBezierPath()
plusPath.moveToPoint(CGPointMake(self.bounds.width/2 - self.bounds.width/8, self.bounds.height/2))
plusPath.addLineToPoint(CGPointMake(self.bounds.width/2 + self.bounds.width/8, self.bounds.height/2))
plusPath.moveToPoint(CGPointMake(self.bounds.width/2, self.bounds.height/2 + self.bounds.height/8))
plusPath.addLineToPoint(CGPointMake(self.bounds.width/2, self.bounds.height/2 - self.bounds.height/8))
plusIconLayer = CAShapeLayer()
self.layoutPlusIconLayer()
plusIconLayer!.strokeColor = UIColor.whiteColor().CGColor
self.layer.addSublayer(plusIconLayer!)
}
func layoutCircleLayer(){
if let layer = circleLayer{
layer.path = UIBezierPath(ovalInRect: self.bounds).CGPath
layer.lineWidth = frame.width * 0.04 //4 for size (100,100)
}
}
func layoutPlusIconLayer(){
if let layer = plusIconLayer{
let plusPath = UIBezierPath()
plusPath.moveToPoint(CGPointMake(self.bounds.width/2 - self.bounds.width/8, self.bounds.height/2))
plusPath.addLineToPoint(CGPointMake(self.bounds.width/2 + self.bounds.width/8, self.bounds.height/2))
plusPath.moveToPoint(CGPointMake(self.bounds.width/2, self.bounds.height/2 + self.bounds.height/8))
plusPath.addLineToPoint(CGPointMake(self.bounds.width/2, self.bounds.height/2 - self.bounds.height/8))
layer.path = plusPath.CGPath
layer.lineWidth = frame.width * 0.03
}
}
}

View File

@ -0,0 +1,57 @@
//
// JCColorHandle.swift
//
// Created by Jonathan Cardasis on 5/16/16.
// Copyright © 2016 Jonathan Cardasis. All rights reserved.
//
import UIKit
class JCColorHandle: UIView {
var color = UIColor.blackColor() {
didSet{
circleLayer.fillColor = color.CGColor
}
}
override var frame: CGRect{
didSet { self.layoutCircleLayer() }
}
var circleLayer = CAShapeLayer()
var shadowOffset: CGSize?{
set{
if let offset = newValue {
circleLayer.shadowColor = UIColor.blackColor().CGColor
circleLayer.shadowRadius = 3
circleLayer.shadowOpacity = 0.3
circleLayer.shadowOffset = offset
}
}
get{
return circleLayer.shadowOffset
}
}
override init(frame: CGRect) {
super.init(frame:frame)
self.backgroundColor = UIColor.clearColor()
/* Add Shape Layer */
//circleLayer.shouldRasterize = true
self.layoutCircleLayer()
circleLayer.strokeColor = UIColor.whiteColor().CGColor
circleLayer.fillColor = color.CGColor
self.layer.addSublayer(circleLayer)
}
func layoutCircleLayer(){
circleLayer.path = UIBezierPath(ovalInRect: self.bounds).CGPath
circleLayer.strokeColor = UIColor.whiteColor().CGColor
circleLayer.lineWidth = frame.width/8.75 //4
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,359 @@
//
// JCColorPicker.swift
//
// Created by Jonathan Cardasis on 5/9/16.
// Copyright © 2016 Jonathan Cardasis. All rights reserved.
//
import UIKit
protocol JCColorPickerDelegate {
/* Called when the user tapps the add button in the center */
func colorPickerDidChooseColor(colorPicker: JCColorPicker, color: UIColor)
}
class JCColorPicker: UIControl {
var hexLabel: UILabel!
var shadeSlider: JCShadeSlider!
var handleView: JCColorHandle!
var handleLine: CAShapeLayer!
var addButton: JCColorAddButton!
var currentColor = UIColor()
var delegate: JCColorPickerDelegate?
var currentAngle: Float = 0
var radius: CGFloat = 0
var stroke: CGFloat = 1
var padding: CGFloat = 15
var handleSize: CGSize{
get{ return CGSizeMake(self.bounds.width * 0.1, self.bounds.height * 0.1) }
}
//MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit(){
self.backgroundColor = UIColor.clearColor()
let minDimension = min(self.bounds.size.width, self.bounds.size.height)
radius = minDimension/2 - handleSize.width/2
/* Setup Handle */
handleView = JCColorHandle(frame: CGRectMake(0,0, handleSize.width, handleSize.height))
handleView.shadowOffset = CGSizeMake(0,2)
/* Setup pan gesture for handle */
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(JCColorPicker.handleWasMoved(_:)))
handleView.addGestureRecognizer(panRecognizer)
/* Setup Add Button */
addButton = JCColorAddButton()
self.layoutAddButton() //layout frame
addButton.addTarget(self, action: #selector(JCColorPicker.addButtonPressed(_:)), forControlEvents: .TouchUpInside)
/* Setup Handle Line */
handleLine = CAShapeLayer()
handleLine.lineWidth = 2
handleLine.strokeColor = UIColor.whiteColor().colorWithAlphaComponent(0.2).CGColor
/* Setup Color Hex Label */
hexLabel = UILabel()
self.layoutHexLabel() //layout frame
hexLabel.layer.cornerRadius = 2
hexLabel.adjustsFontSizeToFitWidth = true
hexLabel.textAlignment = .Center
hexLabel.textColor = UIColor(red: 51/255.0, green:51/255.0, blue: 51/255.0, alpha: 0.65)
/* Setup Shade Slider */
shadeSlider = JCShadeSlider()
shadeSlider.delegate = self
self.layoutShadeSlider()
/* Add components to view */
self.layer.addSublayer(handleLine)
self.addSubview(shadeSlider)
self.addSubview(hexLabel)
self.addSubview(handleView)
self.addSubview(addButton)
}
override func willMoveToSuperview(newSuperview: UIView?) {
/* Get the starting color */
currentColor = colorOnWheelFromAngle(currentAngle)
handleView.center = positionOnWheelFromAngle(currentAngle) //update pos for angle
self.layoutHandleLine() //layout the lines positioning
handleView.color = currentColor
addButton.color = currentColor
shadeSlider.primaryColor = currentColor
self.updateHexLabel() //update for hex value
}
//MARK: - Handle Touches
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?){
//Overriden to prevent uicontrolevents being called from the super
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?){
let touchPoint = touches.first!.locationInView(self)
if CGRectContainsPoint(handleView.frame, touchPoint) {
self.sendActionsForControlEvents(.TouchDown)
/* Enlarge Animation */
UIView.animateWithDuration(0.15, delay: 0, options: .CurveEaseIn, animations: { () -> Void in
self.handleView.transform = CGAffineTransformMakeScale(1.45, 1.45)
}, completion: nil)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
//Run this animation after a pan or here if touches are released
if handleView.transform.d > 1 { //if scale is larger than 1 (already animated)
self.executeHandleShrinkAnimation()
}
}
func handleWasMoved(recognizer: UIPanGestureRecognizer) {
switch(recognizer.state){
case UIGestureRecognizerState.Changed:
let touchPosition = recognizer.locationInView(self)
self.moveHandleTowardPoint(touchPosition)
self.sendActionsForControlEvents(.TouchDragInside)
break
case UIGestureRecognizerState.Ended:
/* Shrink Animation */
self.executeHandleShrinkAnimation()
break
default:
break
}
}
private func executeHandleShrinkAnimation(){
self.sendActionsForControlEvents(.TouchUpInside)
UIView.animateWithDuration(0.15, delay: 0, options: .CurveEaseOut, animations: { () -> Void in
self.handleView.transform = CGAffineTransformMakeScale(1, 1)
}, completion: nil)
}
private func moveHandleTowardPoint(point: CGPoint){
currentAngle = angleToCenterFromPoint(point) //Find the angle of point to the frames center
//Layout Handle
self.layoutHandle()
//Layout Line
self.layoutHandleLine()
//Update color for shade slider
shadeSlider.primaryColor = handleView.color//currentColor
//Update color for add button if a shade isnt selected
if shadeSlider.currentValue == 0 {
self.updateCurrentColor(shadeSlider.currentColor)
}
//Update Text Field display value
self.updateHexLabel()
}
func addButtonPressed(sender: JCColorAddButton){
//Do a 'bob' animation
UIView.animateWithDuration(0.2,
delay: 0,
options: .CurveEaseIn,
animations: { () -> Void in
sender.transform = CGAffineTransformMakeScale(1.1, 1.1)
}, completion: { (done) -> Void in
UIView.animateWithDuration(0.1, animations: { () -> Void in
sender.transform = CGAffineTransformMakeScale(1, 1)
})
})
delegate?.colorPickerDidChooseColor(self, color: sender.color) //Delegate call
}
//MARK: - Drawing
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let ctx = UIGraphicsGetCurrentContext()
drawRainbowCircle(in: ctx, outerRadius: radius - padding, innerRadius: radius - stroke - padding, resolution: 1)
}
/*
Resolution should be between 0.1 and 1
*/
func drawRainbowCircle(in context: CGContextRef?, outerRadius: CGFloat, innerRadius: CGFloat, resolution: Float){
CGContextSaveGState(context)
CGContextTranslateCTM(context, CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)) //Move context to center
let subdivisions:CGFloat = CGFloat(resolution * 512) //Max subdivisions of 512
let innerHeight = (CGFloat(M_PI)*innerRadius)/subdivisions //height of the inner wall for each segment
let outterHeight = (CGFloat(M_PI)*outerRadius)/subdivisions
let segment = UIBezierPath()
segment.moveToPoint(CGPointMake(innerRadius, -innerHeight/2))
segment.addLineToPoint(CGPointMake(innerRadius, innerHeight/2))
segment.addLineToPoint(CGPointMake(outerRadius, outterHeight/2))
segment.addLineToPoint(CGPointMake(outerRadius, -outterHeight/2))
segment.closePath()
//Draw each segment and rotate around the center
for i in 0 ..< Int(ceil(subdivisions)) {
UIColor(hue: CGFloat(i)/subdivisions, saturation: 1, brightness: 1, alpha: 1).set()
segment.fill()
let lineTailSpace = CGFloat(M_PI*2)*outerRadius/subdivisions //The amount of space between the tails of each segment
segment.lineWidth = lineTailSpace //allows for seemless scaling
segment.stroke()
//Rotate to correct location
let rotate = CGAffineTransformMakeRotation(-(CGFloat(M_PI*2)/subdivisions)) //rotates each segment
segment.applyTransform(rotate)
}
CGContextTranslateCTM(context, -CGRectGetMidX(self.bounds), -CGRectGetMidY(self.bounds)) //Move context back to original position
CGContextRestoreGState(context)
}
//MARK: - Layout Updates
/* re-layout view and all its subview and drawings */
func layout() {
self.setNeedsDisplay() //mark view as dirty
let minDimension = min(self.bounds.size.width, self.bounds.size.height)
radius = minDimension/2 - handleSize.width/2 //create radius for new size
self.layoutAddButton()
//Update handle's size
handleView.frame = CGRect(origin: .zero, size: handleSize)
self.layoutHandle()
//Ensure colors are updated
self.updateCurrentColor(handleView.color)
shadeSlider.primaryColor = handleView.color
self.layoutShadeSlider()
self.layoutHandleLine()
self.layoutHexLabel()
}
func layoutAddButton(){
let addButtonSize = CGSize(width: self.bounds.width/5, height: self.bounds.height/5)
addButton.frame = CGRect(x: CGRectGetMidX(self.bounds) - addButtonSize.width/2, y: CGRectGetMidY(self.bounds) - addButtonSize.height/2, width: addButtonSize.width, height: addButtonSize.height)
}
/*
Update the handleView's position and color for the currentAngle
*/
func layoutHandle(){
let angle = currentAngle //Preserve value in case it changes
let newPosition = positionOnWheelFromAngle(angle) //find the correct position on the color wheel
//Update handle position
handleView.center = newPosition
//Update color for the movement
handleView.color = colorOnWheelFromAngle(angle)
}
/*
Updates the line view's position for the current angle
Pre: dependant on addButtons position
*/
func layoutHandleLine(){
let linePath = UIBezierPath()
linePath.moveToPoint(addButton.center)
linePath.addLineToPoint(positionOnWheelFromAngle(currentAngle))
handleLine.path = linePath.CGPath
}
/*
Pre: dependant on addButtons position
*/
func layoutHexLabel(){
hexLabel.frame = CGRect(x: 0, y: 0, width: addButton.bounds.width*1.5, height: addButton.bounds.height/3)
hexLabel.center = CGPointMake(CGRectGetMidX(self.bounds), (addButton.frame.origin.y + (padding + handleView.frame.height/2 + stroke/2))/1.75) //Divided by 1.75 not 2 to make it a bit lower
hexLabel.font = UIFont(name: "Menlo-Regular", size: hexLabel.bounds.height)
}
/*
Pre: dependant on radius
*/
func layoutShadeSlider(){
/* Calculate proper length for slider */
let centerPoint = CGPoint(x: CGRectGetMidX(bounds), y: CGRectGetMidY(bounds))
let insideRadius = radius - padding
let pointLeft = CGPoint(x: centerPoint.x + insideRadius*CGFloat(cos(7*M_PI/6)), y: centerPoint.y - insideRadius*CGFloat(sin(7*M_PI/6)))
let pointRight = CGPoint(x: centerPoint.x + insideRadius*CGFloat(cos(11*M_PI/6)), y: centerPoint.y - insideRadius*CGFloat(sin(11*M_PI/6)))
let deltaX = pointRight.x - pointLeft.x //distance on circle between points at 7pi/6 and 11pi/6
let sliderSize = CGSize(width: deltaX * 0.75, height: 0.08 * (bounds.height - padding*2))//bounds.height
shadeSlider.frame = CGRect(x: bounds.midX - sliderSize.width/2, y: pointLeft.y - sliderSize.height/2, width: sliderSize.width, height: sliderSize.height)
shadeSlider.handleCenterX = shadeSlider.bounds.width/2 //set handle starting position
shadeSlider.layoutLayerFrames() //call sliders' layout function
}
func updateHexLabel(){
hexLabel.text = "#" + currentColor.hexCode
}
func updateCurrentColor(color: UIColor){
currentColor = color
addButton.color = color
self.sendActionsForControlEvents(.ValueChanged)
}
//MARK: - Helper Methods
private func angleToCenterFromPoint(point: CGPoint) -> Float {
let deltaX = Float(CGRectGetMidX(self.bounds) - point.x)
let deltaY = Float(CGRectGetMidY(self.bounds) - point.y)
let angle = atan2f(deltaX, deltaY)
// Convert the angle to be between 0 and 2PI
var adjustedAngle = angle + Float(M_PI/2)
if (adjustedAngle < 0){ //Left side (Q2 and Q3)
adjustedAngle += Float(M_PI*2)
}
return adjustedAngle
}
/* Find the angle relative to the center of the frame and uses the angle to find what color lies there */
private func colorOnWheelFromAngle(angle: Float) -> UIColor {
return UIColor(hue: CGFloat(Double(angle)/(2*M_PI)), saturation: 1, brightness: 1, alpha: 1)
}
/* Returns a position centered on the wheel for a given angle */
private func positionOnWheelFromAngle(angle: Float) -> CGPoint{
let buffer = padding + stroke/2
return CGPoint(x: CGRectGetMidX(self.bounds) + ((radius - buffer) * CGFloat(cos(-angle))), y: CGRectGetMidY(self.bounds) + ((radius - buffer) * CGFloat(sin(-angle))))
}
}
extension JCColorPicker: JCShadeSliderDelegate{
func shadeSliderChoseColor(slider: JCShadeSlider, color: UIColor) {
self.updateCurrentColor(color) //update main controller for selected color
self.updateHexLabel()
}
}

View File

@ -0,0 +1,184 @@
//
// JCShadeSlider.swift
//
// Created by Jonathan Cardasis on 7/7/16.
// Copyright © 2016 Jonathan Cardasis. All rights reserved.
//
import UIKit
class JCSliderTrackLayer: CALayer{
let gradient = CAGradientLayer()
override init() {
super.init()
gradient.actions = ["position" : NSNull(), "bounds" : NSNull(), "path" : NSNull()]
self.addSublayer(gradient)
}
override init(layer: AnyObject) {
super.init(layer: layer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
protocol JCShadeSliderDelegate {
func shadeSliderChoseColor(slider: JCShadeSlider, color: UIColor)
}
class JCShadeSlider: UIControl {
var currentValue: CGFloat = 0.0 //range of {-1,1}
let trackLayer = JCSliderTrackLayer()
let handleView = JCColorHandle()
var handleWidth: CGFloat{ return self.bounds.height }
var handleCenterX: CGFloat = 0.0
var delegate: JCShadeSliderDelegate?
var primaryColor = UIColor.grayColor(){
didSet{
self.changeColorHue(to: currentColor)
self.updateGradientTrack(for: primaryColor)
}
}
/* The computed color of the primary color with shading based on the currentValue */
var currentColor: UIColor{
get{
if currentValue < 0 {//darken
return primaryColor.darkerColor(-currentValue)
}
else{ //lighten
return primaryColor.lighterColor(currentValue)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit(){
self.backgroundColor = nil
handleCenterX = self.bounds.width/2
trackLayer.backgroundColor = UIColor.blueColor().CGColor
trackLayer.masksToBounds = true
trackLayer.actions = ["position" : NSNull(), "bounds" : NSNull(), "path" : NSNull()] //disable implicit animations
self.layer.addSublayer(trackLayer)
handleView.color = UIColor.blueColor()
handleView.circleLayer.borderWidth = 3
handleView.userInteractionEnabled = false //disable interaction for touch events
self.layer.addSublayer(handleView.layer)
let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(resetHandleToCenter))
doubleTapGesture.numberOfTapsRequired = 2
self.addGestureRecognizer(doubleTapGesture)
self.layoutLayerFrames()
self.changeColorHue(to: currentColor)
self.updateGradientTrack(for: primaryColor)
}
override func didMoveToSuperview() {
self.updateGradientTrack(for: primaryColor)
}
func layoutLayerFrames(){
trackLayer.frame = self.bounds.insetBy(dx: handleWidth/2, dy: self.bounds.height/4) //Make half the height of the bounds
trackLayer.cornerRadius = trackLayer.bounds.height/2
self.updateGradientTrack(for: primaryColor)
handleCenterX = (currentValue+1)/2 * (bounds.width - handleView.bounds.width) + handleView.bounds.width/2 //Update where the handles center should be
self.layoutHandleFrame()
}
//Lays out handle according to the currentValue on slider
func layoutHandleFrame(){
handleView.frame = CGRect(x: handleCenterX - handleWidth/2, y: self.bounds.height/2 - handleWidth/2, width: handleWidth, height: handleWidth)
}
func changeColorHue(to newColor: UIColor){
handleView.color = newColor
if currentValue != 0 { //Don't call delegate if the color hasnt changed
self.delegate?.shadeSliderChoseColor(self, color: newColor)
}
}
func updateGradientTrack(for color: UIColor){
trackLayer.gradient.frame = trackLayer.bounds
trackLayer.gradient.startPoint = CGPoint(x: 0, y: 0.5)
trackLayer.gradient.endPoint = CGPoint(x: 1, y: 0.5)
//Gradient is for astetics - the slider is actually between black and white
trackLayer.gradient.colors = [color.darkerColor(0.65).CGColor, color.CGColor, color.lighterColor(0.65).CGColor]
}
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
let location = touch.locationInView(self)
if handleView.frame.contains(location) {
return true
}
return false
}
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
let location = touch.locationInView(self)
//Update for center point
handleCenterX = location.x
handleCenterX = fittedValueInBounds(handleCenterX) //adjust value to fit in bounds if needed
//Update current value
currentValue = ((handleCenterX - handleWidth/2)/trackLayer.bounds.width - 0.5) * 2 //find current value between {-1,1} of the slider
//Update handle color
self.changeColorHue(to: currentColor)
//Update layers frames
CATransaction.begin()
CATransaction.setDisableActions(true)
self.layoutHandleFrame()
CATransaction.commit()
self.sendActionsForControlEvents(.ValueChanged)
return true
}
func resetHandleToCenter(recognizer: UITapGestureRecognizer){
let location = recognizer.locationInView(self)
guard handleView.frame.contains(location) else {
return
}
//tap is on handle
handleCenterX = self.bounds.width/2
self.layoutHandleFrame()
handleView.color = primaryColor
currentValue = 0.0
self.sendActionsForControlEvents(.ValueChanged)
self.delegate?.shadeSliderChoseColor(self, color: currentColor)
}
/* Helper Methods */
//Returns a CGFloat for the highest/lowest possble value such that it is inside the views bounds
private func fittedValueInBounds(value: CGFloat) -> CGFloat {
return min(max(value, trackLayer.frame.minX), trackLayer.frame.maxX)
}
}

View File

@ -0,0 +1,59 @@
//
// UIColor+Utilities.swift
// JCUIElements
//
// Created by Jonathan Cardasis on 8/17/15.
// Copyright (c) 2015 Jonathan Cardasis. All rights reserved.
//
import UIKit
extension UIColor{
var hexCode: String {
get{
let colorComponents = CGColorGetComponents(self.CGColor)
return String(format: "%02x%02x%02x", Int(colorComponents[0]*255.0), Int(colorComponents[1]*255.0),Int(colorComponents[2]*255.0)).uppercaseString
}
}
//Amount should be between 0 and 1
func lighterColor(amount: CGFloat) -> UIColor{
return UIColor.blendColors(color: self, destinationColor: UIColor.whiteColor(), amount: amount)
}
func darkerColor(amount: CGFloat) -> UIColor{
return UIColor.blendColors(color: self, destinationColor: UIColor.blackColor(), amount: amount)
}
static func blendColors(color color: UIColor, destinationColor: UIColor, amount : CGFloat) -> UIColor{
var amountToBlend = amount;
if amountToBlend > 1{
amountToBlend = 1.0
}
else if amountToBlend < 0{
amountToBlend = 0
}
var r,g,b, alpha : CGFloat
r = 0
g = 0
b = 0
alpha = 0
color.getRed(&r, green: &g, blue: &b, alpha: &alpha) //gets the rgba values (0-1)
//Get the destination rgba values
var dest_r, dest_g, dest_b, dest_alpha : CGFloat
dest_r = 0
dest_g = 0
dest_b = 0
dest_alpha = 0
destinationColor.getRed(&dest_r, green: &dest_g, blue: &dest_b, alpha: &dest_alpha)
r = amountToBlend * (dest_r * 255) + (1 - amountToBlend) * (r * 255)
g = amountToBlend * (dest_g * 255) + (1 - amountToBlend) * (g * 255)
b = amountToBlend * (dest_b * 255) + (1 - amountToBlend) * (b * 255)
alpha = fabs(alpha / dest_alpha)
return UIColor(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: alpha)
}
}