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")
}
}
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.
set NSManagedObject type in closure argument.
ForEach(books, id: \.self) { book in
//
}
to
ForEach(books, id: \.self) { (book: Book) in
//
}
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:
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()
.
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.
sortDescriptor
declaration from:let sortDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
request.sortDescriptors = [sortDescriptor]
to
request.sortDescriptors = [
NSSortDescriptor(key: "createdAt", ascending: true)
]
Restarting Xcode (although did this multiple times to no avail during anyway).
Staging, not committing, my tracked code changes.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With