swiftuistructbooleanmutating-function

SwiftUI: Calling a mutating func in a struct from a View body throws 'immutable value' error


I like to change a var in a struct from a View body. However, this throws in 'immutable value' error each time. After Googling, a mutating func seems appropriate, but also this method throws the same error.

Minimum reproducible example below (tap the button > toggle the Bool in the struct > change the button color).

Note that in my original code it's not about changing the button color, but marking a quiz module as complete and updating some interface items and colors.

// Module.swift

import SwiftUI

struct Module: Identifiable {
    var id = UUID()
    var title: String
    var complete: Bool
    
    mutating func toggleComplete() {
        complete.toggle()
    }
}

var modules = [
    Module(title: "Module 1", complete: false)
    Module(title: "Module 2", complete: false)
]
// ModuleButton.swift

import SwiftUI

struct ModuleButton: View {
    var module: Module
    
    var body: some View {
        Button(module.title){}
            .padding(8)
            .padding(.horizontal, 10)
            .background(module.complete ? .green : .blue)
            .foregroundColor(.white)
            .clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
            .onTapGesture{
                module.toggleComplete()
            }
    }
}

struct ModuleButton_Previews: PreviewProvider {
    static var previews: some View {
        ModuleButton(module: modules[0])
    }
}

Some examples of how I got inspired to use mutating func:

https://www.hackingwithswift.com/sixty/7/5/mutating-methods

https://chris.eidhof.nl/post/structs-and-mutation-in-swift/

https://developer.apple.com/forums/thread/654058


Solution

  • As I said in my comment, module should be an @State variable. Here's a question explaining what @State is: What does the SwiftUI `@State` keyword do?. See the question for more information on the background behind @State. Here's how you should modify your code:

    struct ModuleButton: View {
        @State var module: Module //<<<--- Here!!!
        
        var body: some View {
            Button(module.title){}
                .padding(8)
                .padding(.horizontal, 10)
                .background(module.complete ? .green : .blue)
                .foregroundColor(.white)
                .clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous))
                .onTapGesture{
                    //This now works
                    module.toggleComplete()
                }
        }
    }