Skip to content

Commit 1c6b899

Browse files
committed
added handle rotation feature and minor improvements
1 parent 9b81fc5 commit 1c6b899

File tree

16 files changed

+205
-18
lines changed

16 files changed

+205
-18
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ The changelog for `MSCircularSlider`. Summarized release notes can be found in t
33

44
------------------------
55

6+
## 1.2.2 - 19-06-2018
7+
#### Added
8+
- An option to rotate the handle image to always point outwards
9+
10+
#### Changed
11+
- Minor structural improvement
12+
613
## 1.2.1 - 09-02-2018
714
#### Fixed
815
- A setter conflict occuring on earlier Swift versions

MSCircularSlider.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
Pod::Spec.new do |s|
22
s.name = 'MSCircularSlider'
3-
s.version = '1.2.1'
3+
s.version = '1.2.2'
44
s.license = { :type => 'MIT', :file => 'LICENSE' }
55
s.authors = { 'ThunderStruct' => '[email protected]' }
66
s.summary = 'A full-featured circular slider for iOS applications'
77
s.homepage = 'https://github.com/ThunderStruct/MSCircularSlider'
88

99
# Source Info
1010
s.platform = :ios, '9.3'
11-
s.source = { :git => 'https://github.com/ThunderStruct/MSCircularSlider.git', :branch => "master", :tag => "1.2.1" }
11+
s.source = { :git => 'https://github.com/ThunderStruct/MSCircularSlider.git', :branch => "master", :tag => "1.2.2" }
1212
s.source_files = 'MSCircularSlider/*.{swift}'
1313

1414
s.requires_arc = true

MSCircularSlider/MSCircularSlider+IB.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ extension MSCircularSlider {
141141
}
142142
}
143143

144+
@IBInspectable public var _handleRotatable: Bool {
145+
get {
146+
return handleRotatable
147+
}
148+
set {
149+
handleRotatable = newValue
150+
}
151+
}
152+
144153
//================================================================================
145154
// LABELS PROPERTIES
146155
//================================================================================

MSCircularSlider/MSCircularSlider.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ public class MSCircularSlider: UIControl {
221221
}
222222
}
223223

