Error when calling function to move an element: "Cannot use mutating member on immutable value: 'self' is immutable"

Hi,

The goal is the move an element based on an input matrix using timer to trigger changes in the position. Say we have 2 positions, an input matrix and the element that is to be moved defined:

import SwiftUI import Foundation

// Positions

struct PositionConstants {
    
    static let pos_1 = CGPoint(x: 155, y: 475)
    static let pos_2 = CGPoint(x: 135, y: 375)
    
    
}

// Input Matrix

struct Input {

    static let input_1: [[Int]] = [
        [    1,        1,       0,      0,      0,      0,     0,     0,     0   ],
        [    0,        0,       0,      0,      0,      0,     0,     0,     0   ],
        [    0,        0,       0,      0,      0,      0,     0,     0,     0   ],
        [    0,        0,       0,      0,      0,      0,     0,     0,     0   ],
        [    1,        0,       1,      0,      0,      0,     0,     0,     0   ], 
        [    0,        0,       0,      0,      0,      0,     0,     0,     0   ], 
        [    0,        0,       0,      0,      0,      0,     0,     0,     0   ], 
        [    0,        0,       0,      0,      0,      0,     0,     0,     0   ], 
    ]
}

// Element Class

class Element: ObservableObject {
    let imageName: String
    let name: String
    let size: CGSize
    @Published var position: CGPoint
    
    init(imageName: String, name: String, size: CGSize, position: CGPoint) {
        self.imageName = imageName
        self.name = name
        self.size = size
        self.position = position
    }
}

Moving the element without a function works perfectly fine:

struct Pos_View: View {
    @ObservedObject var element_1 = Element(imageName: "pic_1", name: "", size: CGSize(width: 100, height: 100), position: PositionConstants.pos_1)
    
    let Input_Matrix = Input.input_1 
    
    // Index to track current row in matrix
    @State private var currentIndex = 0
    
    // Timer to trigger updates
    private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
    
    var body: some View {
        VStack {
            // Display element
            Image(element_1.imageName)
                .resizable()
                .frame(width: element_1.size.width, height: element_1.size.height)
                .position(element_1.position)
                .onReceive(timer) { _ in
                    // Update element position based on matrix
                    if currentIndex < Input_Matrix.count {
                        let isFirstElementOne = Input_Matrix[currentIndex][0] == 1
                        let newPosition = isFirstElementOne ? PositionConstants.pos_2 : PositionConstants.pos_1
                        withAnimation {
                            element_1.position = newPosition
                        }
                        currentIndex += 1
                    } else {
                        // Stop the timer when we reach the end of the matrix
                        timer.upstream.connect().cancel()
                    }
                }
        }
    }
}


struct ContentView: PreviewProvider {
    static var previews: some View {
        Pos_View()
    }
}

But when using a function to trigger the animation, I get the error "Cannot use mutating member on immutable value: 'self' is immutable" when calling the function using a button:

struct Pos_View: View {
    @ObservedObject var element_1 = Element(imageName: "pic_1", name: "", size: CGSize(width: 100, height: 100), position: PositionConstants.pos_1)
    
    let Input_Matrix = Input.input_1
    
    // Index to track current row in matrix
    @State var currentIndex = 0
    
    // Timer to trigger updates
    private var timer: Timer?
    
    var body: some View {
        VStack {
            // Display element
            Image(element_1.imageName)
                .resizable()
                .frame(width: element_1.size.width, height: element_1.size.height)
                .position(element_1.position)
            
            // Button to start animation
            Button("Start Animation") {
                startAnimation()
            }
        }
    }
    
    mutating func startAnimation() {
        currentIndex = 0 // Reset index before starting animation
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
            if self.currentIndex < self.Input_Matrix.count {
                let isFirstElementOne = self.Input_Matrix[self.currentIndex][0] == 1
                let newPosition = isFirstElementOne ? PositionConstants.pos_2 : PositionConstants.pos_1
                withAnimation {
                    self.element_1.position = newPosition
                }
                self.currentIndex += 1
            } else {
                // Stop the timer when we reach the end of matrix
                timer.invalidate()
            }
        }
    }
}

struct ContentView: PreviewProvider {
    static var previews: some View {
        Pos_View()
    }
}

The function is defined as mutating and the element as as @ObservedObject. Anyone has a clue?

Where exactly is the error ? On timer probably.

That's because you declare the function as mutating (as compiler asks for).

But you have then to make timer a State variable:

struct Pos_View: View { 
    @ObservedObject var element_1 = Element(imageName: "pic_1", name: "", size: CGSize(width: 100, height: 100), position: PositionConstants.pos_1) 
    
    let Input_Matrix = Input.input_1
    
    // Index to track current row in matrix
    @State var currentIndex = 0
    
    // Timer to trigger updates
    @State private var timer: Timer?    // <<-- State var
    
    var body: some View {
        VStack {
            // Display element
            Image(element_1.imageName)
                .resizable()
                .frame(width: element_1.size.width, height: element_1.size.height)
                .position(element_1.position)
            
            // Button to start animation
            Button("Start Animation") {
                startAnimation()
            }
        }
    }
    
    /*mutating*/ func startAnimation() {      // <<-- no mutating
        currentIndex = 0 // Reset index before starting animation
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
            if self.currentIndex < self.Input_Matrix.count {
                let isFirstElementOne = self.Input_Matrix[self.currentIndex][0] == 1
                let newPosition = isFirstElementOne ? PositionConstants.pos_2 : PositionConstants.pos_1
                withAnimation {
                    self.element_1.position = newPosition
                }
                self.currentIndex += 1
            } else {
                // Stop the timer when we reach the end of matrix
                timer.invalidate()
            }
        }
    }
}

Ah. I See. Thank you very much! It's working now.

Thanks for the feedback. Don't forget to mark the correct answer to close the thread. Good continuation.

Error when calling function to move an element: "Cannot use mutating member on immutable value: 'self' is immutable"
 
 
Q