-
Notifications
You must be signed in to change notification settings - Fork 1
/
SMRotaryWheel.swift
129 lines (106 loc) · 4.26 KB
/
SMRotaryWheel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//
// SMRotaryWheel.swift
// RotatingWheelController
//
// Created by tom on 12/11/18.
// Copyright © 2018 tom. All rights reserved.
//
import UIKit
class SMRotaryWheel: UIControl {
var delegate: SMRotaryProtocol?
var container: UIView?
var numberOfSections: Int?
var startTransform = CGAffineTransform.identity
var deltaAngle: CGFloat?
var sectors: [SMSector<Int>] = []
var currentSector: Int = 0
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init(frame: CGRect, delegate: SMRotaryProtocol, numberOfSections: Int) {
super.init(frame: frame)
self.delegate = delegate
self.numberOfSections = numberOfSections
self.setup()
}
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let touchPoint = touch.location(in: self)
let dist = calculateDistanceFromCentre(point: touchPoint)
if dist < 40 || dist > 400 {
return false
}
guard let container = container else {
return false
}
let dx = touchPoint.x - container.center.x
let dy = touchPoint.y - container.center.y
deltaAngle = atan2(dy, dx)
startTransform = container.transform
return true
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
guard let container = container, let deltaAngle = deltaAngle else {
return false
}
let touchPoint = touch.location(in: self)
let dx = touchPoint.x - container.center.x
let dy = touchPoint.y - container.center.y
let angle = atan2(dy, dx)
let angleDifference = deltaAngle - angle
container.transform = startTransform.rotated(by: -angleDifference)
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
guard let container = container else {
return
}
let transformAngle = atan2(container.transform.b, container.transform.a)
guard let sector = sectors.first(where: {$0.contains(transformAngle)}) else {
return
}
let newVal = CGFloat(transformAngle - sector.middle)
currentSector = sector.value
UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(0.2)
container.transform = container.transform.rotated(by: -newVal)
UIView.commitAnimations()
delegate?.wheelDidChangeValue(to: currentSector)
}
func calculateDistanceFromCentre(point: CGPoint) -> CGFloat {
let centre = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
let dx = point.x - centre.x
let dy = point.y - centre.y
return (dx * dx + dy * dy).squareRoot()
}
func setup() {
container = UIView(frame: frame)
guard let container = container, let delegate = delegate, let numberOfSections = numberOfSections else {
return
}
let sectorAngle = 2 * CGFloat.pi / CGFloat(numberOfSections)
var upper = sectorAngle / 2.0
for i in 0..<numberOfSections {
// Create and add view to container
let view = delegate.viewFor(tag: i)
view.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5)
view.layer.position = CGPoint(x: container.bounds.size.width / 2.0, y: container.bounds.size.height / 2.0)
view.transform = CGAffineTransform(rotationAngle: CGFloat(i) * sectorAngle)
container.addSubview(view)
// Create and add sector slice information
if upper == -CGFloat.pi {
upper = CGFloat.pi
}
let lower = upper - sectorAngle
if lower < -CGFloat.pi {
sectors.append(SMSector<Int>(lower: -upper, upper: upper, value: i))
upper = -upper
} else {
sectors.append(SMSector<Int>(lower: lower, upper: upper, value: i))
upper = lower
}
}
container.isUserInteractionEnabled = false
addSubview(container)
delegate.wheelDidChangeValue(to: currentSector)
}
}