Refactor color handle layer positioning, pulling up layout logic to parent. Picker now uses touch delegate events instead of a gesture for faster recognition. Additional handle positioning improvements.

This commit is contained in:
Jonathan Cardasis 2019-04-19 18:26:24 -04:00
parent c701c6d109
commit 5de59e1092
3 changed files with 103 additions and 113 deletions

View File

@ -39,7 +39,14 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
} }
} }
//public var handleSize: CGSize { /* TODO */ } /// The size handles should be displayed at.
public var handleSize: CGSize = defaultHandleSize {
didSet { setNeedsLayout() }
}
/// An extension to handles' hitboxes in the +Y direction.
/// Allows for handles to be grabbed more easily.
public var handleHitboxExtensionY: CGFloat = 10.0
/// Handles added to the color picker. /// Handles added to the color picker.
private(set) public var handles: [ChromaColorHandle] = [] private(set) public var handles: [ChromaColorHandle] = []
@ -65,9 +72,9 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
updateBorderIfNeeded() updateBorderIfNeeded()
handles.forEach { handle in handles.forEach { handle in
//let location = colorWheelView.location(of: handle.color) let location = colorWheelView.location(of: handle.color)
handle.frame.size = defaultHandleSize handle.frame.size = defaultHandleSize
//handle.center = location positionHandle(handle, forColorLocation: location)
} }
} }
@ -75,16 +82,13 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
@discardableResult @discardableResult
public func addHandle(at color: UIColor? = nil) -> ChromaColorHandle { public func addHandle(at color: UIColor? = nil) -> ChromaColorHandle {
let handleColor = color ?? defaultHandleColorPosition
let handle = ChromaColorHandle() let handle = ChromaColorHandle()
handle.color = handleColor handle.color = color ?? defaultHandleColorPosition
addHandle(handle) addHandle(handle)
return handle return handle
} }
public func addHandle(_ handle: ChromaColorHandle) { public func addHandle(_ handle: ChromaColorHandle) {
addPanGesture(to: handle)
handles.append(handle) handles.append(handle)
colorWheelView.addSubview(handle) colorWheelView.addSubview(handle)
} }
@ -101,49 +105,66 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
self.backgroundColor = UIColor.clear self.backgroundColor = UIColor.clear
self.layer.masksToBounds = false self.layer.masksToBounds = false
setupColorWheelView() setupColorWheelView()
//setupGestures()
} }
// MARK: - Control // MARK: - Control
// public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
// let location = touch.location(in: self) let location = touch.location(in: self)
//
// for handle in handles { for handle in handles {
// if handle.frame.contains(location) { if extendedHitFrame(for: handle).contains(location) {
// print("tapped on handle") currentHandle = handle
// currentHandle = handle colorWheelView.bringSubviewToFront(handle)
// return true animateHandleScale(handle, shouldGrow: true)
// } return true
// } }
// return false }
// } return false
// }
// public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
// let location = touch.location(in: colorWheelView) public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
// guard let handle = currentHandle else { return false } var location = touch.location(in: colorWheelView)
// guard let handle = currentHandle else { return false }
// handle.center = location
// if let selectedColor = colorWheelView.pixelColor(at: location) { if !colorWheelView.pointIsInColorWheel(location) {
// print(location) // Touch is outside color wheel and should map to outermost edge.
// handle.color = selectedColor let center = colorWheelView.center
// let radius = colorWheelView.radius
// delegate?.colorPickerDidChooseColor(self, color: selectedColor) let angleToCenter = atan2(location.x - center.x, location.y - center.y)
// } let positionOnColorWheelEdge = CGPoint(x: center.x + radius * sin(angleToCenter),
// y: center.y + radius * cos(angleToCenter))
// sendActions(for: .valueChanged) print("pos: \(positionOnColorWheelEdge)")
// return true location = positionOnColorWheelEdge
// } }
//
// public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { if let pixelColor = colorWheelView.pixelColor(at: location) {
// sendActions(for: .editingDidEnd) let previousBrightness = handle.color.brightness
// } handle.color = pixelColor.withBrightness(previousBrightness)
positionHandle(handle, forColorLocation: location)
if let slider = brightnessSlider {
slider.trackColor = pixelColor
slider.currentValue = slider.value(brightness: previousBrightness)
}
informDelegateOfColorChange(on: handle)
sendActions(for: .valueChanged)
}
return true
}
public override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
if let handle = currentHandle {
animateHandleScale(handle, shouldGrow: false)
}
sendActions(for: .editingDidEnd)
}
internal func addPanGesture(to handle: ChromaColorHandle) { public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleWasMoved(_:))) // Self should handle all touch events, forwarding if needed.
panGesture.maximumNumberOfTouches = 1 return self
panGesture.minimumNumberOfTouches = 1
handle.addGestureRecognizer(panGesture)
} }
// MARK: Setup & Layout // MARK: Setup & Layout
@ -172,62 +193,8 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
colorWheelView.layer.borderWidth = borderWidth colorWheelView.layer.borderWidth = borderWidth
} }
internal func setupGestures() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(colorWheelTapped(_:)))
colorWheelView.isUserInteractionEnabled = true
colorWheelView.addGestureRecognizer(tapGesture)
}
// MARK: Actions // 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 @objc
internal func brightnessSliderDidValueChange(_ slider: ChromaBrightnessSlider) { internal func brightnessSliderDidValueChange(_ slider: ChromaBrightnessSlider) {
guard let currentHandle = currentHandle else { return } guard let currentHandle = currentHandle else { return }
@ -239,7 +206,29 @@ public class ChromaColorPicker: UIControl, ChromaControlStylable {
internal func informDelegateOfColorChange(on handle: ChromaColorHandle) { internal func informDelegateOfColorChange(on handle: ChromaColorHandle) {
delegate?.colorPickerDidChooseColor(self, color: handle.color) delegate?.colorPickerDidChooseColor(self, color: handle.color)
} }
// MARK: - Helpers
internal func extendedHitFrame(for handle: ChromaColorHandle) -> CGRect {
var frame = handle.frame
frame.size.height += handleHitboxExtensionY
return frame
}
internal func positionHandle(_ handle: ChromaColorHandle, forColorLocation location: CGPoint) {
handle.center = location.applying(CGAffineTransform.identity.translatedBy(x: 0, y: -handle.bounds.height / 2))
}
internal func animateHandleScale(_ handle: ChromaColorHandle, shouldGrow: Bool) {
if shouldGrow && handle.transform.d > 1 { return } // Already grown
let transform = shouldGrow ? CGAffineTransform(scaleX: 1.25, y: 1.25) : CGAffineTransform(scaleX: 1, y: 1)
UIView.animate(withDuration: 0.15, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.6, options: .curveEaseInOut, animations: {
handle.transform = transform
}, completion: nil)
}
} }
internal let defaultHandleColorPosition: UIColor = .white internal let defaultHandleColorPosition: UIColor = .white
internal let defaultHandleSize: CGSize = CGSize(width: 42, height: 42) internal let defaultHandleSize: CGSize = CGSize(width: 34, height: 42)

View File

@ -37,6 +37,7 @@ public class ColorWheelView: UIView {
/** /**
Returns the (x,y) location of the color provided within the ColorWheelView. Returns the (x,y) location of the color provided within the ColorWheelView.
Disregards color's brightness component.
*/ */
public func location(of color: UIColor) -> CGPoint { public func location(of color: UIColor) -> CGPoint {
var hue: CGFloat = 0 var hue: CGFloat = 0
@ -86,6 +87,17 @@ public class ColorWheelView: UIView {
return color return color
} }
/**
Returns whether or not the point is in the circular area of the color wheel.
*/
public func pointIsInColorWheel(_ point: CGPoint) -> Bool {
guard bounds.offsetBy(dx: 1, dy: 1).contains(point) else { return false }
let distanceFromCenter: CGFloat = hypot(center.x - point.x, center.y - point.y)
let pointExistsInRadius: Bool = distanceFromCenter <= (radius - layer.borderWidth)
return pointExistsInRadius
}
// MARK: - Private // MARK: - Private
internal let imageView = UIImageView() internal let imageView = UIImageView()
@ -123,12 +135,4 @@ public class ColorWheelView: UIView {
]) ])
return filter?.outputImage return filter?.outputImage
} }
internal func pointIsInColorWheel(_ point: CGPoint) -> Bool {
guard bounds.offsetBy(dx: 1, dy: 1).contains(point) else { return false }
let distanceFromCenter: CGFloat = hypot(center.x - point.x, center.y - point.y)
let pointExistsInRadius: Bool = distanceFromCenter <= (radius - layer.borderWidth)
return pointExistsInRadius
}
} }

