Error “Cannot use mutating member on immutable value: ‘self’ is immutable

import SwiftUI
import AVFoundation

struct ChordLibrary: View {
var audioPlayer:AVAudioPlayer?
mutating func btnTapped(_sender: Any) {
if let player = audioPlayer, player.isPlaying {
player.stop()
}else{
let source = Bundle.main.path(forResource: "How You Remind Me", ofType: "mp3")
let url = URL(fileURLWithPath: source!)
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.play()
}catch{
}
}
}
var body: some View{
Button(action: {btnTapped(_sender: Any)}, label: {
Text("Play Audio")
})
}
}

What can I do to get rid of the error? I don’t know if the rest of the code will or won’t work either, I wrote this to Swift Playgrounds from an Xcode guide

Make the audioPlayer property a State variable.

The modified version of your code is below, properly formatted to make it easier for others to read.

import SwiftUI
import AVFoundation

struct ChordLibrary: View {
    @State var audioPlayer:AVAudioPlayer?
    
    func btnTapped () {
        if let player = audioPlayer, player.isPlaying {
            player.stop()
        }else{
            let source = Bundle.main.path(forResource: "How You Remind Me", ofType: "mp3")
            let url = URL(fileURLWithPath: source!)
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: url)
                audioPlayer?.play()
            }catch{
            }
        }
    }
    var body: some View{
        Button(action: {btnTapped()}, label: {
            Text("Play Audio")
        })
    }
}


1 Like

See Documentation for more detail how mutation on value types works, it will help in future to understand errors and concepts better.

As for solution, @ibex10 gave you the answer how to fix it, I just want to add why this works (even without mutating) and your version doesn't. So, mutating methods require mutable self as you'll see from docs, in order to, well, mutate instance. body is not mutating property on this struct, therefore it has immutable self and cannot call mutating methods on it, which gives you this error. And you cannot make this property mutating as well, since that is a requirement of a protocol.

Second part of the solution lies more in SwiftUI details: views in it designed to be a lightweight "interfaces" to the graphical system underneath, so it is cheap to construct them and they can be declarative, which removes a direct mutation. On the other hand, you need to update view somehow, so here comes @State property wrapper. Its implementation is a black box, so we can say it does some kind of magic internally to preserve state for the view, but we actually shouldn't care about this a lot at this level, the only thing that matters here is that it is managed by SwiftUI. The important bit here is that it provides us way to mutate state on a struct, without actually mutating this struct, but only state that "attached" to it. How is that possible can be answered if we take a look at how this property wrapper is implemented, particularly wrappedValue | Apple Developer Documentation in it – note that it has nonmutating set, so any "mutation" of property isn't treated as mutation of a struct, and value lifetime is managed outside of the struct lifecycle by SwiftUI framework.

1 Like