iosswifttimeravaudioplayercadisplaylink

Updating UIProgressView via delegate method on AudioPlayer


I'm attempting to use CADisplayLink to update a UIProgressView. Here's CADisplayLink's initialiation signature

Here's a summary of the relevant variables from MyAudioPlayer class.

protocol AudioPlayerDelegate: class {
    func updateSlider(sender: AudioPlayer)
}

class AudioPlayer: NSObject {
    // relevant vars
    var updater: CADisplayLink?
    weak var delegate: AudioPlayerDelegate?

    func play() {
        // some setup stuff

        updater = CADisplayLink(target: self, selector: #selector(delegate?.updateSlider(sender:)))

        // regular stuff to play an audio file
    }
}

When I initialize updater in the play() method, the compiler says, :

Argument of #selector refers to instance method updateSlider(sender:) that is no exposed to Objective-C.

Easy enough...add objc in front of func updateSlider(sender: AudioPlayer), like so:

@objc func updateSlider(sender: AudioPlayer)

The compiler error goes away for a nanosecond, then the the line I just updated says:

@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes.

There are a few answers covering this topic, but I haven't found one that works.

I tried throwing updateSlider in an extension instead of in the protocol section, but I keep getting this ping pong of the compiler wanting to add @objc in front followed by wanting to remove it.

I also attempted to use an instance of the Timer class instead of the CADisplayLink class to do this, but I encounter the same issue.

Thank you for reading. I welcome your suggestions.


Solution

  • Dan got me over the finish line on this issue. Instead of adding @objc in front of just my delegate method, I needed to add @objc in front of the protocol declaration.

    // First change
    @objc protocol AudioPlayerDelegate: class {
        @objc func updateSlider(sender: AudioPlayer)
    }
    

    Further down in the AudioPlayer class...

    // Delegate declaration
    weak var delegate: AudioPlayerDelegate?
    
    // Second Change
    if let delegate = delegate {
        updater = CADisplayLink(target: delegate, selector: #selector(AudioPlayerDelegate.updateSlider(sender:)))
        updater?.preferredFramesPerSecond = 30
        updater?.add(to: RunLoop.current, forMode: .commonModes)
    }
    

    Lastly, when done with the updater, invalidate it and nil it out (most likely in whatever method you use to stop your AVAudioPlayer instance.

        updater?.invalidate()
        updater = nil