3
votes

I'm trying to build an example BBQ app to learn SwiftUI (XCode 11 beta 5), and have been unable to figure out how to navigate to an object's detail view from a list view of objects coming from Core Data. Xcode is unhelpful, mostly popping unrelated errors depending on what I try.

I've tried applying state, bindings, observable objects etc to the best of my logical ability but haven't been able to crack it.

Here is my list view, which builds just fine, and allows me to navigate to destination (which is just a view containing the id property as a string):

struct CookListView: View {

    @ObservedObject var cookListVM: CookListViewModel
    @State var cookCreatorVM = CookCreatorViewModel()

    init() {
        cookListVM = CookListViewModel()
    }

    var body: some View {


        NavigationView {
            List {
                ForEach(cookListVM.cooks, id: \.id) { cook in
                    NavigationLink(destination: Text("\(cook.id)")) {
                        VStack {
                            Text(cook.protein)
                        }
                    }
                }
            }.navigationBarTitle("BBQ")
        }
    }
}

But if I change my NavigationLink destination like so:

NavigationView {
    List {
        ForEach(cookListVM.cooks, id: \.id) { cook in
            NavigationLink(destination: CookDetailView(cook: cook)) {
                VStack {
                    Text(cook.protein)
                }
            }
        }
    }
}

Xcode will no longer build the project. The errors it gives me seem somewhat unrelated, such as Type '_' has no member 'id' on the ForEach line, or if I remove , id: \.id from the ForEach (which I don't really need thanks to Identifiable), I'll get Type of expression is ambiguous without more context on the Text(cook.protein) line.

If I use a hard-coded array it builds and can navigate perfectly well. The issue only arises when I'm trying to use Core Data.

My CookDetailView looks like this:

struct CookDetailView: View {
    var cook: Cook
    var body: some View {
        VStack {
            Text("\(cook.protein!)")
        }
    }
}

And the model for the Cook object itself looks like this:

class CookViewModel: ObservableObject, Identifiable {

    var protein: String = ""
    var type: String = ""
    var id: UUID = UUID()

    init(cook: Cook){
        self.protein = cook.protein!
        self.type = cook.type!
        self.id = UUID()
    }
}

It's also been set up thru the .xcdatamodeld file.

I'm more than happy to add in any additional/omitted code, such as how I'm writing to/reading from core data, if that would be helpful.

1
I don't think it's a problem related to CoreData, but in order for us to copy-paste your example and try to figure out the issue we'd need all the code as a minimum viable example. For instance: the Cook class is missing and I can't really copy your code and try to debug it.matteopuc
Is cook.protein an optional? You can replace { cook in with { (cook: CookType) in. Remove the VStack from around the label. If one of the two arguments is a Label it's more likely to work. If you try to pass in a binding it can get more complicated. Especially if it it's backed by a computed array.. but let's not go into that. Oh you can add cook.protein as a first unnamed argument, this more likely to work then, while removing the trailing closure ofc. Yay. SwiftUI!Fabian
Since we can't see the rest of your code, take a look at this project. It is complete and compiles and runs on Beta 5. It may give you some ideas on a direction to take for your project. github.com/Whiffer/SwiftUI-Core-Data-TestChuck H

1 Answers

2
votes

I can totally identify with your frustration with Xcode's error messages. The actual error is often nowhere near the error message. [Sug: use source control and commit after each clean compile ;-) ] The error that you are getting in your List view is because you specified a wrong type in your detail view's argument list. Try the following:

struct CookDetailView: View {

    var cook: CookViewModel

    var body: some View {
        VStack {
            Text("\(cook.name)")
        }
    }
}

By the way, since you are fetching into an array, you will not observe changes in that array unless you manually publish them. I gave up on trying to get my NSManagedObject subclasses to publish their own changes. There are two ways to workaround that. You can call objectWillChange from NSFetchedResultsController's controllerDidChangeContent delegate or you can do the same while observing NSManagedObjectContextDidSave notifications when not using a fetched results controller.

This answer is applicable to Beta 5. Things will certainly change in future betas.