@@ -36,6 +36,9 @@ import UIKit
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.
40+ @IBInspectable public var autorepeat: Bool = true
41+
3942 /// Text on the left button. Be sure that it fits in the button. Defaults to "-".
4043 @IBInspectable public var leftButtonText: String = "-" {
4144 didSet {
@@ -121,10 +124,10 @@ import UIKit
121124 private let labelSlideLength: CGFloat = 5
122125
123126 /// Duration of the sliding animation
124− private let labelSlideDuration: NSTimeInterval = 0.1
127+ private let labelSlideDuration = NSTimeInterval(0.1)
125128
126129 /// Duration of the flashing animation when the value hits the limit.
127− private let limitHitAnimationDuration: NSTimeInterval = 0.1
130+ private let limitHitAnimationDuration = NSTimeInterval(0.1)
128131
129132 /// Color of the flashing animation on the buttons in case the value hit the limit.
130133 @IBInspectable public var limitHitAnimationColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1)
@@ -142,6 +145,40 @@ import UIKit
142145 }
143146 private var panState = LabelPanState.Stale
144147
148+ private var timer: NSTimer?
149+
150+ /** When UIStepper reaches its top speed, it alters the value with a time interval of ~0.05 sec.
151+ The user pressing and holding on the stepper repeatedly:
152+ - First 2.5 sec, the stepper changes the value every 0.5 sec.
153+ - For the next 1.5 sec, it changes the value every 0.1 sec.
154+ - Then, every 0.05 sec.
155+ */
156+ private let timerInterval = NSTimeInterval(0.05)
157+
158+ /// 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 {
161+ if timerFireCount > 80 {
162+ return 1 // 0.05 sec * 1 = 0.05 sec
163+ } else if timerFireCount > 50 {
164+ return 2 // 0.05 sec * 2 = 0.1 sec
165+ } else {
166+ return 10 // 0.05 sec * 10 = 0.5 sec
167+ }
168+ }
169+
170+ lazy var printTimerGaps: () -> () = {
171+ var prevTime: CFAbsoluteTime?
172+
173+ return { _ in
174+ var now = CFAbsoluteTimeGetCurrent()
175+ if let prevTime = prevTime {
176+ println(now - prevTime)
177+ }
178+ prevTime = now
179+ }
180+ }()
181+
145182 required public init(coder aDecoder: NSCoder) {
146183 super.init(coder: aDecoder)
147184 setup()
@@ -157,7 +194,9 @@ import UIKit
157194 leftButton.setTitleColor(buttonsTextColor, forState: .Normal)
158195 leftButton.backgroundColor = buttonsBackgroundColor
159196 leftButton.titleLabel?.font = buttonsFont
160− leftButton.addTarget(self, action: "leftButtonTapped:", forControlEvents: .TouchDown)
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)
161200 addSubview(leftButton)
162201
163202 rightButton.setTitle(rightButtonText, forState: .Normal)
@@ -165,6 +204,8 @@ import UIKit
165204 rightButton.backgroundColor = buttonsBackgroundColor
166205 rightButton.titleLabel?.font = buttonsFont
167206 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)
168209 addSubview(rightButton)
169210
170211 label.textAlignment = .Center
@@ -196,51 +237,7 @@ import UIKit
196237 labelOriginalCenter = label.center
197238 }
198239
199− @objc private func leftButtonTapped(button: UIButton) {
200− if value == minimumValue {
201− animateLimitHitForButton(leftButton)
202− } else {
203− value -= 1
204− animateSlideLeft()
205− }
206− }
207240
208− @objc private func rightButtonTapped(button: UIButton) {
209− if value == maximumValue {
210− animateLimitHitForButton(rightButton)
211− } else {
212− value += 1
213− animateSlideRight()
214− }
215− }
216−
217− private func animateSlideLeft() {
218− UIView.animateWithDuration(labelSlideDuration, animations: {
219− self.label.center.x -= self.labelSlideLength
220− }, completion: { _ in
221− UIView.animateWithDuration(self.labelSlideDuration, animations: {
222− self.label.center.x += self.labelSlideLength
223− })
224− })
225− }
226−
227− private func animateSlideRight() {
228− UIView.animateWithDuration(labelSlideDuration, animations: {
229− self.label.center.x += self.labelSlideLength
230− }, completion: { _ in
231− UIView.animateWithDuration(self.labelSlideDuration, animations: {
232− self.label.center.x -= self.labelSlideLength
233− })
234− })
235− }
236−
237− private func animateLimitHitForButton(button: UIButton){
238− UIView.animateWithDuration(limitHitAnimationDuration, animations: {
239− button.backgroundColor = self.limitHitAnimationColor
240− }, completion: { _ in
241− button.backgroundColor = self.buttonsBackgroundColor
242− })
243− }
244241
245242 @objc private func handlePan(gesture: UIPanGestureRecognizer) {
246243 switch gesture.state {
@@ -268,7 +265,7 @@ import UIKit
268265 }
269266
270267 if value == maximumValue {
271− rightButton.backgroundColor = self.limitHitAnimationColor
268+ animateLimitHitForButton(rightButton)
272269 }
273270 } else if label.center.x == labelMinimumCenterX {
274271
@@ -278,7 +275,7 @@ import UIKit
278275 }
279276
280277 if value == minimumValue {
281− leftButton.backgroundColor = self.limitHitAnimationColor
278+ animateLimitHitForButton(leftButton)
282279 }
283280 } else {
284281 leftButton.backgroundColor = buttonsBackgroundColor
@@ -301,5 +298,103 @@ import UIKit
301298 self.leftButton.backgroundColor = self.buttonsBackgroundColor
302299 })
303300 }
301+}
302+
303+// MARK: Button Events
304+extension GMStepper {
305+ @objc private func leftButtonTouchDown(button: UIButton) {
306+ if value == minimumValue {
307+ animateLimitHitForButton(leftButton)
308+ } else {
309+ value -= 1
310+ if autorepeat {
311+ scheduleTimerWithUserInfo(leftButton)
312+ }
313+ animateSlideLeft()
314+ }
315+ }
316+
317+ @objc private func leftButtonTouchUp(button: UIButton) {
318+ reset()
319+ stopTimer()
320+ }
321+
322+ @objc private func rightButtonTapped(button: UIButton) {
323+ if value == maximumValue {
324+ animateLimitHitForButton(rightButton)
325+ } else {
326+ value += 1
327+ if autorepeat {
328+ scheduleTimerWithUserInfo(rightButton)
329+ }
330+ animateSlideRight()
331+ }
332+ }
333+
334+ @objc private func rightButtonTouchUp(button: UIButton) {
335+ reset()
336+ stopTimer()
337+ }
338+}
339+
340+// MARK: Animations
341+extension GMStepper {
342+
343+ private func animateSlideLeft() {
344+ UIView.animateWithDuration(labelSlideDuration) {
345+ self.label.center.x -= self.labelSlideLength
346+ }
347+ }
348+
349+ private func animateSlideRight() {
350+ UIView.animateWithDuration(labelSlideDuration) {
351+ self.label.center.x += self.labelSlideLength
352+ }
353+ }
354+
355+ private func animateToOriginalPosition() {
356+ if self.label.center != self.labelOriginalCenter {
357+ UIView.animateWithDuration(labelSlideDuration) {
358+ self.label.center = self.labelOriginalCenter
359+ }
360+ }
361+ }
362+
363+ private func animateLimitHitForButton(button: UIButton){
364+ UIView.animateWithDuration(limitHitAnimationDuration) {
365+ button.backgroundColor = self.limitHitAnimationColor
366+ }
367+ }
304368
305369}
370+
371+extension GMStepper {
372+ func handleTimerFire(timer: NSTimer) {
373+ timerFireCount += 1
374+ if timerFireCount % timerFireCountModulo == 0 {
375+
376+ if let button = timer.userInfo as? UIButton {
377+ if button == leftButton {
378+ value -= 1
379+ } else {
380+ value += 1
381+ }
382+ }
383+// printTimerGaps()
384+ }
385+ }
386+
387+ func scheduleTimerWithUserInfo(userInfo: AnyObject?) {
388+ timer = NSTimer.scheduledTimerWithTimeInterval(timerInterval, target: self, selector: "handleTimerFire:", userInfo: userInfo, repeats: true)
389+ }
390+
391+ func stopTimer() {
392+ if let timer = timer {
393+ timer.invalidate()
394+ self.timer = nil
395+ timerFireCount = 0
396+ }
397+ }
398+
399+
400+}