View File

@ -62,7 +62,6 @@ public class ChromaColorHandle: UIView, ChromaControlStylable {
internal let handleShape = CAShapeLayer() internal let handleShape = CAShapeLayer()
internal func commonInit() { internal func commonInit() {
backgroundColor = UIColor.red.withAlphaComponent(0.25) //DEBUG
layer.addSublayer(handleShape) layer.addSublayer(handleShape)
} }
@ -87,14 +86,12 @@ public class ChromaColorHandle: UIView, ChromaControlStylable {
} }
internal func layoutHandleShape() { internal func layoutHandleShape() {
let size = CGSize(width: bounds.height * handleWidthHeightRatio, height: bounds.height) let size = CGSize(width: bounds.width - borderWidth, height: bounds.height - borderWidth)
handleShape.path = makeHandlePath(frame: CGRect(origin: .zero, size: size)) 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.frame = CGRect(origin: CGPoint(x: bounds.midX - (size.width / 2), y: bounds.midY - (size.height / 2)), size: size)
handleShape.fillColor = color.cgColor handleShape.fillColor = color.cgColor
handleShape.strokeColor = borderColor.cgColor handleShape.strokeColor = borderColor.cgColor
handleShape.lineWidth = borderWidth handleShape.lineWidth = borderWidth
} }
} }
internal let handleWidthHeightRatio: CGFloat = 52.0 / 65