2

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?

Duck
  • 34,902
  • 47
  • 248
  • 470
  • 1
    Property observers don't work on state objects (`@State`, `@ObservedObject`, etc.). You have to use `.onChange)_`. – Yrb Mar 18 '22 at 18:30
  • what do you mean? Core Data objects are, by default, Observable Objects. Care to explain? – Duck Mar 18 '22 at 18:32
  • [See this answer](https://stackoverflow.com/a/57486407/7129318) – Yrb Mar 18 '22 at 18:35
  • sorry but I do not understand how to use that on my code. Thanks anyway. – Duck Mar 18 '22 at 18:49
  • You are already using one `.onChange(of:)` in your code. What exactly don't you understand about it? Also, one more thing, when you initially create your `config`, it should be an `@StateObject`, not an `@ObservedObject`. – Yrb Mar 18 '22 at 18:54
  • Making it a @StateObject does not solve it. Your answer is too vague. If you want to make it a detailed answer on how my code should be written, I appreciate. If not, thanks anyway. – Duck Mar 18 '22 at 19:00
  • Are you expecting your `didSet` to fire when you change a property on `Config` like `isSoft`? – jnpdx Mar 18 '22 at 19:29
  • this is me trying to make it work – Duck Mar 18 '22 at 19:34
  • I'm trying to understand the circumstances you want that code to run so that I can provide an answer. Is the scenario that I mentioned the one in which you're expecting `didSet` to fire? – jnpdx Mar 18 '22 at 19:37
  • yes, but I suppose this can be done using another way. The core data entity is updating correctly according to the switches' values. The problem is the switches not updating when core data changes. – Duck Mar 18 '22 at 19:39

1 Answers1

2

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
jnpdx
  • 45,847
  • 6
  • 64
  • 94