on master help sign in
Add UIStepper's autorepeat for button taps
8f3ccfdGunay Mert Karadogan
GMStepper/GMStepper.swift+144−49
@@ -36,6 +36,9 @@ import UIKit
36 }
36 }
37 }
37 }
38
38
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+
39 /// Text on the left button. Be sure that it fits in the button. Defaults to "-".
42 /// Text on the left button. Be sure that it fits in the button. Defaults to "-".
40 @IBInspectable public var leftButtonText: String = "-" {
43 @IBInspectable public var leftButtonText: String = "-" {
41 didSet {
44 didSet {
@@ -121,10 +124,10 @@ import UIKit
121 private let labelSlideLength: CGFloat = 5
124 private let labelSlideLength: CGFloat = 5
122
125
123 /// Duration of the sliding animation
126 /// Duration of the sliding animation
124− private let labelSlideDuration: NSTimeInterval = 0.1
127+ private let labelSlideDuration = NSTimeInterval(0.1)
125
128
126 /// Duration of the flashing animation when the value hits the limit.
129 /// 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)
128
131
129 /// Color of the flashing animation on the buttons in case the value hit the limit.
132 /// Color of the flashing animation on the buttons in case the value hit the limit.
130 @IBInspectable public var limitHitAnimationColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1)
133 @IBInspectable public var limitHitAnimationColor: UIColor = UIColor(red:0.26, green:0.6, blue:0.87, alpha:1)
@@ -142,6 +145,40 @@ import UIKit
142 }
145 }
143 private var panState = LabelPanState.Stale
146 private var panState = LabelPanState.Stale
144
147
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+
145 required public init(coder aDecoder: NSCoder) {
182 required public init(coder aDecoder: NSCoder) {
146 super.init(coder: aDecoder)
183 super.init(coder: aDecoder)
147 setup()
184 setup()
@@ -157,7 +194,9 @@ import UIKit
157 leftButton.setTitleColor(buttonsTextColor, forState: .Normal)
194 leftButton.setTitleColor(buttonsTextColor, forState: .Normal)
158 leftButton.backgroundColor = buttonsBackgroundColor
195 leftButton.backgroundColor = buttonsBackgroundColor
159 leftButton.titleLabel?.font = buttonsFont
196 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)
161 addSubview(leftButton)
200 addSubview(leftButton)
162
201
163 rightButton.setTitle(rightButtonText, forState: .Normal)
202 rightButton.setTitle(rightButtonText, forState: .Normal)
@@ -165,6 +204,8 @@ import UIKit
165 rightButton.backgroundColor = buttonsBackgroundColor
204 rightButton.backgroundColor = buttonsBackgroundColor
166 rightButton.titleLabel?.font = buttonsFont
205 rightButton.titleLabel?.font = buttonsFont
167 rightButton.addTarget(self, action: "rightButtonTapped:", forControlEvents: .TouchDown)
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)
168 addSubview(rightButton)
209 addSubview(rightButton)
169
210
170 label.textAlignment = .Center
211 label.textAlignment = .Center
@@ -196,51 +237,7 @@ import UIKit
196 labelOriginalCenter = label.center
237 labelOriginalCenter = label.center
197 }
238 }
198
239
199− @objc private func leftButtonTapped(button: UIButton) {
200− if value == minimumValue {
201− animateLimitHitForButton(leftButton)
202− } else {
203− value -= 1
204− animateSlideLeft()
205− }
206− }
207
240
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− }
244
241
245 @objc private func handlePan(gesture: UIPanGestureRecognizer) {
242 @objc private func handlePan(gesture: UIPanGestureRecognizer) {
246 switch gesture.state {
243 switch gesture.state {
@@ -268,7 +265,7 @@ import UIKit
268 }
265 }
269
266
270 if value == maximumValue {
267 if value == maximumValue {
271− rightButton.backgroundColor = self.limitHitAnimationColor
268+ animateLimitHitForButton(rightButton)
272 }
269 }
273 } else if label.center.x == labelMinimumCenterX {
270 } else if label.center.x == labelMinimumCenterX {
274
271
@@ -278,7 +275,7 @@ import UIKit
278 }
275 }
279
276
280 if value == minimumValue {
277 if value == minimumValue {
281− leftButton.backgroundColor = self.limitHitAnimationColor
278+ animateLimitHitForButton(leftButton)
282 }
279 }
283 } else {
280 } else {
284 leftButton.backgroundColor = buttonsBackgroundColor
281 leftButton.backgroundColor = buttonsBackgroundColor
@@ -301,5 +298,103 @@ import UIKit
301 self.leftButton.backgroundColor = self.buttonsBackgroundColor
298 self.leftButton.backgroundColor = self.buttonsBackgroundColor
302 })
299 })
303 }
300 }
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+ }
304
368
305}
369}
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+}
GMStepperExample/GMStepperExample/Base.lproj/Main.storyboard+12−6
@@ -25,7 +25,10 @@
25 </constraints>
25 </constraints>
26 <userDefinedRuntimeAttributes>
26 <userDefinedRuntimeAttributes>
27 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
27 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
28− <integer key="value" value="1"/>
28+ <integer key="value" value="0"/>
29+ </userDefinedRuntimeAttribute>
30+ <userDefinedRuntimeAttribute type="number" keyPath="maximumValue">
31+ <integer key="value" value="100"/>
29 </userDefinedRuntimeAttribute>
32 </userDefinedRuntimeAttribute>
30 </userDefinedRuntimeAttributes>
33 </userDefinedRuntimeAttributes>
31 </view>
34 </view>
@@ -38,10 +41,10 @@
38 </constraints>
41 </constraints>
39 <userDefinedRuntimeAttributes>
42 <userDefinedRuntimeAttributes>
40 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
43 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
41− <integer key="value" value="7"/>
44+ <integer key="value" value="0"/>
42 </userDefinedRuntimeAttribute>
45 </userDefinedRuntimeAttribute>
43 <userDefinedRuntimeAttribute type="number" keyPath="maximumValue">
46 <userDefinedRuntimeAttribute type="number" keyPath="maximumValue">
44− <integer key="value" value="10"/>
47+ <integer key="value" value="100"/>
45 </userDefinedRuntimeAttribute>
48 </userDefinedRuntimeAttribute>
46 <userDefinedRuntimeAttribute type="color" keyPath="buttonsBackgroundColor">
49 <userDefinedRuntimeAttribute type="color" keyPath="buttonsBackgroundColor">
47 <color key="value" red="1" green="0.3591497541" blue="0.36842127060000002" alpha="1" colorSpace="calibratedRGB"/>
50 <color key="value" red="1" green="0.3591497541" blue="0.36842127060000002" alpha="1" colorSpace="calibratedRGB"/>
@@ -81,10 +84,10 @@
81 <color key="value" white="1" alpha="1" colorSpace="calibratedWhite"/>
84 <color key="value" white="1" alpha="1" colorSpace="calibratedWhite"/>
82 </userDefinedRuntimeAttribute>
85 </userDefinedRuntimeAttribute>
83 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
86 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
84− <integer key="value" value="9"/>
87+ <integer key="value" value="0"/>
85 </userDefinedRuntimeAttribute>
88 </userDefinedRuntimeAttribute>
86 <userDefinedRuntimeAttribute type="number" keyPath="maximumValue">
89 <userDefinedRuntimeAttribute type="number" keyPath="maximumValue">
87− <integer key="value" value="13"/>
90+ <integer key="value" value="100"/>
88 </userDefinedRuntimeAttribute>
91 </userDefinedRuntimeAttribute>
89 <userDefinedRuntimeAttribute type="color" keyPath="limitHitAnimationColor">
92 <userDefinedRuntimeAttribute type="color" keyPath="limitHitAnimationColor">
90 <color key="value" red="0.6588235294" green="0.85882352939999995" blue="0.6588235294" alpha="1" colorSpace="calibratedRGB"/>
93 <color key="value" red="0.6588235294" green="0.85882352939999995" blue="0.6588235294" alpha="1" colorSpace="calibratedRGB"/>
@@ -117,11 +120,14 @@
117 <userDefinedRuntimeAttribute type="string" keyPath="leftButtonText" value="😞"/>
120 <userDefinedRuntimeAttribute type="string" keyPath="leftButtonText" value="😞"/>
118 <userDefinedRuntimeAttribute type="string" keyPath="rightButtonText" value="☺️"/>
121 <userDefinedRuntimeAttribute type="string" keyPath="rightButtonText" value="☺️"/>
119 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
122 <userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
120− <integer key="value" value="5"/>
123+ <integer key="value" value="0"/>
121 </userDefinedRuntimeAttribute>
124 </userDefinedRuntimeAttribute>
122 <userDefinedRuntimeAttribute type="color" keyPath="limitHitAnimationColor">
125 <userDefinedRuntimeAttribute type="color" keyPath="limitHitAnimationColor">
123 <color key="value" red="0.98624199629999998" green="0.77968657019999998" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
126 <color key="value" red="0.98624199629999998" green="0.77968657019999998" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
124 </userDefinedRuntimeAttribute>
127 </userDefinedRuntimeAttribute>
128+ <userDefinedRuntimeAttribute type="number" keyPath="maximumValue">
129+ <integer key="value" value="100"/>
130+ </userDefinedRuntimeAttribute>
125 </userDefinedRuntimeAttributes>
131 </userDefinedRuntimeAttributes>
126 <variation key="default">
132 <variation key="default">
127 <mask key="constraints">
133 <mask key="constraints">