224+
/** Specifies whether or not the handle should rotate to always point outwards - *default: false* */
225+
public var handleRotatable: Bool {
226+
set {
227+
handle.isRotatable = newValue
228+
}
229+
get {
230+
return handle.isRotatable
231+
}
232+
}
233+
224234
/** The calculated handle's diameter based on its type */
225235
public var handleDiameter: CGFloat {
226236
return handle.diameter
@@ -416,9 +426,10 @@ public class MSCircularSlider: UIControl {
416426
drawLabels(ctx: ctx!)
417427

418428
// Rotate slider
419-
self.transform = getRotationalTransform()
429+
let rotationalTransform = getRotationalTransform()
430+
self.transform = rotationalTransform
420431
for view in subviews { // cancel rotation on all subviews added by the user
421-
view.transform = getRotationalTransform().inverted()
432+
view.transform = rotationalTransform.inverted()
422433
}
423434

424435
}
@@ -832,7 +843,6 @@ public class MSCircularSlider: UIControl {
832843
return transform
833844
}
834845
else {
835-
836846
if let rotation = self.rotationAngle {
837847
return CGAffineTransform.identity.rotated(by: CGFloat(toRad(Double(rotation))))
838848
}

MSCircularSlider/MSCircularSliderHandle.swift

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ public class MSCircularSliderHandle: CALayer {
4646
}
4747
}
4848

49+
/** Specifies whether or not the handle should rotate to always point outwards - *default: false* */
50+
internal var isRotatable: Bool = false {
51+
didSet {
52+
setNeedsDisplay()
53+
}
54+
}
55+
4956
/** The handle's color - *default: .darkGray* */
5057
internal var color: UIColor = .darkGray {
5158
didSet {
@@ -123,8 +130,13 @@ public class MSCircularSliderHandle: CALayer {
123130
y: center().y - diameter * 0.5,
124131
width: diameter,
125132
height: diameter)
126-
image?.draw(in: frame)
127-
133+
if isRotatable {
134+
let rotatedImg = imageRotated(img: image!, byDegrees: angle)
135+
rotatedImg.draw(in: frame)
136+
}
137+
else {
138+
image?.draw(in: frame)
139+
}
128140
}
129141
else if handleType == .doubleCircle {
130142
calculatedHandleColor.withAlphaComponent(isHighlightable && isPressed ? 0.9 : 1.0).set()
@@ -159,4 +171,41 @@ public class MSCircularSliderHandle: CALayer {
159171
return point.x >= center().x - handleRadius && point.x <= center().x + handleRadius && point.y >= center().y - handleRadius && point.y <= center().y + handleRadius
160172
}
161173

174+
/** Converts degrees to radians */
175+
private func toRad(_ degrees: Double) -> Double {
176+
return ((Double.pi * degrees) / 180.0)
177+
}
178+
179+
/** Converts radians to degrees */
180+
private func toDeg(_ radians: Double) -> Double {
181+
return ((180.0 * radians) / Double.pi)
182+
}
183+
184+
/** Rotates a given image by the specified degrees */
185+
private func imageRotated(img: UIImage, byDegrees degrees: CGFloat) -> UIImage {
186+
187+
// calculate the size of the rotated view's containing box for our drawing space
188+
let rotatedViewBox = UIView(frame: CGRect(origin: CGPoint.zero, size: img.size))
189+
let t = CGAffineTransform(rotationAngle: CGFloat(toRad(Double(degrees))))
190+
rotatedViewBox.transform = t
191+
let rotatedSize = rotatedViewBox.frame.size
192+
193+
// Create the bitmap context
194+
UIGraphicsBeginImageContext(rotatedSize)
195+
let bitmap = UIGraphicsGetCurrentContext()
196+
197+
// Move the origin to the middle of the image so we will rotate and scale around the center.
198+
bitmap?.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0)
199+
200+
// // Rotate the image context
201+
bitmap?.rotate(by: CGFloat(toRad(Double(degrees))));
202+
203+
// Now, draw the rotated/scaled image into the context
204+
bitmap?.draw(img.cgImage!, in: CGRect(x: -img.size.width / 2, y: -img.size.height / 2, width: img.size.width, height: img.size.height))
205+
206+
let newImage = UIGraphicsGetImageFromCurrentImageContext()
207+
UIGraphicsEndImageContext()
208+
209+
return newImage!
210+
}
162211
}

MSCircularSlider/MSDoubleHandleCircularSlider+IB.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,13 @@ extension MSDoubleHandleCircularSlider {
8383
}
8484
}
8585

86+
@IBInspectable public var _secondHandleRotatable: Bool {
87+
get {
88+
return secondHandleRotatable
89+
}
90+
set {
91+
secondHandleRotatable = newValue
92+
}
93+
}
8694

8795
}

MSCircularSlider/MSDoubleHandleCircularSlider.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ public class MSDoubleHandleCircularSlider: MSCircularSlider {
139139
}
140140
}
141141

142+
/** Specifies whether or not the second handle should rotate to always point outwards - *default: false* */
143+
public var secondHandleRotatable: Bool {
144+
set {
145+
secondHandle.isRotatable = newValue
146+
}
147+
get {
148+
return secondHandle.isRotatable
149+
}
150+
}
151+
142152
// CALCULATED MEMBERS
143153

144154
/** The calculated second handle's diameter based on its type */
@@ -186,7 +196,7 @@ public class MSDoubleHandleCircularSlider: MSCircularSlider {
186196
secondHandle.center = {
187197
return self.pointOnCircleAt(angle: self.secondAngle)
188198
}
189-
secondHandle.angle = 60
199+
secondHandle.angle = CGFloat(max(0, 60.0).truncatingRemainder(dividingBy: Double(maximumAngle + 1)))
190200
}
191201

192202
override init(frame: CGRect) {

MSCircularSliderExample/MSCircularSlider/MSCircularSlider+IB.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ extension MSCircularSlider {
141141
}
142142
}
143143

144+
@IBInspectable public var _handleRotatable: Bool {
145+
get {
146+
return handleRotatable
147+
}
148+
set {
149+
handleRotatable = newValue
150+
}
151+
}
152+
144153
//================================================================================
145154
// LABELS PROPERTIES
146155
//================================================================================

MSCircularSliderExample/MSCircularSlider/MSCircularSlider.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ public class MSCircularSlider: UIControl {
221221
}
222222
}
223223

