From dfdf167c5ba56803ae8eb73105b2b8313fda1c93 Mon Sep 17 00:00:00 2001 From: cemolcay Date: Thu, 26 Nov 2020 17:16:56 +0200 Subject: [PATCH] Add markers Add editingDidBegin event support. --- .DS_Store | Bin 6148 -> 6148 bytes LiveKnob.podspec | 2 +- LiveKnob.xcodeproj/project.pbxproj | 12 +- LiveKnob/LiveKnob.swift | 171 +++++++++++++++++++++-------- LiveKnob/ViewController.swift | 11 +- README.md | 3 + 6 files changed, 147 insertions(+), 52 deletions(-) diff --git a/.DS_Store b/.DS_Store index 7d919f858fc4415ca8126c40497bdd92f635dd15..b0aff99167c90bcf09b0d3d34caa2a6bc908dbe4 100644 GIT binary patch delta 69 zcmZoMXffE3#Kd$^c=7@!t;u~%Tx=UyeE9xtpFEGr48hdmFUT+qPR`FQU;qI|h0Ti0 P&sZil@NH)2_{$FfCzTc; delta 69 zcmZoMXffE3#Kbh^?BoSZT9f;jxY$l>-n@6dV)8sDGXztMzaYafI5|JJfB_61Y*u7` O#xk*iZ! midPointAngle { boundedAngle -= 2 * CGFloat.pi } else if boundedAngle < (midPointAngle - 2 * CGFloat.pi) { boundedAngle += 2 * CGFloat.pi } - + boundedAngle = min(endAngle, max(startAngle, boundedAngle)) value = min(maximumValue, max(minimumValue, valueForAngle(boundedAngle))) } - + // Inform changes based on continuous behaviour of the knob. if continuous { sendActions(for: .valueChanged) @@ -182,20 +193,24 @@ public enum LiveKnobControlType: Int, Codable { sendActions(for: .valueChanged) } } - + + if gesture.state == .began { + sendActions(for: .editingDidBegin) + } + if gesture.state == .ended { sendActions(for: .editingDidEnd) } } - + // MARK: Value/Angle conversion - + public func valueForAngle(_ angle: CGFloat) -> Float { let angleRange = Float(endAngle - startAngle) let valueRange = maximumValue - minimumValue return Float(angle - startAngle) / angleRange * valueRange + minimumValue } - + public func angleForValue(_ value: Float) -> CGFloat { let angleRange = endAngle - startAngle let valueRange = CGFloat(maximumValue - minimumValue) @@ -213,62 +228,128 @@ public class LiveKnobGestureRecognizer: UIPanGestureRecognizer { private var lastTouchPoint: CGPoint = .zero /// Horizontal and vertical slide sensitivity multiplier. Defaults 0.005. public var slidingSensitivity: CGFloat = 0.005 - + // MARK: UIGestureRecognizerSubclass - + public override func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) - + state = .began + // Update diagonal movement. guard let touch = touches.first else { return } lastTouchPoint = touch.location(in: view) - + // Update rotary movement. updateTouchAngleWithTouches(touches) } - + public override func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) - + state = .changed + // Update diagonal movement. guard let touchPoint = touches.first?.location(in: view) else { return } diagonalChange.width = (touchPoint.x - lastTouchPoint.x) * slidingSensitivity diagonalChange.height = (touchPoint.y - lastTouchPoint.y) * slidingSensitivity - + // Reset last touch point. lastTouchPoint = touchPoint - + // Update rotary movement. updateTouchAngleWithTouches(touches) } - + public override func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) state = .ended } - + private func updateTouchAngleWithTouches(_ touches: Set) { guard let touch = touches.first else { return } let touchPoint = touch.location(in: view) touchAngle = calculateAngleToPoint(touchPoint) } - + private func calculateAngleToPoint(_ point: CGPoint) -> CGFloat { let centerOffset = CGPoint(x: point.x - view!.bounds.midX, y: point.y - view!.bounds.midY) return atan2(centerOffset.y, centerOffset.x) } - + // MARK: Lifecycle - + public init() { super.init(target: nil, action: nil) maximumNumberOfTouches = 1 minimumNumberOfTouches = 1 } - + public override init(target: Any?, action: Selector?) { super.init(target: target, action: action) maximumNumberOfTouches = 1 minimumNumberOfTouches = 1 } } + +extension LiveKnob { + + func refreshMarkersIfNeeded() { + for sub in subviews { + if sub is LiveKnobMarker { + sub.removeFromSuperview() + } + } + for marker in markers { + addSubview(marker) + } + } + + func layoutMarkersIfNeeded() { + for (index, marker) in markers.enumerated() { + let angle = radians(from: self.angle(for: marker, at: index)) + + let x = cos(angle) + let y = sin(angle) + + marker.transform = .identity + var rect = marker.frame + let radius = (min(baseLayer.bounds.width, baseLayer.bounds.height) / 2) - baseLineWidth + marker.markerOffset + + let newX = CGFloat(x) * radius + center.x + let newY = CGFloat(y) * radius + center.y + + rect.origin.x = newX - marker.frame.width / 2 + rect.origin.y = newY - marker.frame.height / 2 + marker.frame = rect + marker.transform = marker.markerTransform ?? CGAffineTransform(rotationAngle: angle) + } + } + + func angle(for marker: UIView, at index: Int) -> Float { + let percentage: Float = ((100.0 / Float(markers.count - 1)) * Float(index)) / 100.0 + return degrees(for: percentage, from: degrees(from: startAngle), to: degrees(from: endAngle)) + } + + func degrees(for percentage: Float, from startAngle: Float, to endAngle: Float) -> Float { + if endAngle > startAngle { + return startAngle + (endAngle - startAngle) * percentage + } else { + return startAngle + (360.0 + endAngle - startAngle) * percentage + } + } + + public func degrees(from radian: CGFloat) -> Float { + return degrees(from: Float(radian)) + } + + public func degrees(from radian: Float) -> Float { + return radian * (180 / Float.pi) + } + + func radians(from degree: Float) -> CGFloat { + return radians(from: CGFloat(degree)) + } + + func radians(from degree: CGFloat) -> CGFloat { + return degree * .pi / 180 + } +} diff --git a/LiveKnob/ViewController.swift b/LiveKnob/ViewController.swift index 9d97487..3054d95 100644 --- a/LiveKnob/ViewController.swift +++ b/LiveKnob/ViewController.swift @@ -17,8 +17,17 @@ class ViewController: UIViewController { guard let knob = knob, let knobLabel = knobLabel else { return } knobLabel.text = String(format: "%.2f", arguments: [knob.value]) knob.controlType = .horizontalAndVertical + knob.markers = [Int](0..<8).map({ _ in createMarker() }) } - + + func createMarker() -> LiveKnobMarker { + let view = LiveKnobMarker(frame: CGRect(origin: .zero, size: CGSize(width: 8, height: 8))) + view.isUserInteractionEnabled = false + view.backgroundColor = .lightGray + view.layer.cornerRadius = 4 + return view + } + @IBAction func knobValueDidChange(sender: LiveKnob) { knobLabel?.text = String(format: "%.2f", arguments: [sender.value]) } diff --git a/README.md b/README.md index 8cb364d..3c22c05 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ You can change the line width and color of the base ring, progress ring and poin ### LiveKnobControlType You can set the `controlType` for changing the knob's touch control behaviour. It supports horizontal and/or vertical slidings as well as rotary slidings. +### LiveKnobMarker +You can create custom marker views in with `LiveKnobMarker` type and set them to LiveKnob's `markers` array in order to draw markers around the knob. You can set individual offset and transform for each marker as well. + AppStore ----