Jessy
Jessy

Reputation: 167

'SwiftUI' how to change checkmark in `listview`

I'm trying to change the checkmark image on listview row selection, but I'm not getting expected result from selection.

Below code I'm creating listview from the array todo items and added tapGesture to the row. When I'm selecting row I can able to change the completed value but not reflecting that on UI.

struct ContentView: View {
    @State var items: [Todo] = todoItems
    
    var body: some View {
        List(items, id: \.self) { item in
            Row(item: item) { item in
                item.completed.toggle()
                print(item.completed)
            }
        }
    }
}


struct Row: View {
     let item: Todo
    
    var onTap: ((_ item: Todo) -> Void)?
    
    var body: some View {
        HStack {
            Image(systemName: item.completed ? "checkmark.square.fill" :  "squareshape" )
                .foregroundColor(item.completed ? .green : .gray)
            Text(item.title).font(.headline).foregroundColor(.secondary)
        }.onTapGesture {
            onTap?(item)
        }
    }
}

class Todo: Hashable {
    static func == (lhs: Todo, rhs: Todo) -> Bool {
        lhs.title == rhs.title
    }
    
    var title: String
    var completed: Bool
    
    init(title: String, completed: Bool) {
        self.title = title
        self.completed = completed
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(title)
    }
}


let todoItems = [
    Todo(title: "$300", completed: false),
    Todo(title: "$550", completed: false),
    Todo(title: "$450", completed: false)
]

Upvotes: 1

Views: 3384

Answers (1)

George
George

Reputation: 30341

You can make Todo a struct. For data like this, it is much more preferable for it to be a struct rather than a class.

The item also doesn't need to be passed down, since you already get it in the List. I also used the $ syntax within for $items, so $item is a Binding and you therefore can mutate item. Changing item is changing that element in @State variable items, therefore a view update occurs.

Your implementation for Equatable and Hashable on Todo was incorrect - because otherwise the view won't update if a Todo instance is the same, even with a different completed value. I added Identifiable to Todo anyway, so you can implicitly identify by id.

Code:

struct ContentView: View {
    @State var items: [Todo] = todoItems

    var body: some View {
        List($items) { $item in
            Row(item: item) {
                item.completed.toggle()
            }
        }
    }
}
struct Row: View {
    let item: Todo
    let onTap: (() -> Void)?

    var body: some View {
        HStack {
            Image(systemName: item.completed ? "checkmark.square.fill" :  "squareshape" )
                .foregroundColor(item.completed ? .green : .gray)
            Text(item.title).font(.headline).foregroundColor(.secondary)
        }.onTapGesture {
            onTap?()
        }
    }
}
struct Todo: Hashable, Identifiable {
    let id = UUID()
    var title: String
    var completed: Bool

    init(title: String, completed: Bool) {
        self.title = title
        self.completed = completed
    }
}

Upvotes: 1

Related Questions