224+
/** Specifies whether or not the handle should rotate to always point outwards - *default: false* */
225+
public var handleRotatable: Bool {
226+
set {
227+
handle.isRotatable = newValue
228+
}
229+
get {
230+
return handle.isRotatable
231+
}
232+
}
233+
224234
/** The calculated handle's diameter based on its type */
225235
public var handleDiameter: CGFloat {
226236
return handle.diameter
@@ -416,9 +426,10 @@ public class MSCircularSlider: UIControl {
416426
drawLabels(ctx: ctx!)
417427

418428
// Rotate slider
419-
self.transform = getRotationalTransform()
429+
let rotationalTransform = getRotationalTransform()
430+
self.transform = rotationalTransform
420431
for view in subviews { // cancel rotation on all subviews added by the user
421-
view.transform = getRotationalTransform().inverted()
432+
view.transform = rotationalTransform.inverted()
422433
}
423434

424435
}
@@ -832,7 +843,6 @@ public class MSCircularSlider: UIControl {
832843
return transform
833844
}
834845
else {
835-
836846
if let rotation = self.rotationAngle {
837847
return CGAffineTransform.identity.rotated(by: CGFloat(toRad(Double(rotation))))
838848
}

MSCircularSliderExample/MSCircularSlider/MSCircularSliderHandle.swift

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ public class MSCircularSliderHandle: CALayer {
4646
}
4747
}
4848

49+
/** Specifies whether or not the handle should rotate to always point outwards - *default: false* */
50+
internal var isRotatable: Bool = false {
51+
didSet {
52+
setNeedsDisplay()
53+
}
54+
}
55+
4956
/** The handle's color - *default: .darkGray* */
5057
internal var color: UIColor = .darkGray {
5158
didSet {
@@ -123,8 +130,13 @@ public class MSCircularSliderHandle: CALayer {
123130
y: center().y - diameter * 0.5,
124131
width: diameter,
125132
height: diameter)
126-
image?.draw(in: frame)
127-
133+
if isRotatable {
134+
let rotatedImg = imageRotated(img: image!, byDegrees: angle)
135+
rotatedImg.draw(in: frame)
136+
}
137+
else {
138+
image?.draw(in: frame)
139+
}
128140
}
129141
else if handleType == .doubleCircle {
130142
calculatedHandleColor.withAlphaComponent(isHighlightable && isPressed ? 0.9 : 1.0).set()
@@ -159,4 +171,41 @@ public class MSCircularSliderHandle: CALayer {
159171
return point.x >= center().x - handleRadius && point.x <= center().x + handleRadius && point.y >= center().y - handleRadius && point.y <= center().y + handleRadius
160172
}
161173

174+
/** Converts degrees to radians */
175+
private func toRad(_ degrees: Double) -> Double {
176+
return ((Double.pi * degrees) / 180.0)
177+
}
178+
179+
/** Converts radians to degrees */
180+
private func toDeg(_ radians: Double) -> Double {
181+
return ((180.0 * radians) / Double.pi)
182+
}
183+
184+
/** Rotates a given image by the specified degrees */
185+
private func imageRotated(img: UIImage, byDegrees degrees: CGFloat) -> UIImage {
186+
187+
// calculate the size of the rotated view's containing box for our drawing space
188+
let rotatedViewBox = UIView(frame: CGRect(origin: CGPoint.zero, size: img.size))
189+
let t = CGAffineTransform(rotationAngle: CGFloat(toRad(Double(degrees))))
190+
rotatedViewBox.transform = t
191+
let rotatedSize = rotatedViewBox.frame.size
192+
193+
// Create the bitmap context
194+
UIGraphicsBeginImageContext(rotatedSize)
195+
let bitmap = UIGraphicsGetCurrentContext()
196+
197+
// Move the origin to the middle of the image so we will rotate and scale around the center.
198+
bitmap?.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0)
199+
200+
// // Rotate the image context
201+
bitmap?.rotate(by: CGFloat(toRad(Double(degrees))));
202+
203+
// Now, draw the rotated/scaled image into the context
204+
bitmap?.draw(img.cgImage!, in: CGRect(x: -img.size.width / 2, y: -img.size.height / 2, width: img.size.width, height: img.size.height))
205+
206+
let newImage = UIGraphicsGetImageFromCurrentImageContext()
207+
UIGraphicsEndImageContext()
208+
209+
return newImage!
210+
}
162211
}

0 commit comments

Comments
 (0)