Reputation: 51
I want to display a table with a text and an image on each row. Clicking on the image should toggle the picture. The table is associated with an array of instances of a class, which has a boolean property for storing the state of the image.
Clicking seems to have no effect.
import SwiftUI
struct TestView: View {
@State private var items = [
Item(id: 1, text: "Eins", isClicked: false),
Item(id: 2, text: "Zwei", isClicked: true),
Item(id: 3, text: "Drei", isClicked: false)
]
var body: some View {
Table(items) {
TableColumn("Items") { item in
HStack {
Text(item.text)
Button(action: {
item.isClicked.toggle()
}) {
Image(systemName: item.isClicked ? "checkmark.circle.fill" : "circle")
}
}
}
}
}
}
#Preview {
TestView()
}
class Item: Identifiable {
var id: Int
var text: String
var isClicked: Bool
init(id: Int, text: String, isClicked: Bool) {
self.id = id
self.text = text
self.isClicked = isClicked
}
}
What did I do wrong?
Upvotes: 0
Views: 251
Reputation: 36304
If targeting iOS-17, try this approach using the Observation
framework. When the item
is changed, the view will refresh due to the observation.
@Observable class Item: Identifiable { // <--- here
....
}
EDIT-1
If you are targeting ios-16+, then you could use this approach using ObservableObject
.
Example code:
struct ContentView: View {
var body: some View {
TestView()
}
}
struct TestView: View {
@StateObject private var dataModel = DataModel()
var body: some View {
Table($dataModel.items) {
TableColumn("Items") { $item in
HStack {
Text(item.text)
Button(action: {
item.isClicked.toggle()
}) {
Image(systemName: item.isClicked ? "checkmark.circle.fill" : "circle")
}
}
}
}
}
}
class DataModel: ObservableObject {
@Published var items: [Item] = [
Item(id: 1, text: "Eins", isClicked: false),
Item(id: 2, text: "Zwei", isClicked: true),
Item(id: 3, text: "Drei", isClicked: false)
]
}
struct Item: Identifiable {
var id: Int
var text: String
var isClicked: Bool
}
Upvotes: 0
Reputation: 29271
SwiftUI is highly dependent on being able to identify when something has changed, it is mostly done via Hashable
, Identifiable
and Equatable
but reference types require a little more work.
For a class
to work it must be an ObservableObject
or an Observable
.
The most compatible solution is to use a struct
instead of a class
.
struct Item: Identifiable, Hashable {
var id: Int
var text: String
var isClicked: Bool
init(id: Int, text: String, isClicked: Bool) {
self.id = id
self.text = text
self.isClicked = isClicked
}
}
Then the View
will just need a minor adjustment.
struct TwoWayTable: View {
@State private var items = [
Item(id: 1, text: "Eins", isClicked: false),
Item(id: 2, text: "Zwei", isClicked: true),
Item(id: 3, text: "Drei", isClicked: false)
]
var body: some View {
Table($items) { //Enable two-way communication
TableColumn("Items") { $item in
HStack {
Text(item.text)
Button(action: {
$item.wrappedValue.isClicked.toggle() //Affect the wrapped value so State can trigger a redraw.
}) {
Image(systemName: item.isClicked ? "checkmark.circle.fill" : "circle")
}
}
}
}
}
}
Upvotes: 0
Reputation: 27
The issue here is that SwiftUI's Table
does not directly support interactive elements like buttons within cells. You should use List
instead, which is designed to handle user interaction with its elements.
Try this:
import SwiftUI
struct TestView: View {
@State private var items = [
Item(id: 1, text: "Eins", isClicked: false),
Item(id: 2, text: "Zwei", isClicked: true),
Item(id: 3, text: "Drei", isClicked: false)
]
var body: some View {
List(items) { item in
HStack {
Text(item.text)
Button(action: {
item.isClicked.toggle()
}) {
Image(systemName: item.isClicked ? "checkmark.circle.fill" : "circle")
}
}
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
class Item: Identifiable {
var id: Int
var text: String
var isClicked: Bool
init(id: Int, text: String, isClicked: Bool) {
self.id = id
self.text = text
self.isClicked = isClicked
}
}
Upvotes: -2