Search code examples
iosswiftplaybackanimatewithdurationsoundeffect

animateWithDuration play sound doesn't repeat


I have a pulsing rectangle animated with animateWithDuration and setAnimationRepeatCount().

I'm trying to add a sound effect in the animations block witch is "clicking" synchronously. But the sound effect is only playing once. I can't find any hint on this anywhere.

UIView.animateWithDuration(0.5,
                           delay: 0,
                           options: UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.Repeat,
                           animations: {
                               UIView.setAnimationRepeatCount(4)
                               self.audioPlayer.play()
                               self.img_MotronomLight.alpha = 0.1
                           }, completion: nil)

The sound effect should play four times but doesn't.

Audio Implementation:

//global:
var metronomClickSample = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("metronomeClick", ofType: "mp3")!)
var audioPlayer = AVAudioPlayer()

override func viewDidLoad() {
    super.viewDidLoad()

    audioPlayer = AVAudioPlayer(contentsOfURL: metronomClickSample, error: nil)
    audioPlayer.prepareToPlay()
....
}

@IBAction func act_toggleStartTapped(sender: AnyObject) {
 ....
    UIView.animateWithDuration(....
                    animations: { 
                           UIView.setAnimationRepeatCount(4)
                           self.audioPlayer.play()
                           self.img_MotronomLight.alpha = 0.1
                    }, completion: nil)
}

Solution

  • Providing the UIViewAnimationOptions.Repeat option does NOT cause the animation block to be called repeatedly. The animation is repeated on a CoreAnimation level. If you place a breakpoint in the animation block, you'll notice that it is only executed once.

    If you want the animation to execute in a loop alongside the sound, create a repeating NSTimer and call the animation / sound from there. Keep in mind that the timer will retain the target, so don't forget to invalidate the timer to prevent retain cycle.

    EDIT: Added implementation below

    First, we'll need to create the timer, assuming we have an instance variable called timer. This can be done in viewDidLoad: or init method of a view. Once initialized, we schedule for execution with the run loop, otherwise it will not fire repeatedly.

    self.timesFired = 0
    self.timer = NSTimer(timeInterval: 0.5, target: self, selector:"timerDidFire:", userInfo: nil, repeats: true)
    if let timer = self.timer {
        NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSDefaultRunLoopMode)
    }
    

    The following is the method fired by the timer every interval (in this case 0.5 seconds). Here, you can run your animations and audio playback. Note that UIViewAnimationOptions.Repeat option has been removed since the timer is now responsible for handling the repeating animation and audio. If you only the timer to fire a specific number of times, you can add an instance variable to keep track of times fired and invalidate the timer if the count is above the threshold.

    func timerDidFire(timer: NSTimer) {
    
        /* 
         * If limited number of repeats is required 
         * and assuming there's an instance variable 
         * called `timesFired`
         */
        if self.timesFired > 5 {
            if let timer = self.timer {
                timer.invalidate()
            }
            self.timer = nil
            return
        }
        ++self.timesFired
    
        self.audioPlayer.play()
    
        var options = UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.CurveEaseOut;
        UIView.animateWithDuration(0.5, delay: 0, options: options, animations: {
                self.img_MotronomLight.alpha = 0.1
        }, completion: nil)
    }