How do you fix the "found nil while unwrapping optional value" error when trying to play sound?

Viewed 339

I'm making a function to play sound

func playSound(soundName: String) {
    let url = Bundle.main.url(forResource: soundName, withExtension: "wav")
    player = try! AVAudioPlayer(contentsOf: url!)
    player.play()
}

Then call this function in an IBAction that contains all my buttons

@IBAction func buttonPiano(_ sender: UIButton) {
    playSound(soundName: String(sender.currentTitle!))
    
    sender.backgroundColor = UIColor.white
    sender.alpha = 0.3
    
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
        sender.backgroundColor = UIColor.systemBackground
        sender.alpha = 1
    })       
}

Running the app I can do. But whenever you press a button, it crashes and gives me this error:

Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/administrator/Desktop/Xcode Projects/pianoButtons/pianoButtons/ViewController.swift, line 37

The optional value seems to be url! from my sound function.

I've tried all I could, but no luck. How do I avoid this error and play the sound without crashes?

2 Answers

Make sure that your soundName.wave file is shown inside the copy bundle resources. You can find that by clicking on your project > selecting your target > Build Phases > Copy Bundle Resources. If you do not see it there, click the plus button to add it.

screenshot

var soundPlayer: AVAudioPlayer?
 func playSentSound() {
    DispatchQueue.main.async{
        let path = Bundle.main.path(forResource: "soundName.mp3", ofType: nil)!
        let url = URL(fileURLWithPath: path)

        do {
            self.soundPlayer = try AVAudioPlayer(contentsOf: url)
            print("Playing")
            self.soundPlayer?.play()
        } catch {
            // couldn't load file :(
            print("Cant Load File")
        }
    }
}

Your code is essentially calling this

try! AVAudioPlayer(contentsOf: Bundle.main.url(forResource: String(button.currentTitle!), withExtension: "wav")!)

Each ! is a potential crash. This is when they would occur

  1. The button may not have a current title at the time the action is triggered.
  2. The main bundle may not have a resource with that name/extension
  3. The audio player may not be able to play the contents of that file

In your specific case it seems like it is failing at 2. The nicer way to handle this is like so

if let url = Bundle.main.url(forResource: soundName, withExtension: "wav") {
  player = try! AVAudioPlayer(contentsOf: url)
} else {
  print("No resouce named \(soundName).wav")
}

First of all your app will just not play a sound instead of crashing, second you will get a helpful log message which might show you why the resource isn't being found.

Ideally all of your ! should be replaced with similar constructs, to log errors or perform some fallback action instead of crashing.

Related