3

XCode Version 12.5 (12E262) - Swift 5

To simplify this example, I've created a testObj class and added a few items to an array. Let's pretend that I want to render buttons on the screen (see preview below), once you click on the button, it should set testObj.isSelected = true which triggers the button to change the background color.

I know it's changing the value to true, however is not triggering the button to change its color.

Here's the example:

//
//  TestView.swift
//
import Foundation
import SwiftUI
import Combine

struct TestView: View {
    
    @State var arrayOfTestObj:[testObj] = [
        testObj(label: "test1"),
        testObj(label: "test2"),
        testObj(label: "test3")
    ]

    var body: some View {
        VStack {
            ForEach(arrayOfTestObj, id: \.id) { o in
                HStack {
                    Text(o.label)
                        .width(200)
                        .padding(20)
                        .background(Color.red.opacity(o.isSelected ? 0.4: 0.1))
                        .onTapGesture {
                            o.isSelected.toggle()
                        }
                }
            }
        }
    }
}
  
class testObj: ObservableObject {
    let didChange = PassthroughSubject<testObj, Never>()
    
    var id:String = UUID().uuidString   {didSet {didChange.send((self))}}
    var label:String = ""               {didSet {didChange.send((self))}}
    var value:String = ""               {didSet {didChange.send((self))}}
    var isSelected:Bool = false         {didSet {didChange.send((self))}}
    
    init (label:String? = "") {
        self.label = label!
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

If I update the ForEach as...

ForEach($arrayOfTestObj, id: \.id) { o in

... then I get this error:

Key path value type '_' cannot be converted to contextual type '_'

How can I change testObj to make it bindable?

Any help is greatly appreciated.

Test snippet to illustrate bindable custom object issue

Richard H
  • 300
  • 7
  • 15
  • 2
    You can’t put a binding array in the for each loop. Your objects are ObservableObjects so just use the ObservedObject wrapper in a View made to display your row and use the Published wrapper in your ObservableObject – lorem ipsum May 04 '21 at 14:35

1 Answers1

2
struct TestView: View {
    
    @State var arrayOfTestObj:[TestObj] = [
        TestObj(label: "test1"),
        TestObj(label: "test2"),
        TestObj(label: "test3")
    ]
    
    var body: some View {
        VStack {
            ForEach(arrayOfTestObj, id: \.id) { o in
                //Use a row view
                TestRowView(object: o)
            }
        }
    }
}
//You can observe each object by creating a RowView
struct TestRowView: View {
    //And by using this wrapper you observe changes
    @ObservedObject var object: TestObj
    var body: some View {
        HStack {
            Text(object.label)
                .frame(width:200)
                .padding(20)
                .background(Color.red.opacity(object.isSelected ? 0.4: 0.1))
                .onTapGesture {
                    object.isSelected.toggle()
                }
        }
        
    }
}
//Classes and structs should start with a capital letter
class TestObj: ObservableObject {
    //You don't have to declare didChange if you need to update manually use the built in objectDidChange
    let id:String = UUID().uuidString
    //@Published will notify of changes
    @Published var label:String = ""
    @Published var value:String = ""
    @Published var isSelected:Bool = false
    
    init (label:String? = "") {
        self.label = label!
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48