UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Why is this an immutable value?

Forums > SwiftUI

I'm trying to toggle the task.completed boolean as seen in the screenshot, but it says that it is a let constant and I have no idea why that is the case or how I am supposed to make it mutable.

2      

In a ForEach loop you have basically a let variable assigned to each value in the array in turn.

If you have an array of objects (instances of a class), then you can modify properties of that item. If you have an array of struct items (like your case), then those will be immutable.

You could try...

ForEach(0..<tasks.count) { index in
  ...
  tasks[index].completed.toggle()
  .....

3      

ForEach(0..<tasks.count) { index in

Be aware, however, that using this version of ForEach can result in crashes if you add to and will result in crashes if you delete from your tasks array. Using ForEach with a Range is intended for constant data that doesn't change. SwiftUI will read it once and then not again, meaning if you change the underlying array, those changes won't get noticed and will lead to crashes.

(And if you aren't planning to add to or delete from your tasks array, then there's no need to make it an @State variable.)

Instead, you can use something like this:

ForEach(Array(tasks.enumerated()), id: \.1.id) { idx, task in
    Button {
        tasks[idx].completed.toggle()
    } label: {
        HStack {
            Image(systemName: task.completed ? "checkmark.circle" : "circle")
            Text(task.name)
            // NOTE: you can also use tasks[idx] here instead of task
        }
    }
}

With Swift 5.5, it becomes even easier:

ForEach($tasks) { $task in
    Button {
        task.completed.toggle()
    } label: {
        HStack {
            Image(systemName: task.completed ? "checkmark.circle" : "circle")
            Text(task.name)
        }
    }
}

And the great thing about this is, since it's a language feature and not a SwiftUI feature, as long as you are compiling against Swift 5.5 (which for the moment requires Xcode 13 beta), it will work in iOS 13 and 14 as well.

(Another note: When looping over an array of Identifiable things, you don't need to include the id: \.id parameter to ForEach. It is implicit in the things being Identifiable. So, ForEach(tasks) instead of ForEach(tasks, id: \.id). Just FYI.)

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.