1

I am trying to change a value that is stored in class through a custom structure using a picker. I am trying to make an app that lists course name and grade. The picker is in a HStack with a name. I am not able to change the value of a particular element, (in this example, grade) using a Picker. I am using Observable class as I have multiple views which need the access the class.

Here is some of my code,

struct courseItem: Identifiable{
    var id = UUID()
    let name: String
    let grade: String
}
@Observable
class Courses{
    var items = [courseItem]()
}
struct ContentView: View{
    @State private var courses = Courses()
    let grades = ["A", "B", "C"]
    var body: some View{
        List{
            ForEach(courses.items){ item in 
                HStack{
                    Text(item.name)
                    Spacer()
                    Picker("Grade", selection: item.grade){
                        ForEach(grades, id: \.self){
                            Text($0)
                        }
                    }
                }
            }
     
        }
    }
}

I know item.grade should be binding but I get an error when I do that. Right now I get an error saying the compiler is unable to type check the expression in reasonable time. Any help is much appreciated.

7
  • Can’t use state for a class, only value types or it’ll leak heap memory. Need to use StateObject for a class. Commented Dec 31, 2024 at 10:13
  • @malhal, this is not true at all, Apple recommends to use @State for @Observable class, here Managing model data in your app to Create the source of truth for model data Commented Dec 31, 2024 at 12:14
  • @workingdogsupportUkraine the docs are wrong and you know it Commented Jan 1 at 4:08
  • @malhal, the docs are not wrong but are very clear; that is what you should do: @State private var book = Book() and @State private var library = Library(). No examples could possibly illuminate this point with greater clarity. And the example code provided works very well. If you have some other documentation for @Observable class that shows the opposite, then please provide a link. Commented Jan 1 at 6:16
  • The docs were written for iOS 17 and in 17b1 State was changed to have an auto closure so it would work for objects but it was removed in 17b5 (probably because it made State for values inefficient). The docs were never reverted to remove all uses of State with objects. Commented Jan 1 at 11:34

1 Answer 1

1

Try this approach using bindings $ as shown in the example code, ...to change the value of a particular element, (in this example, grade)

struct courseItem: Identifiable {
    let id = UUID()  // <--- here
    var name: String
    var grade: String  // <--- here
}

@Observable class Courses {
    // <--- for testing
    var items: [courseItem] = [courseItem(name: "name-1", grade: "A"),
                               courseItem(name: "name-2", grade: "B"),
                               courseItem(name: "name-3", grade: "C")
    ]
}

struct ContentView: View {
    @State private var courses = Courses()
    
    let grades = ["A", "B", "C"]
    
    var body: some View{
        List{
            ForEach($courses.items){ $item in   // <--- here
                HStack{
                    Text(item.name)
                    Spacer()
                    Picker("Grade", selection: $item.grade) {  // <--- here
                        ForEach(grades, id: \.self) {
                            Text($0)
                        }
                    }
                }
            }
        }
    }
}

If you want to pass courses to other views, you can use @Bindable var courses: Courses to allow editing of the desired property, such as:

struct ContentView: View {
    @State private var courses = Courses() // <--- here, only one source of truth data

    var body: some View {
        TestOtherView(courses: courses) // <--- here
    }
}

struct TestOtherView: View {
    @Bindable var courses: Courses  // <--- here
    
    let grades = ["A", "B", "C"]
    
    var body: some View {
        List{
            ForEach($courses.items){ $item in   // <--- here
                HStack{
                    Text(item.name)
                    Spacer()
                    Picker("Grade", selection: $item.grade) {  // <--- here
                        ForEach(grades, id: \.self) {
                            Text($0)
                        }
                    }
                }
            }
        }
    }
}

Note, if you want to pass courses to many other views consider using @Environment(Courses.self) private var courses see the docs at Managing model data in your app.

For example:

struct ContentView: View{
    @State private var courses = Courses()

    var body: some View{
        TestOtherView()
            .environment(courses) // <--- here
    }
}

struct TestOtherView: View{
    @Environment(Courses.self) private var courses  // <--- here
    let grades = ["A", "B", "C"]
    
    var body: some View{
        @Bindable var courses = courses  // <--- here
        List{
            ForEach($courses.items){ $item in   // <--- here
                HStack{
                    Text(item.name)
                    Spacer()
                    Picker("Grade", selection: $item.grade) {  // <--- here
                        ForEach(grades, id: \.self) {
                            Text($0)
                        }
                    }
                }
            }
        }
    }
}
 
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.