Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: ForEach of FetchedResults gives the error - "Value of type 'NSManagedObject' has no member..."

Tags:

swift

swiftui

I'm new to SwiftUI and partially to Swift, so hope someone can shed some light on something I am blatantly missing. Excuse the messy code! I haven't had chance yet to refactor.

I'm using a @FetchRequest property wrapper to pull a list of all Memories back from a CoreData model, with a NSFetchRequest<Memory> method defined on it, extending from NSManagedObject.

When I iterate through the results of the FetchRequest<Memory> in SwiftUI via a ForEach, it returns an instance of NSManagedObject into the loop variable and not of Memory, and thus displays the error: Value of type 'NSManagedObject' has no member 'createdAt' for the following SwiftUI class:

import SwiftUI

struct MemoryView: View {

    @Environment(
        \.managedObjectContext
    ) var managedObjectContext

    @FetchRequest(
        fetchRequest: Memory.allMemories()
    ) var allMemories: FetchedResults<Memory>

    @State private var newMemory = ""

    var body: some View {
        NavigationView {
            List {
                Section(header: Text("What's on your mind?")) {
                    HStack {
                        TextField("New memory", text: self.$newMemory)

                        Button(action: {
                            let memory = Memory(context: self.managedObjectContext)

                            memory.title = self.newMemory
                            memory.createdAt = Date()

                            do {
                                try self.managedObjectContext.save()
                            } catch {
                                print(error)
                            }

                            self.newMemory = ""
                        }) {
                            Image(systemName: "plus.circle.fill")
                                .foregroundColor(.green)
                                .imageScale(.large)
                        }
                    }
                }
                .font(.headline)

                Section(header: Text("My Memories")) {
                    ForEach(self.allMemories) { memoryItem in
                        MemoryRow(title: memoryItem.title, createdAt: memoryItem.createdAt)
                        //               /\ /\ /\ /\ /\ /\            /\ /\ /\ /\ /\ /\ /\
                        // >>> Error:
                        // >>> Value of type 'NSManagedObject' has no member 'createdAt'
                    }
                }
            }
            .navigationBarTitle(Text("Memories"))
            .navigationBarItems(trailing: EditButton())
        }
    }
}

struct MemoryView_Previews: PreviewProvider {
    static var previews: some View {
        MemoryView()
    }
}

And for reference, the Memory model in question is as follows:

import Foundation
import CoreData
import CoreLocation

// Define the CoreData Object Model.
// Ensuring that this class name matches
// the entity model name.
public class Memory: NSManagedObject, Identifiable {
    @NSManaged public var title: String?
    @NSManaged public var body: String?

    @NSManaged public var latitude: Double
    @NSManaged public var longitude: Double

    @NSManaged public var createdAt: Date?
    @NSManaged public var updatedAt: Date?

//    @NSManaged public var type: Type?
//    @NSManaged public var tags: [Tags]?
}

// Create an extension in which to place things out of the context
// of the initial class declaration within.
extension Memory {
    // Define a helper method for accessing a generated
    // CLLocationCoordinate2D instance from the objects
    // latitude and longitude values.
    var locationCoordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(
            latitude: latitude,
            longitude: longitude
        )
    }

    // Define a helper method that grabs all instances of a Memory
    // sorted by their created date in ascending order.
    static func allMemories() -> NSFetchRequest<Memory> {
        let request: NSFetchRequest<Memory> = Memory.fetchRequest() as! NSFetchRequest<Memory>

        let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)

        request.sortDescriptors = [sortDescriptor]

        return request
    }
}

Any help on this would be greatly appreciated as I am all out of ideas. A fresh head for tomorrow's problem me thinks!

Edit: Here is the MemoryRow class:

import SwiftUI

struct MemoryRow: View {
    var title: String = ""
    var createdAt: String = ""

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(title)
                    .font(.headline)

                Text(createdAt)
                    .font(.caption)
            }
        }
    }
}

struct MemoryRow_Previews: PreviewProvider {
    static var previews: some View {
        MemoryRow(title: "Test Title", createdAt: "Test Date")
    }
}
like image 913
Othyn Avatar asked Nov 27 '19 00:11

Othyn


3 Answers

In my case, it was because of the optional data type which is by default selected when we add an attribute in data model and therefore in NSManagedObjectClass it looks like this

@NSManaged public var name: String?

and of course, to resolve this one can either remove ? from the above line or use ?? nil coalescing operator when using that attribute like ClassName.name ?? "Unknown"

From the given answers and comments I went to check my attribute name in class for any typo and then I realised the issue. For now have to deal with this behaviour of SwiftUI that it won't show the actual error or will show on any other line.🤯

Hope this can help someone like me.

like image 56
iRiziya Avatar answered Nov 19 '22 20:11

iRiziya


set NSManagedObject type in closure argument.

ForEach(books, id: \.self) { book in 
     //
}

to

ForEach(books, id: \.self) { (book: Book) in
     //
}
like image 8
Hitesh Agarwal Avatar answered Nov 19 '22 20:11

Hitesh Agarwal


I'm actually a little confused as to what solved this issue for me. Here are a few things I did before it 'just worked'.

I don't know which one caused it, I just re-opened Xcode and the previously failing build was now building again unexpectedly.

So, in a haste to think what I did to fix it and using the Git diffs as some form of reference, this is me re-tracing my steps, in this order:

  1. Checked the View attachement in the SceneDelegate for the contentView, for which View the NSManagedObjectContext context is exposed onto. This was against the wrong View for me, it was attached to MemoryDetail() not MemoryList().

  2. Deleted the offending line:

MemoryRow(title: memoryItem.title!, createdAt: memoryItem.description)

and replacing it with just a filler Text("test"), saving and building the project successfully.

  1. Changing the sortDescriptor declaration from:
let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)

request.sortDescriptors = [sortDescriptor]

to

request.sortDescriptors = [
    NSSortDescriptor(key: "createdAt", ascending: true)
]
  1. Restarting Xcode (although did this multiple times to no avail during anyway).

  2. Staging, not committing, my tracked code changes.

  3. Deleting the filler Text("test") and manually typing back out the line:

MemoryRow(title: memoryItem.title!, createdAt: memoryItem.description)

In which code hint still does not work, but the line now builds fine, so you have to manually do it. And no, the unwrap force ! was not the cause of the error.


EDIT:

After using Git diffs and pasting in my original code, I think I know what the error is:

MemoryRow(title: memoryItem.title!, createdAt: memoryItem.createdAt)
//                                                        ^^^^^^^^^
// This is wrong ----------------------------------------//////////

It doesn't help as the error the compiler gives isn't directly relational to the actual problem. The error should be that memoryItem.createdAt isn't returning the correct type expected by the MemoryRow.

MemoryRow(title: memoryItem.title!, createdAt: "")
// Compiles fine

// And now the corrected version:
MemoryRow(title: memoryItem.title!, createdAt: "\(memoryItem.createdAt!)")

I think the problem is Xcode's somewhat hap-hazard errors sometimes.

I've since amended the MemoryRow to now accept a Date? type instead of String? to actually better describe what the object is accepting. Along with unwrapping, formatting and presenting the variables within that class.

like image 6
Othyn Avatar answered Nov 19 '22 20:11

Othyn