Views observing a Core Data object are not updating

Viewed 126

I have a Core Data object called Config on ContentView.

This object is created like this:

@ObservedObject private var config = Config.with(PersistenceController.shared.context)!

config has 3 boolean properties: turnedOn, isSoft, and willAppear.

turnedON and willAppear are created in a way that if turnedON is true, willAppear must be false and vice-versa.

Now I pass config to 3 toggle switches and let the user adjust each one, true or false, but remember, if the user turns turnedON true, willAppear must be false and vice-versa. The same is true if the user turns willAppear true, in that canse turnedON must switch to false and vice-versa too.

So I pass config to the toggle switches like this on ContentView:

CustomToggle("Turn it on?"),
             config,
             coreDataBooleanPropertyName:"turnedOn")

CustomToggle("Is it soft?"),
             config,
             coreDataBooleanPropertyName:"isSoft")

CustomToggle("Will it appear?"),
             config,
             coreDataBooleanPropertyName:"willAppear")

and this is CustomToggle...

import SwiftUI import CoreData

struct CustomToggle: View {
  @State private var status:Bool
  
  private let title: String
  private let coreDataBooleanPropertyName:String
  @ObservedObject private var config:Config {
    didSet {
      switch coreDataBooleanPropertyName {
      case "isSoft":
        self.status = config.isSoft
      case "turnedOn":
        self.status = config.turnedOn
      case "willAppear":
        self.status = config.willAppear
      default:
        break
      }
    }
  }
  
  init(_ title:String,
       _ config:Config,
       coreDataBooleanPropertyName:String) {
    self.title = title
    self.config = config
    self.coreDataBooleanPropertyName = coreDataBooleanPropertyName
    self.status = defaultStatus
    
    switch coreDataBooleanPropertyName {
    case "isSoft":
      self.status = config.isSoft
    case "turnedOn":
      self.status = config.turnedOn
    case "willAppear":
      self.status = config.willAppear
    default:
      break
    }
  }

  var body: some View {
      Toggle(isOn: $status, label: {
        ControlTitle(title)
      })
        .toggleStyle(CheckboxStyle())
        .onChange(of: status, perform: { newStatus in          
          switch coreDataBooleanPropertyName {
          case "isSoft":
            config.isSoft = newStatus
          case "turnedOn":
            config.turnedOn = newStatus
            config.willAppear = !newStatus
          case "willAppear":
            config.willAppear = newStatus
            config.turnedOn = !newStatus
          default:
            return
          }
          
          let coreDataContext = PersistenceController.shared.context

          do {
            try coreDataContext.save()
          }
          catch let error{
            print(error.localizedDescription)
          }
          
        })


struct CheckboxStyle: ToggleStyle {
  
  func makeBody(configuration: Self.Configuration) -> some View {
    
    return HStack {
      
      Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
        .resizable()
        .frame(width: 24, height: 24)
        .foregroundColor(configuration.isOn ?  Color.from("ffba00") : .gray)
        .font(.system(size: 20, weight: .bold, design: .default))
        .onTapGesture {
          configuration.isOn.toggle()
        }
      configuration.label

      Spacer()
    }
    
  }
}

I have tested the core data entry config and it works properly but the toggle switches willAppear and turnedOn do not update when one or the other are selected.

What am I missing?

1 Answers

Instead of didSet, which will not fire if just a property of your object changes, you could observe its changes via objectWillChange (note inline comments as well):

Toggle(isOn: $status, label: {
        ControlTitle(title)
    })
    .onReceive(config.objectWillChange) { _ in
        //run the code from your didSet here
        //note that you *may* need to use something like DispatchQueue.main.async { } in here to get the updated values of the object since the values may not be updated until the next run loop -- I'm unsure of how this will behave with CoreData objects
    }
    .onChange(of: status, perform: { newStatus in
        //etc
Related