mirror of
https://github.com/joncardasis/ChromaColorPicker.git
synced 2024-11-29 11:15:55 +03:00
205 lines
6.6 KiB
Swift
205 lines
6.6 KiB
Swift
//
|
|
// ChromaBrightnessSlider.swift
|
|
// ChromaColorPicker
|
|
//
|
|
// Created by Jon Cardasis on 4/13/19.
|
|
// Copyright © 2019 Jonathan Cardasis. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
public class ChromaBrightnessSlider: UIControl, ChromaControlStylable {
|
|
|
|
/// The value of the slider between [0.0, 1.0].
|
|
public var currentValue: CGFloat = 0.0 {
|
|
didSet { updateControl(to: currentValue) }
|
|
}
|
|
|
|
/// The base color the slider on the track.
|
|
public var trackColor: UIColor = .white {
|
|
didSet { updateTrackColor(to: trackColor) }
|
|
}
|
|
|
|
/// The value of the color the handle is currently displaying.
|
|
public var currentColor: UIColor {
|
|
return handle.handleColor
|
|
}
|
|
|
|
/// The handle control of the slider.
|
|
public let handle = SliderHandleView()
|
|
|
|
public var borderWidth: CGFloat = 4.0 {
|
|
didSet { layoutNow() }
|
|
}
|
|
|
|
public var borderColor: UIColor = .white {
|
|
didSet { layoutNow() }
|
|
}
|
|
|
|
public var showsShadow: Bool = true {
|
|
didSet { layoutNow() }
|
|
}
|
|
|
|
//MARK: - Initialization
|
|
|
|
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()
|
|
sliderTrackView.layer.cornerRadius = sliderTrackView.bounds.height / 2.0
|
|
sliderTrackView.layer.borderColor = borderColor.cgColor
|
|
sliderTrackView.layer.borderWidth = borderWidth
|
|
|
|
moveHandle(to: currentValue)
|
|
updateShadowIfNeeded()
|
|
}
|
|
|
|
// MARK: - Public
|
|
|
|
/// Attaches control to the provided color picker.
|
|
public func connect(to colorPicker: ChromaColorPicker) {
|
|
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 {
|
|
let location = touch.location(in: self)
|
|
let shouldBeginTracking = interactableBounds.contains(location)
|
|
if shouldBeginTracking {
|
|
sendActions(for: .touchDown)
|
|
}
|
|
return shouldBeginTracking
|
|
}
|
|
|
|
public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
|
let location = touch.location(in: self)
|
|
let clampedPositionX: CGFloat = max(0, min(location.x, confiningTrackFrame.width))
|
|
let value = clampedPositionX / confiningTrackFrame.width
|
|
|
|
currentValue = value
|
|
sendActions(for: .valueChanged)
|
|
return true
|
|
}
|
|
|
|
public override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
|
sendActions(for: .touchUpInside)
|
|
}
|
|
|
|
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
if interactableBounds.contains(point) {
|
|
return true
|
|
}
|
|
return super.point(inside: point, with: event)
|
|
}
|
|
|
|
internal func updateShadowIfNeeded() {
|
|
let views = [handle, sliderTrackView]
|
|
|
|
if showsShadow {
|
|
let shadowProps = shadowProperties(forHeight: bounds.height)
|
|
views.forEach { $0.applyDropShadow(shadowProps) }
|
|
} else {
|
|
views.forEach { $0.removeDropShadow() }
|
|
}
|
|
}
|
|
|
|
// MARK: - Private
|
|
private let sliderTrackView = SliderTrackView()
|
|
|
|
/// The amount of padding caused by visual stylings
|
|
private var horizontalPadding: CGFloat {
|
|
return sliderTrackView.layer.cornerRadius / 2.0
|
|
}
|
|
|
|
private var confiningTrackFrame: CGRect {
|
|
return sliderTrackView.frame.insetBy(dx: horizontalPadding, dy: 0)
|
|
}
|
|
|
|
private var interactableBounds: CGRect {
|
|
let horizontalOffset = -(handle.bounds.width / 2) + horizontalPadding
|
|
return bounds.insetBy(dx: horizontalOffset, dy: 0)
|
|
}
|
|
|
|
private func commonInit() {
|
|
backgroundColor = .clear
|
|
setupSliderTrackView()
|
|
setupSliderHandleView()
|
|
updateTrackColor(to: trackColor)
|
|
}
|
|
|
|
private func setupSliderTrackView() {
|
|
sliderTrackView.isUserInteractionEnabled = false
|
|
sliderTrackView.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(sliderTrackView)
|
|
NSLayoutConstraint.activate([
|
|
sliderTrackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
sliderTrackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
sliderTrackView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75),
|
|
sliderTrackView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
])
|
|
}
|
|
|
|
private func setupSliderHandleView() {
|
|
handle.isUserInteractionEnabled = false
|
|
addSubview(handle)
|
|
}
|
|
|
|
private func updateControl(to value: CGFloat) {
|
|
let brightness = 1 - max(0, min(1, value))
|
|
var hue: CGFloat = 0
|
|
var saturation: CGFloat = 0
|
|
trackColor.getHue(&hue, saturation: &saturation, brightness: nil, alpha: nil)
|
|
|
|
let newColor = UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
|
|
|
|
CATransaction.begin()
|
|
CATransaction.setDisableActions(true)
|
|
handle.handleColor = newColor
|
|
CATransaction.commit()
|
|
|
|
moveHandle(to: value)
|
|
}
|
|
|
|
private func updateTrackColor(to color: UIColor) {
|
|
var hue: CGFloat = 0
|
|
var saturation: CGFloat = 0
|
|
var brightness: CGFloat = 0
|
|
color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil)
|
|
|
|
let colorWithMaxBrightness = UIColor(hue: hue, saturation: saturation, brightness: 1, alpha: 1)
|
|
|
|
updateTrackViewGradient(for: colorWithMaxBrightness)
|
|
currentValue = 1 - brightness
|
|
}
|
|
|
|
private func updateTrackViewGradient(for color: UIColor) {
|
|
CATransaction.begin()
|
|
CATransaction.setDisableActions(true)
|
|
sliderTrackView.gradientValues = (color, .black)
|
|
CATransaction.commit()
|
|
}
|
|
|
|
private func moveHandle(to value: CGFloat) {
|
|
let clampedValue = max(0, min(1, value))
|
|
let xPos = (clampedValue * confiningTrackFrame.width) + horizontalPadding
|
|
let size = CGSize(width: bounds.height * 1.15, height: bounds.height)
|
|
|
|
handle.frame = CGRect(origin: CGPoint(x: xPos - (size.width / 2), y: 0), size: size)
|
|
}
|
|
}
|