on master help sign in
Refactoring and bug fixes

- Keep a stepper state - Disable button or buttons in state of increasing or decreasing

commit9619704a90b78e8f332dc69cbb0ce5c0e6e5c057 authorGunay Mert Karadogan <mertkaradogan@gmail.com> dateFri 10 Jul 2015, 22:54 (11 years ago) parent8789187
1 file changed+136−100
GMStepper/GMStepper.swift+136−100
GMStepper/GMStepper.swift+136−100
@@ -11,7 +11,7 @@ import UIKit
1111@IBDesignable public class GMStepper: UIControl {
1212
1313 /// Current value of the stepper. Defaults to 0.
14− public var value = 0 {
14+ @IBInspectable public var value = 0 {
1515 didSet {
1616 value = min(maximumValue, max(minimumValue, value))
1717 label.text = String(value)
@@ -25,18 +25,18 @@ import UIKit
2525 /// Minimum value. Must be less than maximumValue. Defaults to 0.
2626 @IBInspectable public var minimumValue: Int = 0 {
2727 didSet {
28− value = 0
28+ value = min(maximumValue, max(minimumValue, value))
2929 }
3030 }
3131
3232 /// Maximum value. Must be more than minimumValue. Defaults to 10.
3333 @IBInspectable public var maximumValue: Int = 10 {
3434 didSet {
35− value = 0
35+ value = min(maximumValue, max(minimumValue, value))
3636 }
3737 }
3838
39− /// The same as UIStepper's autorepeat. If true, holding on the buttons or the pan gesture repeatedly alters value. Defaults to true.
39+ /// The same as UIStepper's autorepeat. If true, holding on the buttons or keeping the pan gesture alters the value repeatedly. Defaults to true.
4040 @IBInspectable public var autorepeat: Bool = true
4141
4242 /// Text on the left button. Be sure that it fits in the button. Defaults to "-".
@@ -118,34 +118,83 @@ import UIKit
118118 }
119119 }
120120
121+ /// 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)
123+
121124 /**
122125 Width of the sliding animation. When buttons clicked, the middle label does a slide animation towards to the clicked button. Defaults to 5.
123126 */
124− private let labelSlideLength: CGFloat = 5
127+ let labelSlideLength: CGFloat = 5
125128
126129 /// Duration of the sliding animation
127− private let labelSlideDuration = NSTimeInterval(0.1)
130+ let labelSlideDuration = NSTimeInterval(0.1)
131+
132+ /// Duration of the animation when the value hits the limit.
133+ let limitHitAnimationDuration = NSTimeInterval(0.1)
134+
135+ lazy var leftButton: UIButton = {
136+ let button = UIButton()
137+ button.setTitle(self.leftButtonText, forState: .Normal)
138+ button.setTitleColor(self.buttonsTextColor, forState: .Normal)
139+ button.backgroundColor = self.buttonsBackgroundColor
140+ 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)
144+ return button
145+ }()
128146
129− /// Duration of the flashing animation when the value hits the limit.
130− private let limitHitAnimationDuration = NSTimeInterval(0.1)
147+ lazy var rightButton: UIButton = {
148+ let button = UIButton()
149+ button.setTitle(self.rightButtonText, forState: .Normal)
150+ button.setTitleColor(self.buttonsTextColor, forState: .Normal)
151+ button.backgroundColor = self.buttonsBackgroundColor
152+ 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)
156+ return button
157+ }()
131158
132− /// Color of the flashing animation on the buttons in case the value hit the limit.
133− @IBInspectable public var limitHitAnimationColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1)
159+ lazy var label: UILabel = {
160+ let label = UILabel()
161+ label.textAlignment = .Center
162+ label.text = String(self.value)
163+ label.textColor = self.labelTextColor
164+ label.backgroundColor = self.labelBackgroundColor
165+ label.font = self.labelFont
166+ label.userInteractionEnabled = true
167+ let panRecognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
168+ panRecognizer.maximumNumberOfTouches = 1
169+ label.addGestureRecognizer(panRecognizer)
170+ return label
171+ }()
134172
135− private let leftButton = UIButton()
136− private let rightButton = UIButton()
137− private let label = UILabel()
173+ var labelOriginalCenter: CGPoint!
174+ var labelMaximumCenterX: CGFloat!
175+ var labelMinimumCenterX: CGFloat!
138176
139− private var labelOriginalCenter: CGPoint!
140− private var labelMaximumCenterX: CGFloat!
141− private var labelMinimumCenterX: CGFloat!
177+ enum LabelPanState {
178+ case Stable, HitRightEdge, HitLeftEdge
179+ }
180+ var panState = LabelPanState.Stable
142181
143− private enum LabelPanState {
144− case Stale, HitRightEdge, HitLeftEdge
182+ enum StepperState {
183+ case Stable, ShouldIncrease, ShouldDecrease
184+ }
185+ var stepperState = StepperState.Stable {
186+ didSet {
187+ if stepperState != .Stable {
188+ updateValue()
189+ if autorepeat {
190+ scheduleTimer()
191+ }
192+ }
193+ }
145194 }
146− private var panState = LabelPanState.Stale
147195
148− private var timer: NSTimer?
196+ /// Timer used for autorepeat option
197+ var timer: NSTimer?
149198
150199 /** When UIStepper reaches its top speed, it alters the value with a time interval of ~0.05 sec.
151200 The user pressing and holding on the stepper repeatedly:
@@ -153,11 +202,11 @@ import UIKit
153202 - For the next 1.5 sec, it changes the value every 0.1 sec.
154203 - Then, every 0.05 sec.
155204 */
156− private let timerInterval = NSTimeInterval(0.05)
205+ let timerInterval = NSTimeInterval(0.05)
157206
158207 /// 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.
159− private var timerFireCount = 0
160− private var timerFireCountModulo: Int {
208+ var timerFireCount = 0
209+ var timerFireCountModulo: Int {
161210 if timerFireCount > 80 {
162211 return 1 // 0.05 sec * 1 = 0.05 sec
163212 } else if timerFireCount > 50 {
@@ -167,6 +216,7 @@ import UIKit
167216 }
168217 }
169218
219+ /// Useful for logging the timer interval. You can call this in the timer handler to test the autorepeat option.
170220 lazy var printTimerGaps: () -> () = {
171221 var prevTime: CFAbsoluteTime?
172222
@@ -189,39 +239,14 @@ import UIKit
189239 setup()
190240 }
191241
192− private func setup() {
193− leftButton.setTitle(leftButtonText, forState: .Normal)
194− leftButton.setTitleColor(buttonsTextColor, forState: .Normal)
195− leftButton.backgroundColor = buttonsBackgroundColor
196− leftButton.titleLabel?.font = buttonsFont
197− leftButton.addTarget(self, action: "leftButtonTouchDown:", forControlEvents: .TouchDown)
198− leftButton.addTarget(self, action: "leftButtonTouchUp:", forControlEvents: UIControlEvents.TouchUpInside)
199− leftButton.addTarget(self, action: "leftButtonTouchUp:", forControlEvents: UIControlEvents.TouchUpOutside)
242+ func setup() {
200243 addSubview(leftButton)
201−
202− rightButton.setTitle(rightButtonText, forState: .Normal)
203− rightButton.setTitleColor(buttonsTextColor, forState: .Normal)
204− rightButton.backgroundColor = buttonsBackgroundColor
205− rightButton.titleLabel?.font = buttonsFont
206− rightButton.addTarget(self, action: "rightButtonTapped:", forControlEvents: .TouchDown)
207− rightButton.addTarget(self, action: "rightButtonTouchUp:", forControlEvents: UIControlEvents.TouchUpInside)
208− rightButton.addTarget(self, action: "rightButtonTouchUp:", forControlEvents: UIControlEvents.TouchUpOutside)
209244 addSubview(rightButton)
210−
211− label.textAlignment = .Center
212− label.text = String(value)
213− label.textColor = labelTextColor
214− label.backgroundColor = labelBackgroundColor
215− label.font = labelFont
216245 addSubview(label)
217246
218247 backgroundColor = buttonsBackgroundColor
219248 layer.cornerRadius = cornerRadius
220249 clipsToBounds = true
221−
222− let panRecognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
223− label.userInteractionEnabled = true
224− label.addGestureRecognizer(panRecognizer)
225250 }
226251
227252 public override func layoutSubviews() {
@@ -237,9 +262,21 @@ import UIKit
237262 labelOriginalCenter = label.center
238263 }
239264
265+ func updateValue() {
266+ if stepperState == .ShouldIncrease {
267+ value += 1
268+ } else if stepperState == .ShouldDecrease {
269+ value -= 1
270+ }
271+ }
272+}
240273
274+// MARK: Pan Gesture
275+extension GMStepper {
276+ func handlePan(gesture: UIPanGestureRecognizer) {
277+ leftButton.enabled = false
278+ rightButton.enabled = false
241279
242− @objc private func handlePan(gesture: UIPanGestureRecognizer) {
243280 switch gesture.state {
244281 case .Changed:
245282 var translation = gesture.translationInView(label)
@@ -257,30 +294,27 @@ import UIKit
257294
258295 // When the label hits the edges, increase/decrease value and change button backgrounds
259296 if label.center.x == labelMaximumCenterX {
260−
261− // Increase only once for each pan gesture
297+ // If not hit the right edge before, increase the value and start the timer. If already hit the edge, do nothing. Timer will handle it.
262298 if panState != .HitRightEdge {
263− value += 1
264− scheduleTimerWithUserInfo(rightButton)
299+ stepperState = .ShouldIncrease
265300 panState = .HitRightEdge
266301 }
267−
268− if value == maximumValue {
269− animateLimitHitForButton(rightButton)
270− }
302+
303+ animateLimitHitIfNeeded()
271304 } else if label.center.x == labelMinimumCenterX {
272−
273305 if panState != .HitLeftEdge {
274− value -= 1
275− scheduleTimerWithUserInfo(leftButton)
306+ stepperState = .ShouldDecrease
276307 panState = .HitLeftEdge
277308 }
278309
279− if value == minimumValue {
280− animateLimitHitForButton(leftButton)
281− }
310+ animateLimitHitIfNeeded()
282311 } else {
283− reset()
312+ panState = .Stable
313+ stepperState = .Stable
314+ resetTimer()
315+
316+ self.rightButton.backgroundColor = self.buttonsBackgroundColor
317+ self.leftButton.backgroundColor = self.buttonsBackgroundColor
284318 }
285319
286320 case .Ended:
@@ -290,9 +324,12 @@ import UIKit
290324 }
291325 }
292326
293− private func reset() {
294− panState = .Stale
327+ func reset() {
328+ panState = .Stable
329+ stepperState = .Stable
295330 resetTimer()
331+ leftButton.enabled = true
332+ rightButton.enabled = true
296333
297334 UIView.animateWithDuration(self.labelSlideDuration, animations: {
298335 self.label.center = self.labelOriginalCenter
@@ -304,57 +341,55 @@ import UIKit
304341
305342// MARK: Button Events
306343extension GMStepper {
307− @objc private func leftButtonTouchDown(button: UIButton) {
344+ func leftButtonTouchDown(button: UIButton) {
345+ rightButton.enabled = false
346+ label.userInteractionEnabled = false
347+ resetTimer()
348+
308349 if value == minimumValue {
309− animateLimitHitForButton(leftButton)
350+ animateLimitHitIfNeeded()
310351 } else {
311− value -= 1
312− if autorepeat {
313− scheduleTimerWithUserInfo(leftButton)
314− }
352+ stepperState = .ShouldDecrease
315353 animateSlideLeft()
316354 }
317− }
318355
319− @objc private func leftButtonTouchUp(button: UIButton) {
320− reset()
321− stopTimer()
322356 }
323357
324− @objc private func rightButtonTapped(button: UIButton) {
358+ func rightButtonTouchDown(button: UIButton) {
359+ leftButton.enabled = false
360+ label.userInteractionEnabled = false
361+ resetTimer()
362+
325363 if value == maximumValue {
326− animateLimitHitForButton(rightButton)
364+ animateLimitHitIfNeeded()
327365 } else {
328− value += 1
329− if autorepeat {
330− scheduleTimerWithUserInfo(rightButton)
331− }
366+ stepperState = .ShouldIncrease
332367 animateSlideRight()
333368 }
334369 }
335370
336− @objc private func rightButtonTouchUp(button: UIButton) {
371+ func buttonTouchUp(button: UIButton) {
337372 reset()
338− stopTimer()
373+ label.userInteractionEnabled = true
339374 }
340375}
341376
342377// MARK: Animations
343378extension GMStepper {
344379
345− private func animateSlideLeft() {
380+ func animateSlideLeft() {
346381 UIView.animateWithDuration(labelSlideDuration) {
347382 self.label.center.x -= self.labelSlideLength
348383 }
349384 }
350385
351− private func animateSlideRight() {
386+ func animateSlideRight() {
352387 UIView.animateWithDuration(labelSlideDuration) {
353388 self.label.center.x += self.labelSlideLength
354389 }
355390 }
356391
357− private func animateToOriginalPosition() {
392+ func animateToOriginalPosition() {
358393 if self.label.center != self.labelOriginalCenter {
359394 UIView.animateWithDuration(labelSlideDuration) {
360395 self.label.center = self.labelOriginalCenter
@@ -362,32 +397,33 @@ extension GMStepper {
362397 }
363398 }
364399
365− private func animateLimitHitForButton(button: UIButton){
400+ func animateLimitHitIfNeeded() {
401+ if value == minimumValue {
402+ animateLimitHitForButton(leftButton)
403+ } else if value == maximumValue {
404+ animateLimitHitForButton(rightButton)
405+ }
406+ }
407+
408+ func animateLimitHitForButton(button: UIButton){
366409 UIView.animateWithDuration(limitHitAnimationDuration) {
367410 button.backgroundColor = self.limitHitAnimationColor
368411 }
369412 }
370−
371413}
372414
373415// MARK: Timer
374416extension GMStepper {
375417 func handleTimerFire(timer: NSTimer) {
376418 timerFireCount += 1
377− if timerFireCount % timerFireCountModulo == 0 {
378419
379− if let button = timer.userInfo as? UIButton {
380− if button == leftButton {
381− value -= 1
382− } else {
383− value += 1
384− }
385− }
420+ if timerFireCount % timerFireCountModulo == 0 {
421+ updateValue()
386422 }
387423 }
388424
389− func scheduleTimerWithUserInfo(userInfo: AnyObject?) {
390− timer = NSTimer.scheduledTimerWithTimeInterval(timerInterval, target: self, selector: "handleTimerFire:", userInfo: userInfo, repeats: true)
425+ func scheduleTimer() {
426+ timer = NSTimer.scheduledTimerWithTimeInterval(timerInterval, target: self, selector: "handleTimerFire:", userInfo: nil, repeats: true)
391427 }
392428
393429 func resetTimer() {