16

To set up a game loop in Objective-C I learnt that I should set up a CADisplayLink

updater = [CADisplayLink displayLinkWithTarget:self selector:@selector(gameLoop) ];
[updater setFrameInterval: 1];
[updater addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];

How do I do this in Swift?

I have tried to Google this but I cannot find any examples of this.

matsmats
  • 502
  • 3
  • 12

3 Answers3

26

Pretty much just a direct translation from Objective-C to Swift with a few tweaks.

import QuartzCore
var updater = CADisplayLink(target: self, selector: Selector("gameLoop"))
updater.frameInterval = 1
updater.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
Martin Delille
  • 11,360
  • 15
  • 65
  • 132
Brian Tracy
  • 6,801
  • 2
  • 33
  • 48
  • 3
    Random note: the Swift class that has the selector on it must extend `NSObject`. – Dan Rosenstark Feb 17 '15 at 19:08
  • Another random note: You need to be in a UI based application for this to work. In other words, if you are running this from a Framework target's unit tests, it won't work; no display – AJ Venturella Nov 02 '15 at 23:25
8

Swift 3.x version of Brian Tracy's answer:

let updater = CADisplayLink(target: self, selector: #selector(self.gameLoop))
updater.preferredFramesPerSecond = 60
updater.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)

Apart from the syntax changes of Swift 3, also note the iOS 10 API change from frameInterval (which still works, but produces a deprecated warning) to preferredFramesPerSecond.

Community
  • 1
  • 1
Nikolay Suvandzhiev
  • 8,465
  • 6
  • 41
  • 47
5

Here's my GameLoop class (Swift 3)

import UIKit

class GameLoop : NSObject {

    var doSomething: () -> ()!
    var displayLink : CADisplayLink!

    init(doSomething: @escaping () -> ()) {
        self.doSomething = doSomething
        super.init()
        start()
    }

    // you could overwrite this too
    func handleTimer() {
        doSomething()
    }

    func start() {
        displayLink = CADisplayLink(target: self, selector: #selector(handleTimer))
        /*
        * If set to zero, the
        * display link will fire at the native cadence of the display hardware.
        * The display link will make a best-effort attempt at issuing callbacks
        * at the requested rate.
        */
        displayLink.preferredFramesPerSecond = 0
        displayLink.add(to: .main, forMode: .commonModes)
    }

    func stop() {
        displayLink.invalidate()
        displayLink.remove(from: .main, forMode: .commonModes)
        displayLink = nil
    }
}

If you're updating something on a background GCD queue and want to pull those changes to the Main queue (and runloop), you should use Dispatch Source for Data.

Community
  • 1
  • 1
Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
  • Sorry, but the stop Function does not really stop the loop. What must one do to stop the loop? – bastianwegge Feb 19 '15 at 19:30
  • You need to call '''displayLink.invalidate()''' in order to stop it. Otherwise it will be never removed from '''NSRunLoop''' – DeVladinci Dec 06 '15 at 08:22
  • To stop the loop in Yar's GameLoop class, you need to add: `displayLink.paused = true` to the `stop()` function. Therefore, it should look like this: func stop() { displayLink.paused = true displayLink.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) displayLink = nil } This updated stop function worked for me. Xcode 6.4. – RedKnight Sep 01 '15 at 22:28
  • Thanks @DeVladinci, I've updated the code to invalidate. – Dan Rosenstark May 08 '17 at 19:47