mirror of
https://github.com/joncardasis/ChromaColorPicker.git
synced 2024-11-25 11:42:12 +03:00
Handle view is now drawn with inverted teardrop. Fixed color wheel issue obtaining incorrect colors of via subviews. ChromaColorPicker pan gesture implemented for handles.
This commit is contained in:
parent
ac92bef8c6
commit
c701c6d109
@ -22,6 +22,7 @@
|
||||
FC4387C922625C7000F739F1 /* SliderTrackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4387C822625C7000F739F1 /* SliderTrackView.swift */; };
|
||||
FC4387CB22625DA800F739F1 /* SliderHandleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4387CA22625DA800F739F1 /* SliderHandleView.swift */; };
|
||||
FC4387CD2262B82600F739F1 /* ChromaControlStylable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4387CC2262B82600F739F1 /* ChromaControlStylable.swift */; };
|
||||
FC4387DE226974FD00F739F1 /* UIColor+Brightness.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4387DC226972EB00F739F1 /* UIColor+Brightness.swift */; };
|
||||
FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */; };
|
||||
FCCA42A7226023F000BE2FF9 /* ChromaColorHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */; };
|
||||
FCCA42AA2260329900BE2FF9 /* UIKit+DropShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCA42A92260329900BE2FF9 /* UIKit+DropShadow.swift */; };
|
||||
@ -83,6 +84,7 @@
|
||||
FC4387C822625C7000F739F1 /* SliderTrackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderTrackView.swift; sourceTree = "<group>"; };
|
||||
FC4387CA22625DA800F739F1 /* SliderHandleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderHandleView.swift; sourceTree = "<group>"; };
|
||||
FC4387CC2262B82600F739F1 /* ChromaControlStylable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaControlStylable.swift; sourceTree = "<group>"; };
|
||||
FC4387DC226972EB00F739F1 /* UIColor+Brightness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Brightness.swift"; sourceTree = "<group>"; };
|
||||
FCCA42A4226022A800BE2FF9 /* ColorWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorWheelView.swift; sourceTree = "<group>"; };
|
||||
FCCA42A6226023F000BE2FF9 /* ChromaColorHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromaColorHandle.swift; sourceTree = "<group>"; };
|
||||
FCCA42A92260329900BE2FF9 /* UIKit+DropShadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIKit+DropShadow.swift"; sourceTree = "<group>"; };
|
||||
@ -204,6 +206,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FCCA42A92260329900BE2FF9 /* UIKit+DropShadow.swift */,
|
||||
FC4387DC226972EB00F739F1 /* UIColor+Brightness.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -366,6 +369,7 @@
|
||||
FCEA4E272235AAA200C0A1B6 /* ChromaColorPicker.swift in Sources */,
|
||||
FC4387CD2262B82600F739F1 /* ChromaControlStylable.swift in Sources */,
|
||||
FCCA42AA2260329900BE2FF9 /* UIKit+DropShadow.swift in Sources */,
|
||||
FC4387DE226974FD00F739F1 /* UIColor+Brightness.swift in Sources */,
|
||||
FC4387C62262556600F739F1 /* ChromaBrightnessSlider.swift in Sources */,
|
||||
FCCA42A7226023F000BE2FF9 /* ChromaColorHandle.swift in Sources */,
|
||||
FCCA42A5226022A800BE2FF9 /* ColorWheelView.swift in Sources */,
|
||||
|
@ -19,13 +19,11 @@ class ViewController: UIViewController {
|
||||
super.viewDidLoad()
|
||||
setupColorPicker()
|
||||
setupBrightnessSlider()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
print()
|
||||
setupColorPickerHandles()
|
||||
}
|
||||
|
||||
private func setupColorPicker() {
|
||||
colorPicker.delegate = self
|
||||
colorPicker.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(colorPicker)
|
||||
|
||||
@ -57,7 +55,20 @@ class ViewController: UIViewController {
|
||||
brightnessSlider.heightAnchor.constraint(equalTo: brightnessSlider.widthAnchor, multiplier: brightnessSliderWidthHeightRatio)
|
||||
])
|
||||
}
|
||||
|
||||
private func setupColorPickerHandles() {
|
||||
colorPicker.addHandle(at: .blue)
|
||||
colorPicker.borderWidth = 0
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: ChromaColorPickerDelegate {
|
||||
|
||||
func colorPickerDidChooseColor(_ colorPicker: ChromaColorPicker, color: UIColor) {
|
||||
colorDisplayView.backgroundColor = color
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let defaultColorPickerSize = CGSize(width: 320, height: 320)
|
||||
private let brightnessSliderWidthHeightRatio: CGFloat = 0.1
|
||||
|
@ -69,6 +69,12 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable {
|
||||
colorPicker.connect(self)
|
||||
}
|
||||
|
||||
/// Returns the relative value on the slider [0.0, 1.0] for the given color brightness ([0.0, 1.0]).
|
||||
public func value(brightness: CGFloat) -> CGFloat {
|
||||
let clamedBrightness = max(0, min(brightness, 1.0))
|
||||
return 1.0 - clamedBrightness
|
||||
}
|
||||
|
||||
// MARK: - Control
|
||||
|
||||
public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
@ -178,7 +184,10 @@ public class ChromaBrightnessSlider: UIControl, ChromaControlStylable {
|
||||
}
|
||||
|
||||
internal func updateTrackViewGradient(for color: UIColor) {
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
sliderTrackView.gradientValues = (color, .black)
|
||||
CATransaction.commit()
|
||||
}
|
||||
|
||||
internal func moveHandle(to value: CGFloat) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import Accelerate
|
||||
|
||||
public protocol ChromaColorPickerDelegate {
|
||||
public protocol ChromaColorPickerDelegate: class {
|
||||
/// When the control has changed
|
||||
func colorPickerDidChooseColor(_ colorPicker: ChromaColorPicker, color: UIColor)
|
||||
}
|
||||
@ -18,6 +18,8 @@ public protocol ChromaColorPickerDelegate {
|
||||
@IBDesignable
|
||||
public class ChromaColorPicker: UIControl, ChromaControlStylable {
|
||||
|
||||
public weak var delegate: ChromaColorPickerDelegate?
|
||||
|
||||
@IBInspectable public var borderWidth: CGFloat = 8.0 {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
@ -37,6 +39,14 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
|
||||
}
|
||||
}
|
||||
|
||||
//public var handleSize: CGSize { /* TODO */ }
|
||||
|
||||
/// Handles added to the color picker.
|
||||
private(set) public var handles: [ChromaColorHandle] = []
|
||||
|
||||
/// The last active handle.
|
||||
private(set) public var currentHandle: ChromaColorHandle?
|
||||
|
||||
//MARK: - Initialization
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
@ -53,12 +63,30 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
|
||||
super.layoutSubviews()
|
||||
updateShadowIfNeeded()
|
||||
updateBorderIfNeeded()
|
||||
|
||||
handles.forEach { handle in
|
||||
//let location = colorWheelView.location(of: handle.color)
|
||||
handle.frame.size = defaultHandleSize
|
||||
//handle.center = location
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@discardableResult
|
||||
public func addHandle(at color: UIColor? = nil) -> ChromaColorHandle {
|
||||
return ChromaColorHandle(color: color ?? defaultHandleColorPosition)
|
||||
let handleColor = color ?? defaultHandleColorPosition
|
||||
let handle = ChromaColorHandle()
|
||||
handle.color = handleColor
|
||||
|
||||
addHandle(handle)
|
||||
return handle
|
||||
}
|
||||
|
||||
public func addHandle(_ handle: ChromaColorHandle) {
|
||||
addPanGesture(to: handle)
|
||||
handles.append(handle)
|
||||
colorWheelView.addSubview(handle)
|
||||
}
|
||||
|
||||
public func connect(_ slider: ChromaBrightnessSlider) {
|
||||
@ -73,9 +101,51 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
|
||||
self.backgroundColor = UIColor.clear
|
||||
self.layer.masksToBounds = false
|
||||
setupColorWheelView()
|
||||
setupGestures()
|
||||
//setupGestures()
|
||||
}
|
||||
|
||||
// MARK: - Control
|
||||
|
||||
// public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
// let location = touch.location(in: self)
|
||||
//
|
||||
// for handle in handles {
|
||||
// if handle.frame.contains(location) {
|
||||
// print("tapped on handle")
|
||||
// currentHandle = handle
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
// public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
// let location = touch.location(in: colorWheelView)
|
||||
// guard let handle = currentHandle else { return false }
|
||||
//
|
||||
// handle.center = location
|
||||
// if let selectedColor = colorWheelView.pixelColor(at: location) {
|
||||
// print(location)
|
||||
// handle.color = selectedColor
|
||||
//
|
||||
// delegate?.colorPickerDidChooseColor(self, color: selectedColor)
|
||||
// }
|
||||
//
|
||||
// sendActions(for: .valueChanged)
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// public override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
// sendActions(for: .editingDidEnd)
|
||||
// }
|
||||
|
||||
internal func addPanGesture(to handle: ChromaColorHandle) {
|
||||
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleWasMoved(_:)))
|
||||
panGesture.maximumNumberOfTouches = 1
|
||||
panGesture.minimumNumberOfTouches = 1
|
||||
handle.addGestureRecognizer(panGesture)
|
||||
}
|
||||
|
||||
// MARK: Setup & Layout
|
||||
|
||||
internal func setupColorWheelView() {
|
||||
@ -110,17 +180,66 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@objc
|
||||
internal func handleWasMoved(_ gesture: UIPanGestureRecognizer) {
|
||||
if let touchedView = gesture.view {
|
||||
colorWheelView.bringSubviewToFront(touchedView)
|
||||
}
|
||||
|
||||
switch (gesture.state) {
|
||||
case .began:
|
||||
currentHandle = gesture.view as? ChromaColorHandle
|
||||
case .changed:
|
||||
let location = gesture.location(in: colorWheelView)
|
||||
|
||||
if let handle = currentHandle, let pixelColor = colorWheelView.pixelColor(at: location) {
|
||||
let previousBrightness = handle.color.brightness
|
||||
|
||||
print(pixelColor)
|
||||
|
||||
handle.color = pixelColor.withBrightness(previousBrightness)
|
||||
handle.center = location
|
||||
|
||||
if let slider = brightnessSlider {
|
||||
slider.trackColor = pixelColor
|
||||
slider.currentValue = slider.value(brightness: previousBrightness)
|
||||
}
|
||||
|
||||
informDelegateOfColorChange(on: handle)
|
||||
}
|
||||
|
||||
self.sendActions(for: .touchDragInside)
|
||||
case .ended:
|
||||
/* Shrink Animation */
|
||||
//self.executeHandleShrinkAnimation()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
internal func colorWheelTapped(_ gesture: UITapGestureRecognizer) {
|
||||
let location = gesture.location(in: colorWheelView)
|
||||
let pixelColor = colorWheelView.pixelColor(at: location)
|
||||
print(pixelColor)
|
||||
|
||||
print(location)
|
||||
delegate?.colorPickerDidChooseColor(self, color: pixelColor!)
|
||||
}
|
||||
|
||||
@objc
|
||||
internal func brightnessSliderDidValueChange(_ slider: ChromaBrightnessSlider) {
|
||||
guard let currentHandle = currentHandle else { return }
|
||||
|
||||
currentHandle.color = slider.currentColor
|
||||
informDelegateOfColorChange(on: currentHandle)
|
||||
}
|
||||
|
||||
internal func informDelegateOfColorChange(on handle: ChromaColorHandle) {
|
||||
delegate?.colorPickerDidChooseColor(self, color: handle.color)
|
||||
}
|
||||
}
|
||||
|
||||
internal let defaultHandleColorPosition: UIColor = .black
|
||||
internal let defaultHandleColorPosition: UIColor = .white
|
||||
internal let defaultHandleSize: CGSize = CGSize(width: 42, height: 42)
|
||||
|
@ -74,12 +74,12 @@ public class ColorWheelView: UIView {
|
||||
}
|
||||
|
||||
context.translateBy(x: -point.x, y: -point.y)
|
||||
layer.render(in: context)
|
||||
imageView.layer.render(in: context)
|
||||
let color = UIColor(
|
||||
red: CGFloat(pixel[0]) / 255.0,
|
||||
green: CGFloat(pixel[1]) / 255.0,
|
||||
blue: CGFloat(pixel[2]) / 255.0,
|
||||
alpha: CGFloat(pixel[3]) / 255.0
|
||||
alpha: 1.0
|
||||
)
|
||||
|
||||
pixel.deallocate()
|
||||
|
30
Source/Extensions/UIColor+Brightness.swift
Normal file
30
Source/Extensions/UIColor+Brightness.swift
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// UIColor+Brightness.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Jon Cardasis on 4/18/19.
|
||||
// Copyright © 2019 Jonathan Cardasis. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
internal extension UIColor {
|
||||
|
||||
/// Returns a color with the specified brightness component.
|
||||
internal func withBrightness(_ value: CGFloat) -> UIColor {
|
||||
var hue: CGFloat = 0
|
||||
var saturation: CGFloat = 0
|
||||
var alpha: CGFloat = 0
|
||||
let brightness = max(0, min(value, 1))
|
||||
getHue(&hue, saturation: &saturation, brightness: nil, alpha: &alpha)
|
||||
|
||||
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
|
||||
}
|
||||
|
||||
/// The value of the brightness component.
|
||||
internal var brightness: CGFloat {
|
||||
var brightness: CGFloat = 0
|
||||
getHue(nil, saturation: nil, brightness: &brightness, alpha: nil)
|
||||
return brightness
|
||||
}
|
||||
}
|
@ -8,17 +8,93 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
public class ChromaColorHandle {
|
||||
public class ChromaColorHandle: UIView, ChromaControlStylable {
|
||||
|
||||
/// Current selected color of the handle.
|
||||
fileprivate(set) var color: UIColor
|
||||
public var color: UIColor = .black {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
/// An image to display above the handle.
|
||||
var popoverImage: UIImage?
|
||||
public var popoverImage: UIImage?
|
||||
|
||||
/// A view to display above the handle. Overrides any provided `popoverImage`.
|
||||
var popoverView: UIView?
|
||||
public var popoverView: UIView?
|
||||
|
||||
init(color: UIColor) {
|
||||
public var borderWidth: CGFloat = 3.0 {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var borderColor: UIColor = .white {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var showsShadow: Bool = true {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public convenience init(color: UIColor) {
|
||||
self.init(frame: .zero)
|
||||
self.color = color
|
||||
commonInit()
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
layoutHandleShape()
|
||||
|
||||
layer.masksToBounds = false
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
internal let handleShape = CAShapeLayer()
|
||||
|
||||
internal func commonInit() {
|
||||
backgroundColor = UIColor.red.withAlphaComponent(0.25) //DEBUG
|
||||
layer.addSublayer(handleShape)
|
||||
}
|
||||
|
||||
internal func updateShadowIfNeeded() {
|
||||
if showsShadow {
|
||||
let shadowProps = shadowProperties(forHeight: bounds.height)
|
||||
applyDropShadow(shadowProps)
|
||||
} else {
|
||||
removeDropShadow()
|
||||
}
|
||||
}
|
||||
|
||||
internal func makeHandlePath(frame: CGRect) -> CGPath {
|
||||
let path = UIBezierPath()
|
||||
path.move(to: CGPoint(x: frame.minX + 0.5 * frame.width, y: frame.minY + 1 * frame.height))
|
||||
path.addCurve(to: CGPoint(x: frame.minX + 1 * frame.width, y: frame.minY + 0.40310 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.83333 * frame.width, y: frame.minY + 0.80216 * frame.height), controlPoint2: CGPoint(x: frame.minX + 1 * frame.width, y: frame.minY + 0.60320 * frame.height))
|
||||
path.addCurve(to: CGPoint(x: frame.minX + 0.5 * frame.width, y: frame.minY * frame.height), controlPoint1: CGPoint(x: frame.minX + 1 * frame.width, y: frame.minY + 0.18047 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.77614 * frame.width, y: frame.minY * frame.height))
|
||||
path.addCurve(to: CGPoint(x: frame.minX * frame.width, y: frame.minY + 0.40310 * frame.height), controlPoint1: CGPoint(x: frame.minX + 0.22386 * frame.width, y: frame.minY * frame.height), controlPoint2: CGPoint(x: frame.minX * frame.width, y: frame.minY + 0.18047 * frame.height))
|
||||
path.addCurve(to: CGPoint(x: frame.minX + 0.50000 * frame.width, y: frame.minY + 1 * frame.height), controlPoint1: CGPoint(x: frame.minX * frame.width, y: frame.minY + 0.60837 * frame.height), controlPoint2: CGPoint(x: frame.minX + 0.16667 * frame.width, y: frame.minY + 0.80733 * frame.height))
|
||||
path.close()
|
||||
return path.cgPath
|
||||
}
|
||||
|
||||
internal func layoutHandleShape() {
|
||||
let size = CGSize(width: bounds.height * handleWidthHeightRatio, height: bounds.height)
|
||||
handleShape.path = makeHandlePath(frame: CGRect(origin: .zero, size: size))
|
||||
handleShape.frame = CGRect(origin: CGPoint(x: bounds.midX - (size.width / 2), y: bounds.midY - size.height), size: size)
|
||||
|
||||
handleShape.fillColor = color.cgColor
|
||||
handleShape.strokeColor = borderColor.cgColor
|
||||
handleShape.lineWidth = borderWidth
|
||||
}
|
||||
}
|
||||
|
||||
internal let handleWidthHeightRatio: CGFloat = 52.0 / 65
|
||||
|
Loading…
Reference in New Issue
Block a user