@@ -11,59 +11,79 @@ import UIKit
1111@IBDesignable public class GMStepper: UIControl {
1212
1313 /// Current value of the stepper. Defaults to 0.
14− @IBInspectable public var value = 0 {
14+ @objc @IBInspectable public var value: Double = 0 {
1515 didSet {
1616 value = min(maximumValue, max(minimumValue, value))
17− label.text = String(value)
17+
18+ let isInteger = floor(value) == value
19+
20+ //
21+ // If we have items, we will display them as steps
22+ //
23+
24+ if isInteger && stepValue == 1.0 && items.count > 0 {
25+ label.text = items[Int(value)]
26+ }
27+ else if showIntegerIfDoubleIsInteger && isInteger {
28+ label.text = String(stringInterpolationSegment: Int(value))
29+ } else {
30+ label.text = String(stringInterpolationSegment: value)
31+ }
1832
1933 if oldValue != value {
20− sendActionsForControlEvents(.ValueChanged)
34+ sendActions(for: .valueChanged)
2135 }
2236 }
2337 }
2438
2539 /// Minimum value. Must be less than maximumValue. Defaults to 0.
26− @IBInspectable public var minimumValue: Int = 0 {
40+ @objc @IBInspectable public var minimumValue: Double = 0 {
2741 didSet {
2842 value = min(maximumValue, max(minimumValue, value))
2943 }
3044 }
3145
32− /// Maximum value. Must be more than minimumValue. Defaults to 10.
33− @IBInspectable public var maximumValue: Int = 10 {
46+ /// Maximum value. Must be more than minimumValue. Defaults to 100.
47+ @objc @IBInspectable public var maximumValue: Double = 100 {
3448 didSet {
3549 value = min(maximumValue, max(minimumValue, value))
3650 }
3751 }
3852
53+ /// Step/Increment value as in UIStepper. Defaults to 1.
54+ @objc @IBInspectable public var stepValue: Double = 1
55+
3956 /// The same as UIStepper's autorepeat. If true, holding on the buttons or keeping the pan gesture alters the value repeatedly. Defaults to true.
40− @IBInspectable public var autorepeat: Bool = true
57+ @objc @IBInspectable public var autorepeat: Bool = true
58+
59+ /// If the value is integer, it is shown without floating point.
60+ @objc @IBInspectable public var showIntegerIfDoubleIsInteger: Bool = true
4161
42− /// Text on the left button. Be sure that it fits in the button. Defaults to "-".
43− @IBInspectable public var leftButtonText: String = "-" {
62+ /// Text on the left button. Be sure that it fits in the button. Defaults to "−".
63+ @objc @IBInspectable public var leftButtonText: String = "−" {
4464 didSet {
45− leftButton.setTitle(leftButtonText, forState: .Normal)
65+ leftButton.setTitle(leftButtonText, for: .normal)
4666 }
4767 }
4868
4969 /// Text on the right button. Be sure that it fits in the button. Defaults to "+".
50− @IBInspectable public var rightButtonText: String = "+" {
70+ @objc @IBInspectable public var rightButtonText: String = "+" {
5171 didSet {
52− rightButton.setTitle(rightButtonText, forState: .Normal)
72+ rightButton.setTitle(rightButtonText, for: .normal)
5373 }
5474 }
5575
5676 /// Text color of the buttons. Defaults to white.
57− @IBInspectable public var buttonsTextColor: UIColor = UIColor.whiteColor() {
77+ @objc @IBInspectable public var buttonsTextColor: UIColor = UIColor.white {
5878 didSet {
5979 for button in [leftButton, rightButton] {
60− button.setTitleColor(buttonsTextColor, forState: .Normal)
80+ button.setTitleColor(buttonsTextColor, for: .normal)
6181 }
6282 }
6383 }
6484
6585 /// Background color of the buttons. Defaults to dark blue.
66− @IBInspectable public var buttonsBackgroundColor: UIColor = UIColor(red:0.21, green:0.5, blue:0.74, alpha:1) {
86+ @objc @IBInspectable public var buttonsBackgroundColor: UIColor = UIColor(red:0.21, green:0.5, blue:0.74, alpha:1) {
6787 didSet {
6888 for button in [leftButton, rightButton] {
6989 button.backgroundColor = buttonsBackgroundColor
@@ -73,7 +93,7 @@ import UIKit
7393 }
7494
7595 /// Font of the buttons. Defaults to AvenirNext-Bold, 20.0 points in size.
76− public var buttonsFont = UIFont(name: "AvenirNext-Bold", size: 20.0)! {
96+ @objc public var buttonsFont = UIFont(name: "AvenirNext-Bold", size: 20.0)! {
7797 didSet {
7898 for button in [leftButton, rightButton] {
7999 button.titleLabel?.font = buttonsFont
@@ -82,36 +102,59 @@ import UIKit
82102 }
83103
84104 /// Text color of the middle label. Defaults to white.
85− @IBInspectable public var labelTextColor: UIColor = UIColor.whiteColor() {
105+ @objc @IBInspectable public var labelTextColor: UIColor = UIColor.white {
86106 didSet {
87107 label.textColor = labelTextColor
88108 }
89109 }
90110
91111 /// Text color of the middle label. Defaults to lighter blue.
92− @IBInspectable public var labelBackgroundColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1) {
112+ @objc @IBInspectable public var labelBackgroundColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1) {
93113 didSet {
94114 label.backgroundColor = labelBackgroundColor
95115 }
96116 }
97117
98118 /// Font of the middle label. Defaults to AvenirNext-Bold, 25.0 points in size.
99− public var labelFont = UIFont(name: "AvenirNext-Bold", size: 25.0)! {
119+ @objc public var labelFont = UIFont(name: "AvenirNext-Bold", size: 25.0)! {
100120 didSet {
101121 label.font = labelFont
102122 }
103123 }
124+ /// Corner radius of the middle label. Defaults to 0.
125+ @objc @IBInspectable public var labelCornerRadius: CGFloat = 0 {
126+ didSet {
127+ label.layer.cornerRadius = labelCornerRadius
128+
129+ }
130+ }
104131
105132 /// Corner radius of the stepper's layer. Defaults to 4.0.
106− @IBInspectable public var cornerRadius: CGFloat = 4.0 {
133+ @objc @IBInspectable public var cornerRadius: CGFloat = 4.0 {
107134 didSet {
108135 layer.cornerRadius = cornerRadius
109136 clipsToBounds = true
110137 }
111138 }
139+
140+ /// Border width of the stepper and middle label's layer. Defaults to 0.0.
141+ @objc @IBInspectable public var borderWidth: CGFloat = 0.0 {
142+ didSet {
143+ layer.borderWidth = borderWidth
144+ label.layer.borderWidth = borderWidth
145+ }
146+ }
147+
148+ /// Color of the border of the stepper and middle label's layer. Defaults to clear color.
149+ @objc @IBInspectable public var borderColor: UIColor = UIColor.clear {
150+ didSet {
151+ layer.borderColor = borderColor.cgColor
152+ label.layer.borderColor = borderColor.cgColor
153+ }
154+ }
112155
113156 /// Percentage of the middle label's width. Must be between 0 and 1. Defaults to 0.5. Be sure that it is wide enough to show the value.
114− @IBInspectable public var labelWidthWeight: CGFloat = 0.5 {
157+ @objc @IBInspectable public var labelWidthWeight: CGFloat = 0.5 {
115158 didSet {
116159 labelWidthWeight = min(1, max(0, labelWidthWeight))
117160 setNeedsLayout()
@@ -119,7 +162,7 @@ import UIKit
119162 }
120163
121164 /// Color of the flashing animation on the buttons in case the value hit the limit.
122− @IBInspectable public var limitHitAnimationColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1)
165+ @objc @IBInspectable public var limitHitAnimationColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1)
123166
124167 /**
125168 Width of the sliding animation. When buttons clicked, the middle label does a slide animation towards to the clicked button. Defaults to 5.
@@ -127,44 +170,52 @@ import UIKit
127170 let labelSlideLength: CGFloat = 5
128171
129172 /// Duration of the sliding animation
130− let labelSlideDuration = NSTimeInterval(0.1)
173+ let labelSlideDuration = TimeInterval(0.1)
131174
132175 /// Duration of the animation when the value hits the limit.
133− let limitHitAnimationDuration = NSTimeInterval(0.1)
176+ let limitHitAnimationDuration = TimeInterval(0.1)
134177
135178 lazy var leftButton: UIButton = {
136179 let button = UIButton()
137− button.setTitle(self.leftButtonText, forState: .Normal)
138− button.setTitleColor(self.buttonsTextColor, forState: .Normal)
180+ button.setTitle(self.leftButtonText, for: .normal)
181+ button.setTitleColor(self.buttonsTextColor, for: .normal)
139182 button.backgroundColor = self.buttonsBackgroundColor
140183 button.titleLabel?.font = self.buttonsFont
141− button.addTarget(self, action: "leftButtonTouchDown:", forControlEvents: .TouchDown)
142− button.addTarget(self, action: "buttonTouchUp:", forControlEvents: UIControlEvents.TouchUpInside)
143− button.addTarget(self, action: "buttonTouchUp:", forControlEvents: UIControlEvents.TouchUpOutside)
184+ button.addTarget(self, action: #selector(GMStepper.leftButtonTouchDown), for: .touchDown)
185+ button.addTarget(self, action: #selector(GMStepper.buttonTouchUp), for: .touchUpInside)
186+ button.addTarget(self, action: #selector(GMStepper.buttonTouchUp), for: .touchUpOutside)
187+ button.addTarget(self, action: #selector(GMStepper.buttonTouchUp), for: .touchCancel)
144188 return button
145189 }()
146190
147191 lazy var rightButton: UIButton = {
148192 let button = UIButton()
149− button.setTitle(self.rightButtonText, forState: .Normal)
150− button.setTitleColor(self.buttonsTextColor, forState: .Normal)
193+ button.setTitle(self.rightButtonText, for: .normal)
194+ button.setTitleColor(self.buttonsTextColor, for: .normal)
151195 button.backgroundColor = self.buttonsBackgroundColor
152196 button.titleLabel?.font = self.buttonsFont
153− button.addTarget(self, action: "rightButtonTouchDown:", forControlEvents: .TouchDown)
154− button.addTarget(self, action: "buttonTouchUp:", forControlEvents: UIControlEvents.TouchUpInside)
155− button.addTarget(self, action: "buttonTouchUp:", forControlEvents: UIControlEvents.TouchUpOutside)
197+ button.addTarget(self, action: #selector(GMStepper.rightButtonTouchDown), for: .touchDown)
198+ button.addTarget(self, action: #selector(GMStepper.buttonTouchUp), for: .touchUpInside)
199+ button.addTarget(self, action: #selector(GMStepper.buttonTouchUp), for: .touchUpOutside)
200+ button.addTarget(self, action: #selector(GMStepper.buttonTouchUp), for: .touchCancel)
156201 return button
157202 }()
158203
159204 lazy var label: UILabel = {
160205 let label = UILabel()
161− label.textAlignment = .Center
162− label.text = String(self.value)
206+ label.textAlignment = .center
207+ if self.showIntegerIfDoubleIsInteger && floor(self.value) == self.value {
208+ label.text = String(stringInterpolationSegment: Int(self.value))
209+ } else {
210+ label.text = String(stringInterpolationSegment: self.value)
211+ }
163212 label.textColor = self.labelTextColor
164213 label.backgroundColor = self.labelBackgroundColor
165214 label.font = self.labelFont
166− label.userInteractionEnabled = true
167− let panRecognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
215+ label.layer.cornerRadius = self.labelCornerRadius
216+ label.layer.masksToBounds = true
217+ label.isUserInteractionEnabled = true
218+ let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(GMStepper.handlePan))
168219 panRecognizer.maximumNumberOfTouches = 1
169220 label.addGestureRecognizer(panRecognizer)
170221 return label
@@ -192,9 +243,33 @@ import UIKit
192243 }
193244 }
194245 }
246+
247+
248+ @objc public var items : [String] = [] {
249+ didSet {
250+ let isInteger = floor(value) == value
251+
252+ //
253+ // If we have items, we will display them as steps
254+ //
255+
256+ if isInteger && stepValue == 1.0 && items.count > 0 {
257+
258+ var value = Int(self.value)
259+
260+ if value >= items.count {
261+ value = items.count - 1
262+ self.value = Double(value)
263+ }
264+ else {
265+ label.text = items[value]
266+ }
267+ }
268+ }
269+ }
195270
196271 /// Timer used for autorepeat option
197− var timer: NSTimer?
272+ var timer: Timer?
198273
199274 /** When UIStepper reaches its top speed, it alters the value with a time interval of ~0.05 sec.
200275 The user pressing and holding on the stepper repeatedly:
@@ -202,7 +277,7 @@ import UIKit
202277 - For the next 1.5 sec, it changes the value every 0.1 sec.
203278 - Then, every 0.05 sec.
204279 */
205− let timerInterval = NSTimeInterval(0.05)
280+ let timerInterval = TimeInterval(0.05)
206281
207282 /// Check the handleTimerFire: function. While it is counting the number of fires, it decreases the mod value so that the value is altered more frequently.
208283 var timerFireCount = 0
@@ -216,25 +291,12 @@ import UIKit
216291 }
217292 }
218293
219− /// Useful for logging the timer interval. You can call this in the timer handler to test the autorepeat option.
220− lazy var printTimerGaps: () -> () = {
221− var prevTime: CFAbsoluteTime?
222−
223− return { _ in
224− var now = CFAbsoluteTimeGetCurrent()
225− if let prevTime = prevTime {
226− println(now - prevTime)
227− }
228− prevTime = now
229− }
230− }()
231−
232− required public init(coder aDecoder: NSCoder) {
294+ @objc required public init?(coder aDecoder: NSCoder) {
233295 super.init(coder: aDecoder)
234296 setup()
235297 }
236298
237− public override init(frame: CGRect) {
299+ @objc public override init(frame: CGRect) {
238300 super.init(frame: frame)
239301 setup()
240302 }
@@ -247,6 +309,9 @@ import UIKit
247309 backgroundColor = buttonsBackgroundColor
248310 layer.cornerRadius = cornerRadius
249311 clipsToBounds = true
312+ labelOriginalCenter = label.center
313+
314+ NotificationCenter.default.addObserver(self, selector: #selector(GMStepper.reset), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
250315 }
251316
252317 public override func layoutSubviews() {
@@ -264,26 +329,44 @@ import UIKit
264329
265330 func updateValue() {
266331 if stepperState == .ShouldIncrease {
267− value += 1
332+ value += stepValue
268333 } else if stepperState == .ShouldDecrease {
269− value -= 1
334+ value -= stepValue
270335 }
271336 }
337+
338+ deinit {
339+ resetTimer()
340+ NotificationCenter.default.removeObserver(self)
341+ }
342+
343+ /// Useful closure for logging the timer interval. You can call this in the timer handler to test the autorepeat option. Not used in the current implementation.
344+// lazy var printTimerGaps: () -> () = {
345+// var prevTime: CFAbsoluteTime?
346+//
347+// return { _ in
348+// var now = CFAbsoluteTimeGetCurrent()
349+// if let prevTime = prevTime {
350+// print(now - prevTime)
351+// }
352+// prevTime = now
353+// }
354+// }()
272355}
273356
274357// MARK: Pan Gesture
275358extension GMStepper {
276− func handlePan(gesture: UIPanGestureRecognizer) {
277− leftButton.enabled = false
278− rightButton.enabled = false
279−
359+ @objc func handlePan(gesture: UIPanGestureRecognizer) {
280360 switch gesture.state {
281− case .Changed:
282− var translation = gesture.translationInView(label)
283− gesture.setTranslation(CGPointZero, inView: label)
361+ case .began:
362+ leftButton.isEnabled = false
363+ rightButton.isEnabled = false
364+ case .changed:
365+ let translation = gesture.translation(in: label)
366+ gesture.setTranslation(CGPoint.zero, in: label)
284367
285− var slidingRight = gesture.velocityInView(label).x > 0
286− var slidingLeft = gesture.velocityInView(label).x < 0
368+ let slidingRight = gesture.velocity(in: label).x > 0
369+ let slidingLeft = gesture.velocity(in: label).x < 0
287370
288371 // Move the label with pan
289372 if slidingRight {
@@ -316,22 +399,23 @@ extension GMStepper {
316399 self.rightButton.backgroundColor = self.buttonsBackgroundColor
317400 self.leftButton.backgroundColor = self.buttonsBackgroundColor
318401 }
319−
320− case .Ended:
402+ case .ended, .cancelled, .failed:
321403 reset()
322404 default:
323405 break
324406 }
325407 }
326408
327− func reset() {
409+ @objc func reset() {
328410 panState = .Stable
329411 stepperState = .Stable
330412 resetTimer()
331− leftButton.enabled = true
332− rightButton.enabled = true
333413
334− UIView.animateWithDuration(self.labelSlideDuration, animations: {
414+ leftButton.isEnabled = true
415+ rightButton.isEnabled = true
416+ label.isUserInteractionEnabled = true
417+
418+ UIView.animate(withDuration: self.labelSlideDuration, animations: {
335419 self.label.center = self.labelOriginalCenter
336420 self.rightButton.backgroundColor = self.buttonsBackgroundColor
337421 self.leftButton.backgroundColor = self.buttonsBackgroundColor
@@ -341,9 +425,9 @@ extension GMStepper {
341425
342426// MARK: Button Events
343427extension GMStepper {
344− func leftButtonTouchDown(button: UIButton) {
345− rightButton.enabled = false
346− label.userInteractionEnabled = false
428+ @objc func leftButtonTouchDown(button: UIButton) {
429+ rightButton.isEnabled = false
430+ label.isUserInteractionEnabled = false
347431 resetTimer()
348432
349433 if value == minimumValue {
@@ -355,9 +439,9 @@ extension GMStepper {
355439
356440 }
357441
358− func rightButtonTouchDown(button: UIButton) {
359− leftButton.enabled = false
360− label.userInteractionEnabled = false
442+ @objc func rightButtonTouchDown(button: UIButton) {
443+ leftButton.isEnabled = false
444+ label.isUserInteractionEnabled = false
361445 resetTimer()
362446
363447 if value == maximumValue {
@@ -368,9 +452,8 @@ extension GMStepper {
368452 }
369453 }
370454
371− func buttonTouchUp(button: UIButton) {
455+ @objc func buttonTouchUp(button: UIButton) {
372456 reset()
373− label.userInteractionEnabled = true
374457 }
375458}
376459
@@ -378,20 +461,20 @@ extension GMStepper {
378461extension GMStepper {
379462
380463 func animateSlideLeft() {
381− UIView.animateWithDuration(labelSlideDuration) {
464+ UIView.animate(withDuration: labelSlideDuration) {
382465 self.label.center.x -= self.labelSlideLength
383466 }
384467 }
385468
386469 func animateSlideRight() {
387− UIView.animateWithDuration(labelSlideDuration) {
470+ UIView.animate(withDuration: labelSlideDuration) {
388471 self.label.center.x += self.labelSlideLength
389472 }
390473 }
391474
392475 func animateToOriginalPosition() {
393476 if self.label.center != self.labelOriginalCenter {
394− UIView.animateWithDuration(labelSlideDuration) {
477+ UIView.animate(withDuration: labelSlideDuration) {
395478 self.label.center = self.labelOriginalCenter
396479 }
397480 }
@@ -399,14 +482,14 @@ extension GMStepper {
399482
400483 func animateLimitHitIfNeeded() {
401484 if value == minimumValue {
402− animateLimitHitForButton(leftButton)
485+ animateLimitHitForButton(button: leftButton)
403486 } else if value == maximumValue {
404− animateLimitHitForButton(rightButton)
487+ animateLimitHitForButton(button: rightButton)
405488 }
406489 }
407490
408491 func animateLimitHitForButton(button: UIButton){
409− UIView.animateWithDuration(limitHitAnimationDuration) {
492+ UIView.animate(withDuration: limitHitAnimationDuration) {
410493 button.backgroundColor = self.limitHitAnimationColor
411494 }
412495 }
@@ -414,7 +497,7 @@ extension GMStepper {
414497
415498// MARK: Timer
416499extension GMStepper {
417− func handleTimerFire(timer: NSTimer) {
500+ @objc func handleTimerFire(timer: Timer) {
418501 timerFireCount += 1
419502
420503 if timerFireCount % timerFireCountModulo == 0 {
@@ -423,7 +506,7 @@ extension GMStepper {
423506 }
424507
425508 func scheduleTimer() {
426− timer = NSTimer.scheduledTimerWithTimeInterval(timerInterval, target: self, selector: "handleTimerFire:", userInfo: nil, repeats: true)
509+ timer = Timer.scheduledTimer(timeInterval: timerInterval, target: self, selector: #selector(GMStepper.handleTimerFire), userInfo: nil, repeats: true)
427510 }
428511
429512 func resetTimer() {
@@ -435,4 +518,4 @@ extension GMStepper {
435518 }
436519
437520
438−}
521+}