Home > Back-end >  Am I able to use the value of a variable to call a variable within my struct?
Am I able to use the value of a variable to call a variable within my struct?

Time:01-15

I have sounds stored for different events within the struct that the user will be able to change, and was hoping i could send the function a string to select a song for a specific event.

my function call would look like this:

func playSound(audio: Audio, soundSelect: String = "startSound"){

if let path = Bundle.main.path(forResource: audio.\(soundSelect), ofType: audio.soundType){
do {
        audioPlayer = try! AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
        audioPlayer?.play()
    }catch{
        print("ERROR: Could not find and play the sound file!")
    }

and my struct would look something like this:

struct Audio {
  var startSound: String = "happyMusic"
  var endSound: String = "sadMusic"
  var soundType: String = "mp3"
}

as above i tried string interpolation which didn't seem to work I got the error

"Expected member name following '.'

What I expected was for "audio.\(soundSelect)" to be read like "audio.startSound"

CodePudding user response:

You cannot build variable names at runtime because all variable names are evaluated at compile time.

But if you define the type of the parameter as a KeyPath you are able to address different properties in the struct

func playSound(audio: Audio, soundSelect: KeyPath<Audio,String> = \.startSound) {
    
    if let url = Bundle.main.url(forResource: audio[keyPath: soundSelect], withExtension: audio.soundType) {
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url)
            audioPlayer?.play()
        } catch {
            print("ERROR: Could not find and play the sound file!", error)
        }
    }
}

Notes:

  • If you implement a do - catch block handle (removing the question mark in try?) and print the error rather than just a meaningless string literal.

  • Bundle provides an URL related API which avoids the extra URL(fileURLWithPath call


A still swiftier syntax is to make the function throw and hand over the error(s) to the caller

func playSound(audio: Audio, soundSelect: KeyPath<Audio,String> = \.startSound) throws {
    if let url = Bundle.main.url(forResource: audio[keyPath: soundSelect], withExtension: audio.soundType) {
        audioPlayer = try AVAudioPlayer(contentsOf: url)
        audioPlayer?.play()
    } else {
        throw URLError(.badURL)
    }
}
  • Related