5

I'm trying to figure out what I can possibly be doing wrong (or, of course, misunderstood about relationships, fetches and all).

In a first search, when I read the question's title, I hoped this question could help me, but I was wrong:

SwiftUI TabView with List not refreshing after objected deleted from / added to Core Data

Anyway...

In my app, when I touch a customer item, the detail view is shown properly with its data. If I change any attribute and go back to the first view, its updated correctly.

My problems start when I add a new location or delete one of the existing ones. When I go back to first view, the number of consumer's existing locations is not updated. When I close and reopen the app, all the data is correctly show.

As I said, my Core Data's model has 2 entities, like this:

Entity: Customer id: String name: String locationList: [Location] (to-many (Location), cascade)

Entity: Location id: String addess: String fromCustomer: Customer (to-One (Customer), nullify)

This is the view witch shows the Customers list:


struct CustomerList: View {

    @Environment(\.managedObjectContext) var moc

    @FetchRequest(
        entity: Customer.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \Customer.name, ascending: true),
        ]
    ) var customerList: FetchedResults<Customer>

    var body: some View {

        VStack{
            CustomerArea
        }
        .navigationBarTitle(Text("Customers"), displayMode: .inline)
        .navigationBarItems(leading: btnCreate, trailing: btnNew)

    }

    var CustomerArea: some View {

        List{

            ForEach(customerList, id: \.id) { customer in

                NavigationLink(
                    destination: LocationList(selectedCustomer: customer)
                ) {
                    VStack(alignment: .leading){

                        Text("\(customer.name ?? "?")").font(.headline)

                        HStack{

                                    Spacer()
                                    Text("Locations: \(customer.locationList?.count ?? 0)")
                                        .font(.footnote)

                                }

                    }.padding()
                }

            }.onDelete(perform: self.removeCustomer)

        }

    }

    private func removeCustomer(at offsets: IndexSet) {

        for index in offsets {
            let obj = customerList[index]
            self.moc.delete(obj)
        }

        try? self.moc.save()

    }

}


Finally, this is my LocationList view:


struct LocationList: View {

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @Environment(\.managedObjectContext) var moc

    var requestLocations : FetchRequest<Location>
    var locationList : FetchedResults<Location>{requestLocations.wrappedValue}

    var selectedCustomer: Customer

    init(selectedCustomer: Customer){

        self.selectedCustomer = selectedCustomer

        self.requestLocations = FetchRequest(
            entity: Location.entity(),
            sortDescriptors: [],
            predicate: NSPredicate(format: "fromCustomer.id == %@", selectedCustomer.id!)
        )

    }

    var btnNewLocation : some View { Button(action: {

        let object = Location(context: self.moc)
        object.id = UUID().uuidString
        object.address = UUID().uuidString
        object.fromCustomer = self.selectedCustomer

        try? self.moc.save()

    })  {
        Image(systemName: "plus.circle.fill")
        }
    }

    var body: some View {

        VStack{

            List{

                ForEach(locationList, id: \.id) { location in

                    VStack(alignment: .leading){
                        Text("\(location.address ?? "?")").font(.headline)
                        .onTapGesture {

                            print("Tapped and changed selected parent object")

                            self.selectedCustomer.name = "Changed"

                            try? self.moc.save()

                            self.presentationMode.wrappedValue.dismiss()

                        }

                    }.padding()

                }
                .onDelete(perform: removeLocation)

            }


        }
        .navigationBarTitle(Text("\(selectedCustomer.name ?? "?")"), displayMode: .inline)
        .navigationBarItems(trailing: btnNewLocation)
    }

    private func removeLocation(at offsets: IndexSet) {

        for index in offsets {

            let obj = locationList[index]

            self.moc.delete(obj)

        }

        try? self.moc.save()

    }

}


This is the first view. Imagine I touched the last item:

enter image description here

There's no locations linked to the selected customer:

enter image description here

So, I added on new item:

So, I added on new item

The number of locations is still ZERO, but should be 1:

The number of locations is still ZERO, but should be 1

Fattie
  • 27,874
  • 70
  • 431
  • 719
Boni Machado
  • 169
  • 11
  • ciao, you know, a HUGE SHORTCOMING of core data is that relationships ***do not***, repeat not, trigger controllers. Only the "immediate" fields trigger. I do not totally follow your exposition but perhaps it is related? (There are many questions about this drama, example stackoverflow.com/questions/60277521 ) – Fattie Jun 14 '20 at 15:22
  • @Fattie, my english is not as good as I wish, so take in advance my appologies for not understanding properly what you mean with "immediate fields". Did you mean this is the expected behaviour and core-data relationships are only affected when I CHANGE a child item? – Boni Machado Jun 14 '20 at 15:48
  • ciao Boni, your English is better than everyone in the USA :) Reading here: http://stackoverflow.com/questions/60277521 core data only triggers controllers when you change fields like string, int etc. if you change a *relationship* it does NOT trigger the controller. it is one of the strangest things in all of iOS. let me repeat: I do not 100% understand your question, but, this may be the issue !!! – Fattie Jun 15 '20 at 00:25
  • Boni, notice this QA: https://stackoverflow.com/questions/7533849 in the comments you can see me saying "THIS MUST BE A JOKE?!" :) it's a strange buy true thing – Fattie Jun 15 '20 at 00:43
  • I've got a simple solution (literally a few lines of code) to this problem that I give in detail here: https://stackoverflow.com/a/65309334/7965564. You could modify my solution to work in your case. I'm using a dedicated model controller class and doing work on a background context but it would be easy to modify to your situation. – Brian M Dec 15 '20 at 17:14

2 Answers2

3

A huge surprise in iOS: core data controllers only work on basic fields. Strange but true.

Incredibly, CoreData does not update based on relationships.

Only basic fields (int, string, etc).

You can see many QA and articles about this bizarre issue, example:

NSFetchedResultsController with relationship not updating

It is "just how it is".

There have been many attempts to work around this bizarre situation. There is no real solution. NSFetchedResultsController "is what it is" - it only works on basic fields (string, int, etc).

Unfortunately that is the situation.

Note: if I misunderstand the question here, sorry. But I believe this is the issue.

Fattie
  • 27,874
  • 70
  • 431
  • 719
0

Create a new struct XXXView, set relation object as property. Then it will refresh.

hstdt
  • 5,652
  • 2
  • 34
  • 34
  • 1
    you may add some further explaination / instructions to you're answer to make it valuable – Leo Jun 05 '21 at 12:28