@@ -11,71 +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: Double = 0 {
14+ @objc @IBInspectable public var value: Double = 0 {
1515 didSet {
1616 value = min(maximumValue, max(minimumValue, value))
1717
1818 let isInteger = floor(value) == value
19−
20− if showIntegerIfDoubleIsInteger && isInteger {
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 {
2128 label.text = String(stringInterpolationSegment: Int(value))
2229 } else {
2330 label.text = String(stringInterpolationSegment: value)
2431 }
2532
2633 if oldValue != value {
27− sendActionsForControlEvents(.ValueChanged)
34+ sendActions(for: .valueChanged)
2835 }
2936 }
3037 }
3138
3239 /// Minimum value. Must be less than maximumValue. Defaults to 0.
33− @IBInspectable public var minimumValue: Double = 0 {
40+ @objc @IBInspectable public var minimumValue: Double = 0 {
3441 didSet {
3542 value = min(maximumValue, max(minimumValue, value))
3643 }
3744 }
3845
3946 /// Maximum value. Must be more than minimumValue. Defaults to 100.
40− @IBInspectable public var maximumValue: Double = 100 {
47+ @objc @IBInspectable public var maximumValue: Double = 100 {
4148 didSet {
4249 value = min(maximumValue, max(minimumValue, value))
4350 }
4451 }
4552
4653 /// Step/Increment value as in UIStepper. Defaults to 1.
47− @IBInspectable public var stepValue: Double = 1
54+ @objc @IBInspectable public var stepValue: Double = 1
4855
4956 /// The same as UIStepper's autorepeat. If true, holding on the buttons or keeping the pan gesture alters the value repeatedly. Defaults to true.
50− @IBInspectable public var autorepeat: Bool = true
57+ @objc @IBInspectable public var autorepeat: Bool = true
5158
52− @IBInspectable public var showIntegerIfDoubleIsInteger: Bool = true
59+ /// If the value is integer, it is shown without floating point.
60+ @objc @IBInspectable public var showIntegerIfDoubleIsInteger: Bool = true
5361
54− /// Text on the left button. Be sure that it fits in the button. Defaults to "-".
55− @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 = "−" {
5664 didSet {
57− leftButton.setTitle(leftButtonText, forState: .Normal)
65+ leftButton.setTitle(leftButtonText, for: .normal)
5866 }
5967 }
6068
6169 /// Text on the right button. Be sure that it fits in the button. Defaults to "+".
62− @IBInspectable public var rightButtonText: String = "+" {
70+ @objc @IBInspectable public var rightButtonText: String = "+" {
6371 didSet {
64− rightButton.setTitle(rightButtonText, forState: .Normal)
72+ rightButton.setTitle(rightButtonText, for: .normal)
6573 }
6674 }
6775
6876 /// Text color of the buttons. Defaults to white.
69− @IBInspectable public var buttonsTextColor: UIColor = UIColor.whiteColor() {
77+ @objc @IBInspectable public var buttonsTextColor: UIColor = UIColor.white {
7078 didSet {
7179 for button in [leftButton, rightButton] {
72− button.setTitleColor(buttonsTextColor, forState: .Normal)
80+ button.setTitleColor(buttonsTextColor, for: .normal)
7381 }
7482 }
7583 }
7684
7785 /// Background color of the buttons. Defaults to dark blue.
78− @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) {
7987 didSet {
8088 for button in [leftButton, rightButton] {
8189 button.backgroundColor = buttonsBackgroundColor
@@ -85,7 +93,7 @@ import UIKit
8593 }
8694
8795 /// Font of the buttons. Defaults to AvenirNext-Bold, 20.0 points in size.
88− public var buttonsFont = UIFont(name: "AvenirNext-Bold", size: 20.0)! {
96+ @objc public var buttonsFont = UIFont(name: "AvenirNext-Bold", size: 20.0)! {
8997 didSet {
9098 for button in [leftButton, rightButton] {
9199 button.titleLabel?.font = buttonsFont
@@ -94,36 +102,59 @@ import UIKit
94102 }
95103
96104 /// Text color of the middle label. Defaults to white.
97− @IBInspectable public var labelTextColor: UIColor = UIColor.whiteColor() {
105+ @objc @IBInspectable public var labelTextColor: UIColor = UIColor.white {
98106 didSet {
99107 label.textColor = labelTextColor
100108 }
101109 }
102110
103111 /// Text color of the middle label. Defaults to lighter blue.
104− @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) {
105113 didSet {
106114 label.backgroundColor = labelBackgroundColor
107115 }
108116 }
109117
110118 /// Font of the middle label. Defaults to AvenirNext-Bold, 25.0 points in size.
111− public var labelFont = UIFont(name: "AvenirNext-Bold", size: 25.0)! {
119+ @objc public var labelFont = UIFont(name: "AvenirNext-Bold", size: 25.0)! {
112120 didSet {
113121 label.font = labelFont
114122 }
115123 }
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+ }
116131
117132 /// Corner radius of the stepper's layer. Defaults to 4.0.
118− @IBInspectable public var cornerRadius: CGFloat = 4.0 {
133+ @objc @IBInspectable public var cornerRadius: CGFloat = 4.0 {
119134 didSet {
120135 layer.cornerRadius = cornerRadius
121136 clipsToBounds = true
122137 }
123138 }
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+ }
124155
125156 /// 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.
126− @IBInspectable public var labelWidthWeight: CGFloat = 0.5 {
157+ @objc @IBInspectable public var labelWidthWeight: CGFloat = 0.5 {
127158 didSet {
128159 labelWidthWeight = min(1, max(0, labelWidthWeight))
129160 setNeedsLayout()
@@ -131,7 +162,7 @@ import UIKit
131162 }
132163
133164 /// Color of the flashing animation on the buttons in case the value hit the limit.
134− @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)
135166
136167 /**
137168 Width of the sliding animation. When buttons clicked, the middle label does a slide animation towards to the clicked button. Defaults to 5.
@@ -139,38 +170,40 @@ import UIKit
139170 let labelSlideLength: CGFloat = 5
140171
141172 /// Duration of the sliding animation
142− let labelSlideDuration = NSTimeInterval(0.1)
173+ let labelSlideDuration = TimeInterval(0.1)
143174
144175 /// Duration of the animation when the value hits the limit.
145− let limitHitAnimationDuration = NSTimeInterval(0.1)
176+ let limitHitAnimationDuration = TimeInterval(0.1)
146177
147178 lazy var leftButton: UIButton = {
148179 let button = UIButton()
149− button.setTitle(self.leftButtonText, forState: .Normal)
150− button.setTitleColor(self.buttonsTextColor, forState: .Normal)
180+ button.setTitle(self.leftButtonText, for: .normal)
181+ button.setTitleColor(self.buttonsTextColor, for: .normal)
151182 button.backgroundColor = self.buttonsBackgroundColor
152183 button.titleLabel?.font = self.buttonsFont
153− button.addTarget(self, action: "leftButtonTouchDown:", forControlEvents: .TouchDown)
154− button.addTarget(self, action: "buttonTouchUp:", forControlEvents: UIControlEvents.TouchUpInside)
155− 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)
156188 return button
157189 }()
158190
159191 lazy var rightButton: UIButton = {
160192 let button = UIButton()
161− button.setTitle(self.rightButtonText, forState: .Normal)
162− button.setTitleColor(self.buttonsTextColor, forState: .Normal)
193+ button.setTitle(self.rightButtonText, for: .normal)
194+ button.setTitleColor(self.buttonsTextColor, for: .normal)
163195 button.backgroundColor = self.buttonsBackgroundColor
164196 button.titleLabel?.font = self.buttonsFont
165− button.addTarget(self, action: "rightButtonTouchDown:", forControlEvents: .TouchDown)
166− button.addTarget(self, action: "buttonTouchUp:", forControlEvents: UIControlEvents.TouchUpInside)
167− 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)
168201 return button
169202 }()
170203
171204 lazy var label: UILabel = {
172205 let label = UILabel()
173− label.textAlignment = .Center
206+ label.textAlignment = .center
174207 if self.showIntegerIfDoubleIsInteger && floor(self.value) == self.value {
175208 label.text = String(stringInterpolationSegment: Int(self.value))
176209 } else {
@@ -179,8 +212,10 @@ import UIKit
179212 label.textColor = self.labelTextColor
180213 label.backgroundColor = self.labelBackgroundColor
181214 label.font = self.labelFont
182− label.userInteractionEnabled = true
183− 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))
184219 panRecognizer.maximumNumberOfTouches = 1
185220 label.addGestureRecognizer(panRecognizer)
186221 return label
@@ -208,9 +243,33 @@ import UIKit
208243 }
209244 }
210245 }
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+ }
211270
212271 /// Timer used for autorepeat option
213− var timer: NSTimer?
272+ var timer: Timer?
214273
215274 /** When UIStepper reaches its top speed, it alters the value with a time interval of ~0.05 sec.
216275 The user pressing and holding on the stepper repeatedly:
@@ -218,7 +277,7 @@ import UIKit
218277 - For the next 1.5 sec, it changes the value every 0.1 sec.
219278 - Then, every 0.05 sec.
220279 */
221− let timerInterval = NSTimeInterval(0.05)
280+ let timerInterval = TimeInterval(0.05)
222281
223282 /// 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.
224283 var timerFireCount = 0
@@ -232,25 +291,12 @@ import UIKit
232291 }
233292 }
234293
235− /// Useful for logging the timer interval. You can call this in the timer handler to test the autorepeat option.
236− lazy var printTimerGaps: () -> () = {
237− var prevTime: CFAbsoluteTime?
238−
239− return { _ in
240− var now = CFAbsoluteTimeGetCurrent()
241− if let prevTime = prevTime {
242− println(now - prevTime)
243− }
244− prevTime = now
245− }
246− }()
247−
248− required public init(coder aDecoder: NSCoder) {
294+ @objc required public init?(coder aDecoder: NSCoder) {
249295 super.init(coder: aDecoder)
250296 setup()
251297 }
252298
253− public override init(frame: CGRect) {
299+ @objc public override init(frame: CGRect) {
254300 super.init(frame: frame)
255301 setup()
256302 }
@@ -263,6 +309,9 @@ import UIKit
263309 backgroundColor = buttonsBackgroundColor
264310 layer.cornerRadius = cornerRadius
265311 clipsToBounds = true
312+ labelOriginalCenter = label.center
313+
314+ NotificationCenter.default.addObserver(self, selector: #selector(GMStepper.reset), name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
266315 }
267316
268317 public override func layoutSubviews() {
@@ -285,21 +334,39 @@ import UIKit
285334 value -= stepValue
286335 }
287336 }
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+// }()
288355}
289356
290357// MARK: Pan Gesture
291358extension GMStepper {
292− func handlePan(gesture: UIPanGestureRecognizer) {
293− leftButton.enabled = false
294− rightButton.enabled = false
295−
359+ @objc func handlePan(gesture: UIPanGestureRecognizer) {
296360 switch gesture.state {
297− case .Changed:
298− var translation = gesture.translationInView(label)
299− 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)
300367
301− var slidingRight = gesture.velocityInView(label).x > 0
302− 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
303370
304371 // Move the label with pan
305372 if slidingRight {
@@ -332,22 +399,23 @@ extension GMStepper {
332399 self.rightButton.backgroundColor = self.buttonsBackgroundColor
333400 self.leftButton.backgroundColor = self.buttonsBackgroundColor
334401 }
335−
336− case .Ended:
402+ case .ended, .cancelled, .failed:
337403 reset()
338404 default:
339405 break
340406 }
341407 }
342408
343− func reset() {
409+ @objc func reset() {
344410 panState = .Stable
345411 stepperState = .Stable
346412 resetTimer()
347− leftButton.enabled = true
348− rightButton.enabled = true
349413
350− 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: {
351419 self.label.center = self.labelOriginalCenter
352420 self.rightButton.backgroundColor = self.buttonsBackgroundColor
353421 self.leftButton.backgroundColor = self.buttonsBackgroundColor
@@ -357,9 +425,9 @@ extension GMStepper {
357425
358426// MARK: Button Events
359427extension GMStepper {
360− func leftButtonTouchDown(button: UIButton) {
361− rightButton.enabled = false
362− label.userInteractionEnabled = false
428+ @objc func leftButtonTouchDown(button: UIButton) {
429+ rightButton.isEnabled = false
430+ label.isUserInteractionEnabled = false
363431 resetTimer()
364432
365433 if value == minimumValue {
@@ -371,9 +439,9 @@ extension GMStepper {
371439
372440 }
373441
374− func rightButtonTouchDown(button: UIButton) {
375− leftButton.enabled = false
376− label.userInteractionEnabled = false
442+ @objc func rightButtonTouchDown(button: UIButton) {
443+ leftButton.isEnabled = false
444+ label.isUserInteractionEnabled = false
377445 resetTimer()
378446
379447 if value == maximumValue {
@@ -384,9 +452,8 @@ extension GMStepper {
384452 }
385453 }
386454
387− func buttonTouchUp(button: UIButton) {
455+ @objc func buttonTouchUp(button: UIButton) {
388456 reset()
389− label.userInteractionEnabled = true
390457 }
391458}
392459
@@ -394,20 +461,20 @@ extension GMStepper {
394461extension GMStepper {
395462
396463 func animateSlideLeft() {
397− UIView.animateWithDuration(labelSlideDuration) {
464+ UIView.animate(withDuration: labelSlideDuration) {
398465 self.label.center.x -= self.labelSlideLength
399466 }
400467 }
401468
402469 func animateSlideRight() {
403− UIView.animateWithDuration(labelSlideDuration) {
470+ UIView.animate(withDuration: labelSlideDuration) {
404471 self.label.center.x += self.labelSlideLength
405472 }
406473 }
407474
408475 func animateToOriginalPosition() {
409476 if self.label.center != self.labelOriginalCenter {
410− UIView.animateWithDuration(labelSlideDuration) {
477+ UIView.animate(withDuration: labelSlideDuration) {
411478 self.label.center = self.labelOriginalCenter
412479 }
413480 }
@@ -415,14 +482,14 @@ extension GMStepper {
415482
416483 func animateLimitHitIfNeeded() {
417484 if value == minimumValue {
418− animateLimitHitForButton(leftButton)
485+ animateLimitHitForButton(button: leftButton)
419486 } else if value == maximumValue {
420− animateLimitHitForButton(rightButton)
487+ animateLimitHitForButton(button: rightButton)
421488 }
422489 }
423490
424491 func animateLimitHitForButton(button: UIButton){
425− UIView.animateWithDuration(limitHitAnimationDuration) {
492+ UIView.animate(withDuration: limitHitAnimationDuration) {
426493 button.backgroundColor = self.limitHitAnimationColor
427494 }
428495 }
@@ -430,7 +497,7 @@ extension GMStepper {
430497
431498// MARK: Timer
432499extension GMStepper {
433− func handleTimerFire(timer: NSTimer) {
500+ @objc func handleTimerFire(timer: Timer) {
434501 timerFireCount += 1
435502
436503 if timerFireCount % timerFireCountModulo == 0 {
@@ -439,7 +506,7 @@ extension GMStepper {
439506 }
440507
441508 func scheduleTimer() {
442− 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)
443510 }
444511
445512 func resetTimer() {
@@ -451,4 +518,4 @@ extension GMStepper {
451518 }
452519
453520
454−}
521+}