Stop dispatch_after - swift

I use an animation for specify a tip to help the interaction with delay using these:
let delay = 1.8 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
//call the method which have the steps after delay.
self.rain.alpha = 0
UIView.animateWithDuration(5, animations: {
self.rain.alpha = 1
})
self.tip.startAnimating()
}
But, I need to stop this delay process if, before animation start, user touch the screen.

iOS 8 and OS X Yosemite introduced dispatch_block_cancel that allow you to cancel them before they start executing
You declare one variable in class as follows:
var block: dispatch_block_t?
Init block variable and provide it in dispatch_after:
block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
print("I executed")
}
let time: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(5 * NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue(), block!)
After that you can cancel it as follows:
dispatch_block_cancel(block!)

Swift 3.0 Example DispatchQueue cancel or stop
var dispatchQueue: DispatchQueue?
var dispatchWorkItem: DispatchWorkItem?
func someOnClickButtonStart() {
self.dispatchQueue = DispatchQueue.global(qos: .background) // create queue
self.dispatchWorkItem = DispatchWorkItem { // create work item
// async code goes here
}
if self.dispatchWorkItem != nil {
self.dispatchQueue?.asyncAfter(
deadline: .now() + .seconds(1),
execute: self.dispatchWorkItem!
) // schedule work item
}
}
func someOnClickButtonCancel() {
if let dispatchWorkItem = self.dispatchWorkItem {
dispatchWorkItem.cancel() // cancel work item
}
}

Here's a general solution I wrote to cancel a dispatch_after in Swift:
typealias cancellable_closure = (() -> ())?
func dispatch_after(#seconds:Double, queue: dispatch_queue_t = dispatch_get_main_queue(), closure:()->()) -> cancellable_closure {
var cancelled = false
let cancel_closure: cancellable_closure = {
cancelled = true
}
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))), queue, {
if !cancelled {
closure()
}
}
)
return cancel_closure
}
func cancel_dispatch_after(cancel_closure: cancellable_closure) {
cancel_closure?()
}
Usage:
let doSomethingLater = dispatch_after(seconds: 3.0) {
something()
}
....
if shouldCancelForSomeReason {
cancel_dispatch_after(doSomethingLater)
}
By default it runs on the main queue, but you can pass in a parameter for it to run on another queue:
let doSomethingLater = dispatch_after(seconds: 3.0, queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
something()
}

Just sharing, in Swift 4.x, I do this:
var block: DispatchWorkItem?
self.block = DispatchWorkItem { self.go(self) }
// execute task in 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: self.block!)
and then to cancel the block, self.block?.cancel()
Try this sample project:
import UIKit
class ViewController: UIViewController {
var block: DispatchWorkItem?
#IBAction func go(_ sender: Any) {
self.block?.cancel()
let vc2 = VC2()
self.navigationController?.pushViewController(vc2, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.block = DispatchWorkItem { self.go(self) }
// execute task in 2 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3), execute: self.block!)
}
}
class VC2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .green
}
}

You can create a boolean variable shouldCancelAnimation and test it inside the dispatch_after block to prevent the execution of your animation.
var shouldCancelAnimation = false // property of class
func runAnimation()
{
let delay = 1.8 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
if !self.shouldCancelAnimation
{
self.rain.alpha = 0
UIView.animateWithDuration(5, animations: {
self.rain.alpha = 1
})
self.tip.startAnimating()
}
self.shouldCancelAnimation = false
}
}
func viewWasTouched() // This could be touches began or taprecognizer event
{
shouldCancelAnimation = true
}

Related

Protocol reusable Spinner one liner progressview

