Create Knob class to design progress bar with dim light brightness
class Knob: UIControl {
/** Contains the minimum value of the receiver. */
var minimumValue: Float = 0
/** Contains the maximum value of the receiver. */
var maximumValue: Float = 1
/** Contains the receiver’s current value. */
private (set) var value: Float = 0
/** Sets the receiver’s current value, allowing you to animate the change visually. */
func setValue(_ newValue: Float, animated: Bool = false) {
value = min(maximumValue, max(minimumValue, newValue))
let angleRange = endAngle - startAngle
let valueRange = maximumValue - minimumValue
let angleValue = CGFloat(value - minimumValue) / CGFloat(valueRange) * angleRange + startAngle
renderer.setPointerAngle(angleValue, animated: animated)
}
/** Contains a Boolean value indicating whether changes
in the sliders value generate continuous update events. */
var isContinuous = true
private let renderer = KnobRenderer()
/** Specifies the width in points of the knob control track. Defaults to 2 */
var lineWidth: CGFloat {
get { return renderer.lineWidth }
set { renderer.lineWidth = newValue }
}
/** Specifies the angle of the start of the knob control track. Defaults to -11π/8 */
var startAngle: CGFloat {
get { return renderer.startAngle }
set { renderer.startAngle = newValue }
}
/** Specifies the end angle of the knob control track. Defaults to 3π/8 */
var endAngle: CGFloat {
get { return renderer.endAngle }
set { renderer.endAngle = newValue }
}
/** Specifies the length in points of the pointer on the knob. Defaults to 6 */
var pointerLength: CGFloat {
get { return renderer.pointerLength }
set { renderer.pointerLength = newValue }
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
renderer.updateBounds(bounds)
renderer.color = .red
renderer.setPointerAngle(renderer.startAngle)
layer.addSublayer(renderer.trackLayer)
layer.addSublayer(renderer.pointerLayer)
let gestureRecognizer = RotationGestureRecognizer(target: self, action: #selector(Knob.handleGesture(_:)))
addGestureRecognizer(gestureRecognizer)
}
@objc private func handleGesture(_ gesture: RotationGestureRecognizer) {
// 1
let midPointAngle = (2 * CGFloat(Double.pi) + startAngle - endAngle) / 2 + endAngle
// 2
var boundedAngle = gesture.touchAngle
if boundedAngle > midPointAngle {
boundedAngle -= 2 * CGFloat(Double.pi)
} else if boundedAngle < (midPointAngle - 2 * CGFloat(Double.pi)) {
boundedAngle -= 2 * CGFloat(Double.pi)
}
// 3
boundedAngle = min(endAngle, max(startAngle, boundedAngle))
// 4
let angleRange = endAngle - startAngle
let valueRange = maximumValue - minimumValue
let angleValue = Float(boundedAngle - startAngle) / Float(angleRange) * valueRange + minimumValue
// 5
setValue(angleValue)
if isContinuous {
sendActions(for: .valueChanged)
} else {
if gesture.state == .ended || gesture.state == .cancelled {
sendActions(for: .valueChanged)
}
}
}
}
private class KnobRenderer {
var color: UIColor = .blue {
didSet {
trackLayer.strokeColor = UIColor.red.cgColor
pointerLayer.strokeColor = UIColor.yellow.cgColor
}
}
var lineWidth: CGFloat = 2 {
didSet {
trackLayer.lineWidth = lineWidth
pointerLayer.lineWidth = 20
updateTrackLayerPath()
updatePointerLayerPath()
}
}
var startAngle: CGFloat = CGFloat(-Double.pi) {
didSet {
updateTrackLayerPath()
}
}
var endAngle: CGFloat = CGFloat(Double.pi) / 180 {
didSet {
updateTrackLayerPath()
}
}
var pointerLength: CGFloat = 6 {
didSet {
updateTrackLayerPath()
updatePointerLayerPath()
}
}
private (set) var pointerAngle: CGFloat = CGFloat(-Double.pi)
func setPointerAngle(_ newPointerAngle: CGFloat, animated: Bool = false) {
CATransaction.begin()
CATransaction.setDisableActions(true)
pointerLayer.transform = CATransform3DMakeRotation(newPointerAngle, 0, 0, 1)
if animated {
let midAngleValue = (max(newPointerAngle, pointerAngle) - min(newPointerAngle, pointerAngle)) / 2 + min(newPointerAngle, pointerAngle)
let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z")
animation.values = [pointerAngle, midAngleValue, newPointerAngle]
animation.keyTimes = [0.0, 0.5, 1.0]
animation.timingFunctions = [CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)]
pointerLayer.add(animation, forKey: nil)
}
CATransaction.commit()
pointerAngle = newPointerAngle
}
let trackLayer = CAShapeLayer()
let pointerLayer = CAShapeLayer()
init() {
trackLayer.fillColor = UIColor.clear.cgColor
pointerLayer.fillColor = UIColor.clear.cgColor
}
private func updateTrackLayerPath() {
let bounds = trackLayer.bounds
let center = CGPoint(x: bounds.midX , y: bounds.midY)
let offset = max(pointerLength, lineWidth)
let radius = min(bounds.width, bounds.height) - offset
let ring = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
trackLayer.path = ring.cgPath
}
private func updatePointerLayerPath() {
let bounds = trackLayer.bounds
let pointer = UIBezierPath()
pointer.move(to: CGPoint(x: pointerLayer.frame.size.width / 2 + 100, y: bounds.midY))
pointer.addLine(to: CGPoint(x: bounds.width - 40, y: bounds.midY))
pointerLayer.path = pointer.cgPath
}
func updateBounds(_ bounds: CGRect) {
trackLayer.bounds = bounds
trackLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
updateTrackLayerPath()
pointerLayer.bounds = trackLayer.bounds
pointerLayer.position = trackLayer.position
updatePointerLayerPath()
}
}
import UIKit.UIGestureRecognizerSubclass
private class RotationGestureRecognizer: UIPanGestureRecognizer {
private(set) var touchAngle: CGFloat = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
updateAngle(with: touches)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
updateAngle(with: touches)
}
private func updateAngle(with touches: Set<UITouch>) {
guard
let touch = touches.first,
let view = view
else {
return
}
let touchPoint = touch.location(in: view)
touchAngle = angle(for: touchPoint, in: view)
}
private func angle(for point: CGPoint, in view: UIView) -> CGFloat {
let centerOffset = CGPoint(x: point.x - view.bounds.midX, y: point.y - view.bounds.midY)
return atan2(centerOffset.y, centerOffset.x)
}
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
maximumNumberOfTouches = 1
minimumNumberOfTouches = 1
}
}
Now write code in viewController class
@IBOutlet var animateSwitch: UISwitch!
@IBOutlet var knob: Knob!
@IBOutlet weak var img: UIImageView!
var val = CGFloat()
@IBOutlet weak var progressBar: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
knob.lineWidth = 100
knob.pointerLength = 4
knob.setValue(0)
knob.addTarget(self, action: #selector(ViewController.handleValueChanged(_:)), for: .valueChanged)
updateLabel()
}
@IBAction func handleValueChanged(_ sender: Any) {
// if sender is UISlider {
// knob.setValue(valueSlider.value)
// } else {
// valueSlider.value = knob.value
// }
updateLabel()
}
@IBAction func handleRandomButtonPressed(_ sender: Any) {
// let randomValue = Float(arc4random_uniform(101)) / 100.0
// knob.setValue(randomValue, animated: animateSwitch.isOn)
// valueSlider.setValue(Float(randomValue), animated: animateSwitch.isOn)
updateLabel()
}
private func updateLabel() {
// valueLabel.text = String(format: "%.2f", knob.value)
if val < CGFloat(knob.value)
{
img.backgroundColor = img.backgroundColor?.lighter(by: CGFloat(knob.value))
}
else
{
img.backgroundColor = img.backgroundColor?.darker(by: CGFloat(knob.value))
}
val = CGFloat(knob.value)
}
}
extension UIColor {
func lighter(by percentage: CGFloat = 30.0) -> UIColor? {
return self.adjust(by: abs(percentage) )
}
func darker(by percentage: CGFloat = 30.0) -> UIColor? {
return self.adjust(by: -1 * abs(percentage) )
}
func adjust(by percentage: CGFloat = 30.0) -> UIColor? {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
return UIColor(red: min(red + percentage/100, 1.0),
green: min(green + percentage/100, 1.0),
blue: min(blue + percentage/100, 1.0),
alpha: alpha)
} else {
return nil
}
}
}