This is my code at the moment I instead of using progressView as
self.progressView = showProgressView()
I want to use it as self.showProgress() as a one liner
this is my current protocol
protocol Loadable {
var progressView: ProgressView? { get }
func showProgressView() -> ProgressView
func hideProgressView()
}
extension Loadable where Self: UIViewController {
func showProgressView() -> ProgressView {
let pv = ProgressView(frame: CGRect.zero)
view.addSubview(pv)
pv.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(pv.setConstraints(boundsOf: view))
UIView.animate(withDuration: 0.3) {
pv.alpha = 1.0
}
return pv
}
func hideProgressView() {
guard let progressView = progressView else { return }
UIView.animate(withDuration: 0.6) {
progressView.alpha = 0.0
} completion: { finished in
progressView.removeFromSuperview()
}
}
}```
//this is my current usage on the code
#objc private func loadData() {
print("Calling API")
self.progressView = showProgressView()
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0){
self.civilizationData()
self.hideProgressView()
}
}
//tried using this method
protocol Loadable {
var progressView: ProgressView? { get }
func showProgressView()
func hideProgressView()
}
extension Loadable where Self: UIViewController {
func showProgressView() {
let progressView = ProgressView(frame: CGRect.zero)
view.addSubview(progressView)
progressView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(progressView.setConstraints(boundsOf: view))
UIView.animate(withDuration: 0.3) {
progressView.alpha = 1.0
}
}
//but the added subview of my progressview doesn't disappear
protocol Loadable {
var progressView: ProgressView? { get }
func showProgressView()
func hideProgressView()
}
extension Loadable where Self: UIViewController {
func showProgressView() {
let progressView = ProgressView(frame: CGRect.zero)
view.addSubview(progressView)
progressView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(progressView.setConstraints(boundsOf: view))
UIView.animate(withDuration: 0.3) {
progressView.alpha = 1.0
}
}

Stop and restart a timer

I want to stop this timer and then restart it from where I stopped it.
secondsTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(addSeconds), userInfo: nil, repeats: true)
Below, it was suggested I shouldn't increment a timer in my timer handler. Why not?
For example, using GCD timer:
func countSeconds() {
secondsTimer = DispatchSource.makeTimerSource(queue: .main)
secondsTimer?.schedule(deadline: .now(), repeating: 1.0)
secondsTimer?.setEventHandler { [weak self] in
self?.addSeconds()
}
}
#objc func addSeconds() {
seconds += 1
}
func startGame() {
secondsTimer?.resume()
}
We don't pause/resume Timer instances. We stop them with invalidate(). And when you want to restart it, just create new timer.
Please refer to the Timer documentation, also available right in Xcode.
Note that you can suspend and resume GCD timers, DispatchSourceTimer.
var timer: DispatchSourceTimer? // note, unlike `Timer`, we have to maintain strong reference to GCD timer sources
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 1.0)
timer?.setEventHandler { [weak self] in // assuming you're referencing `self` in here, use `weak` to avoid strong reference cycles
// do something
}
// note, timer is not yet started; you have to call `timer?.resume()`
}
func startTimer() {
timer?.resume()
}
func pauseTiemr() {
timer?.suspend()
}
func stopTimer() {
timer?.cancel()
timer = nil
}
Please note, I am not suggesting that if you want suspend and resume that you should use GCD DispatchSourceTimer. Calling invalidate and recreating Timer as needed is simple enough, so just do that. I only provide this GCD information for the sake of completeness.
By the way, as a general principle, never "increment" some counter in your timer handler. That's a common mistake. Timers are not guaranteed to fire every time or with exact precision. Always save some reference time at the start, and then in your event handler, calculate differences between the current time and the start time. For example, extending my GCD timer example:
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 0.1)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second, .nanosecond]
formatter.zeroFormattingBehavior = .pad
timer?.setEventHandler { [weak self] in
guard let start = self?.start else { return }
let elapsed = (self?.totalElapsed ?? 0) + CACurrentMediaTime() - start
self?.label.text = formatter.string(from: elapsed)
}
}
var start: CFTimeInterval? // if nil, timer not running
var totalElapsed: CFTimeInterval?
#objc func didTapButton(_ button: UIButton) {
if start == nil {
startTimer()
} else {
pauseTimer()
}
}
private func startTimer() {
start = CACurrentMediaTime()
timer?.resume()
}
private func pauseTimer() {
timer?.suspend()
totalElapsed = (totalElapsed ?? 0) + (CACurrentMediaTime() - start!)
start = nil
}
I do it with this code:
var timer: Timer?
func startTimer() {
timer = .scheduledTimer(withTimeInterval: 4, repeats: false, block: { _ in
// Do whatever
})
}
func resetTimer() {
timer?.invalidate()
startTimer()
}
You can start, stop and reset timer in swift4
class ViewController: UIViewController {
var counter = 0
var timer = Timer()
var totalSecond = 20
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func start_btn(_ sender: Any) {
timer.invalidate() // just in case this button is tapped multiple times
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
#IBAction func stop_btn(_ sender: Any) {
do {
self.timer.invalidate()
}
func timeFormatted(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
return String(format: "0:%02d", seconds)
}
}
#IBAction func reset_btn(_ sender: Any) {
timer.invalidate()
//timerAction()
counter = 0
label1.text = "\(counter)"
}
#objc func timerAction()
{
counter += 1
label1.text = "\(counter)"
}
}
You can declare the Timer as 'weak var' instead of just 'var' like:
weak var timer: Timer?
Now you can pause your timer with:
timer?.invalidate()
To resume:
timer?.fire()

Swift2: TKSubmitTransitionButton. How do i stop a button from transition/ animating when user login/ signup are incorrect

I have tried a number of times and the best i get is there would be an animation which never cancels or stop regardless of the command i use.
After following #Mattias example, i updated my code and looks something like this:
// DESIGN ANIMATION... TKTRANSITIONSUBMITBUTTON
#IBOutlet weak var btnFromNib: TKTransitionSubmitButton!
#IBAction func onTapButton(sender: AnyObject) {
btnFromNib.startLoadingAnimation()
if let email = self.emailField.text where email != "", let password = self.passwordField.text where password != "" {
DataService.ds.REF_BASE.authUser(email, password: password, withCompletionBlock: { error, authData in
if error != nil {
self.btnFromNib.returnToOriginalState()
if error.code == STATUS_ACCOUNT_NONEXIST {
self.showErrorAlert("This User does not exist", msg: "Please Sign Up")
} else {
}
} else {
self.btnFromNib.startFinishAnimation(1, completion: {
let myTabbarController = self.storyboard?.instantiateViewControllerWithIdentifier("myTabbarController") as! UITabBarController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = myTabbarController
myTabbarController.transitioningDelegate = self
})
}
})
}
}
The button yet keeps spinning / animating without stopping. After checking the custom animation class the function inherits from :
public func startLoadingAnimation() {
self.cachedTitle = titleForState(.Normal)
self.setTitle("", forState: .Normal)
self.shrink()
NSTimer.schedule(delay: shrinkDuration - 0.25) { timer in
self.spiner.animation()
}
}
public func startFinishAnimation(delay: NSTimeInterval, completion:(()->())?) {
NSTimer.schedule(delay: delay) { timer in
self.didEndFinishAnimation = completion
self.expand()
self.spiner.stopAnimation()
}
}
public func animate(duration: NSTimeInterval, completion:(()->())?) {
startLoadingAnimation()
startFinishAnimation(duration, completion: completion)
}
public override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
let a = anim as! CABasicAnimation
if a.keyPath == "transform.scale" {
didEndFinishAnimation?()
NSTimer.schedule(delay: 1) { timer in
self.returnToOriginalState()
}
}
}
func returnToOriginalState() {
self.layer.removeAllAnimations()
self.setTitle(self.cachedTitle, forState: .Normal)
}
I noticed it had a public overide func animationDidStop(anim: CAAnimation, finished: Bool) to be the function to stop the animation. But when i use it, i get this error!
How do i rightfully get this to work? ...
Thanks in Advance
** UPDATED QUESTION **
I checked the code of TKTransitionSubmitButton and there are public methods called startLoadingAnimation(), returnToOriginalState() and startFinishAnimation().
I suggest:
Button tapped
startLoadingAnimation()
Check credentials
If wrong/error: returnToOriginalState()
If correct: startFinishAnimation()
Transition, from TKTransitionSubmitButton documentation:
btn.startFinishAnimation {
//Your Transition
let secondVC = SecondViewController()
secondVC.transitioningDelegate = self
self.presentViewController(secondVC, animated: true, completion: nil)
}
Edit: As far as I can see .animate() of the class calls both the start and finish animation, and that's why you can't cancel it.
EDIT2 This one works as intended for me (however I'm not sure about the static cornerRadius)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var submitButton: TKTransitionSubmitButton!
override func viewDidLoad() {
super.viewDidLoad()
submitButton.layer.cornerRadius = 15
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonPressed(sender: AnyObject) {
submitButton.startLoadingAnimation()
delay(2, closure: {
self.checkCredentials()
})
}
func checkCredentials()
{
//FAKING WRONG CREDENTIALS
let userAndPasswordCorrect = false
if !userAndPasswordCorrect
{
submitButton.returnToOriginalState()
//Alert or whatever
}
}
//Faking network delay
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
}
I checked the code of TKTransitionSubmitButton.
I resolve this issue. please try to stop animation under DispatchQueue.main.async and working well.
'func apicallforLogin() {
let urlString = "http://"
guard let requestUrl = URL(string:urlString) else { return }
let request = URLRequest(url:requestUrl)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
if error == nil,let usableData = data {
DispatchQueue.main.async {
self.btnLogin.startFinishAnimation(0.1, completion: {
let HomeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
HomeVC.transitioningDelegate = self
self.navigationController?.pushViewController(HomeVC, animated: false)
})
}
print(usableData)
}else{
DispatchQueue.main.async {
self.btnLogin.returnToOriginalState()
}
}
}
task.resume()'
Well you might have got the answer by now but just I would like to give my answer. You can just copy below code and everything will work fine. I have made the change and had created the pull request for this issue.
import Foundation
import UIKit
#IBDesignable
open class TKTransitionSubmitButton : UIButton, UIViewControllerTransitioningDelegate, CAAnimationDelegate {
lazy var spiner: SpinerLayer! = {
let s = SpinerLayer(frame: self.frame)
return s
}()
#IBInspectable open var spinnerColor: UIColor = UIColor.white {
didSet {
spiner.spinnerColor = spinnerColor
}
}
open var didEndFinishAnimation : (()->())? = nil
let springGoEase = CAMediaTimingFunction(controlPoints: 0.45, -0.36, 0.44, 0.92)
let shrinkCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
let expandCurve = CAMediaTimingFunction(controlPoints: 0.95, 0.02, 1, 0.05)
let shrinkDuration: CFTimeInterval = 0.1
#IBInspectable open var normalCornerRadius:CGFloat = 0.0 {
didSet {
self.layer.cornerRadius = normalCornerRadius
}
}
var cachedTitle: String?
var isAnimating = false
public override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
public required init!(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
}
func setup() {
self.clipsToBounds = true
spiner.spinnerColor = spinnerColor
}
open func startLoadingAnimation() {
self.isAnimating = true
self.cachedTitle = title(for: UIControlState())
self.setTitle("", for: UIControlState())
self.layer.addSublayer(spiner)
// Animate
self.cornerRadius()
self.shrink()
_ = Timer.schedule(delay: self.shrinkDuration - 0.25) { timer in
self.spiner.animation()
}
}
open func startFinishAnimation(_ delay: TimeInterval,_ animation: CAMediaTimingFunction, completion:(()->())?) {
self.isAnimating = true
_ = Timer.schedule(delay: delay) { timer in
self.didEndFinishAnimation = completion
self.expand(animation)
self.spiner.stopAnimation()
}
}
open func animate(_ duration: TimeInterval,_ animation: CAMediaTimingFunction, completion:(()->())?) {
startLoadingAnimation()
startFinishAnimation(duration, animation, completion: completion)
}
open func setOriginalState() {
self.returnToOriginalState()
self.spiner.stopAnimation()
}
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
let a = anim as! CABasicAnimation
if a.keyPath == "transform.scale" {
didEndFinishAnimation?()
_ = Timer.schedule(delay: 1) { timer in
self.returnToOriginalState()
}
}
}
open func returnToOriginalState() {
self.spiner.removeFromSuperlayer()
self.layer.removeAllAnimations()
self.setTitle(self.cachedTitle, for: UIControlState())
self.spiner.stopAnimation()
self.isAnimating = false
}
func cornerRadius() {
let cornerRadiusAnim = CABasicAnimation(keyPath: "cornerRadius")
// cornerRadiusAnim.fromValue = frame.width
cornerRadiusAnim.toValue = frame.height/2
cornerRadiusAnim.duration = shrinkDuration
cornerRadiusAnim.timingFunction = shrinkCurve
cornerRadiusAnim.fillMode = kCAFillModeForwards
cornerRadiusAnim.isRemovedOnCompletion = false
layer.add(cornerRadiusAnim, forKey: cornerRadiusAnim.keyPath)
}
func shrink() {
let shrinkAnim = CABasicAnimation(keyPath: "bounds.size.width")
shrinkAnim.beginTime = CACurrentMediaTime() + 0.1
shrinkAnim.fromValue = frame.width
shrinkAnim.toValue = frame.height
shrinkAnim.duration = shrinkDuration
shrinkAnim.timingFunction = shrinkCurve
shrinkAnim.fillMode = kCAFillModeForwards
shrinkAnim.isRemovedOnCompletion = false
layer.add(shrinkAnim, forKey: shrinkAnim.keyPath)
}
func expand(_ animation: CAMediaTimingFunction) {
let expandAnim = CABasicAnimation(keyPath: "transform.scale")
expandAnim.fromValue = 1.0
expandAnim.toValue = 26.0
expandAnim.timingFunction = animation
expandAnim.duration = 0.3
expandAnim.delegate = self
expandAnim.fillMode = kCAFillModeForwards
expandAnim.isRemovedOnCompletion = false
layer.add(expandAnim, forKey: expandAnim.keyPath)
}
}

Block not really executing?

I'm trying to use a subclass of NSThread to run some commands. Before you recommend NSOperation or GCD, YES I need to use threads.
Below is my code and my output. The block is being created and added to the array and supposedly is being dequeued and run by the thread, but I don't see any output resulting from the running of my block. Why not?
import UIKit
class ViewController: UIViewController {
private let _queue = dispatch_queue_create("com.AaronLBratcher.ALBQueue", nil)
private let _thread = TestThread()
private let _lock = NSCondition()
override func viewDidLoad() {
super.viewDidLoad()
_thread.start()
var openSuccessful = false
dispatch_sync(_queue) {[unowned self] () -> Void in
self._lock.lock()
self._thread.openFile("file path here", completion: { (successful) -> Void in
print("completion block running...")
openSuccessful = successful
self._lock.signal()
self._lock.unlock()
})
self._lock.wait()
}
print("open operation complete")
print(openSuccessful)
}
final class TestThread:NSThread {
var _iterations = 0
var _lock = NSCondition()
var _blocks = [Any]()
func openFile(FilePath:String, completion:(successful:Bool) -> Void) {
print("queueing openFile...")
let block = {[unowned self] in
self._iterations = self._iterations + 1
print("opening file...")
completion(successful: true)
}
addBlock(block)
}
func addBlock(block:Any) {
_lock.lock()
_blocks.append(block)
_lock.signal()
_lock.unlock()
}
override func main() {
_lock.lock()
while true {
while _blocks.count == 0 {
print("waiting...")
_lock.wait()
}
print("extracting block...")
if let block = _blocks.first {
_blocks.removeFirst()
_lock.unlock()
print("running block...")
block;
}
_lock.lock()
}
}
}
}
Output:
queueing openFile...
waiting...
extracting block...
running block...
waiting...
The block isn't running because you just have block. You need block(), e.g.:
if let block = _blocks.first as? ()->() {
_blocks.removeFirst()
_lock.unlock()
print("running block...")
block()
}

How do I make a label keep switching between two colors in Swift?

I am trying to make my label flash two colors on repeat. I want it to continuously repeat the text color Blue and Black. Is this possible? And can someone help me do this?
You can do it by using NSTimer and below is complete code for that:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var lbl: UILabel!
var timer : NSTimer?
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
var textColorIsBlue = false
func update() {
if !textColorIsBlue {
lbl.textColor = UIColor.blueColor()
} else {
lbl.textColor = UIColor.blackColor()
}
textColorIsBlue = !textColorIsBlue
}
}
And your result will be:
And you can modify timer as per your need.
And whenever you don't need time you can stop it by using timer?.invalidate().
I couldn't help but create an alternative example
class ViewController: UIViewController {
#IBOutlet private var label: UILabel! {
didSet {
label.text = "This is just some sample text"
}
}
private var timer: dispatch_source_t?
private var generator: AlternatingColorGenerator?
deinit {
guard let timer = timer else { return }
dispatch_source_cancel(timer)
}
override func viewDidLoad() {
super.viewDidLoad()
generator = AlternatingColorGenerator(colors: [.redColor(), .blueColor(), .greenColor()])
let t = repeatingTimer(1, queue: dispatch_get_main_queue()) { [weak self] () -> Void in
self?.label.textColor = self?.generator?.next()
}
dispatch_resume(t)
timer = t
label.textColor = generator?.next()
}
}
private class AlternatingColorGenerator: GeneratorType {
var currentIndex: Int = 0
let numberOfColors: Int
let colors: [UIColor]
init(colors: [UIColor]) {
self.colors = colors
numberOfColors = colors.count
}
func next() -> UIColor? {
let color = colors[currentIndex]
currentIndex = (currentIndex + 1) % numberOfColors
return color
}
}
private func repeatingTimer(interval: NSTimeInterval, queue: dispatch_queue_t, action: () -> Void) -> dispatch_source_t {
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
let timerInterval = UInt64(interval) * NSEC_PER_SEC
let timerLeeway = timerInterval / 10
dispatch_source_set_event_handler(timer, action)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, timerInterval, timerLeeway)
return timer
}
This uses a GCD timer, rather than an NSTimer, and rather than toggle the colours with a method, I've created a generator.
This uses a different programming style: one where the generation of things (timers and colours to use) is separated from the place where they are consumed.