Swift & Core Data unwrapping fetchedresults - swift

I'm pretty new to iOS and trying to understand how to work with Core Data. The basic challenge I struggle with is accessing the data from a fetchResult outside of a view, as if it were in an array or a database query result (which I thought this was)
Example:
Core Data Entity Foo has a 1:1 relationship with Core Data Entity Bar. I create a new Foo that needs to attach to an existing Bar.
I do a fetch<Bar> with a predicate that returns a fetchedResult object. I need to get access to the Bar object inside the fetchedResult object so that I can assign it:
newFoo.bar = fetched<Bar> doesn't work, and I can't figure out how to get to the Bar - I've spent probably 10 hours going through tutorials and Apple docs and can't find any way to unwrap it or access it - convert it or access it as if it were just a simple array of objects.
At this point I'm about to give up - I'm creating duplicate classes for the entities and when the app inits I load ALL of the Core Data from every entity and map it to Arrays that can be easily accessed, then just update Core Data with changes. That can't be how this is supposed to work.
What am I missing? This should be easy.
EDIT===========================
Added a simplified code section:
import Foundation
import CoreData
struct Create: View {
#Environment(\.managedObjectContext) var viewContext
#EnvironmentObject var show: ViewLogic
func newOnDemand() {
let newT = T(context: viewContext)
let newS = S(context: viewContext)
let existingPlace = Place.idRequest(id: placeId) <-- returns fetchedResults<Place>, I need Place or [Place]
newT.place = existingPlace <--- Place object required here. HOW?
newT.S = newS <--- New Objects. Simple.
do {
// print("Saving session...")
try viewContext.save()
} catch let error as NSError {
print("Failed to save session data! \(error): \(error.userInfo)")
}
=========================== fetch
class Place:
static func idRequest(id: UUID) -> NSFetchRequest<Place> {
let request: NSFetchRequest<Place> = Place.fetchRequest()
request.predicate = NSPredicate(format: "id == %#", id)
request.sortDescriptors = [NSSortDescriptor(key: "sortOrder", ascending: true)]
request.fetchLimit = 1 // use to specify specific queries. Position.
return request
=========================== entity class
import Foundation
import CoreData
extension T {
#nonobjc public class func fetchRequest() -> NSFetchRequest<T> {
return NSFetchRequest<T>(entityName: "T")
}
#NSManaged public var id: UUID
#NSManaged public var place: Place?
}

According to the code the method idRequest returns a fetch request, not any fetchresults. You have to call fetch on the managed object context passing the request to fetch the data
let request = Place.idRequest(id: placeId)
do {
if let existingPlace = try viewContext.fetch(request).first {
newT.place = existingPlace
// do other things
}
} catch {
print(error)
}

Related

SwiftUI get total item (row in SQL terms) count from Core Data entity [duplicate]

This question already has answers here:
Cocoa Core Data efficient way to count entities
(10 answers)
Closed 1 year ago.
Using SwiftUI (2021) XCode 13.1 / iOS 14.5+ and Core Data I am trying to get the most efficient total count within an entity.
This from an SQL perspective is like getting the total number of rows from a table.
The Entity will never contain many 'rows' probably never more than 40.
I have Googled lots of examples (including here) but some are old and most don't work. It seems to be something to do with countForFetchRequest but in that I'm not 100% sure.
I have already configured the container and entity that simply contains an id field and a text field.
I'm still new to Core Data so hopefully I have the terminology correct but from various examples this is what I have come up with. (noting I need the data to persist after the app is closed).
import CoreData
class FooCoreDataService {
private let container: NSPersistentContainer
private let containerName: String = "FooCoreDataModel"
private let entityName: String = "FooEntity"
init() {
container = NSPersistentContainer(name: containerName)
container.loadPersistentStores { (_, error) in
if let error = error {
print("Error loading Core Data! \(error)")
}
}
}
// Get total item ('Row') count of FooEntity
func getFooCount() -> Int {
var countOfItems: Int = 0
// NOT SURE WHAT GOES HERE
let fetchRequest = NSFetchRequest ????
// SOMETHING TO DO WITH countForFetchRequest ????
return countOfItems
}
}
Is this what you need?
static func getFooCount() -> Int {
let object = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Product")
let countOfItems = try! object.count(for: fetchRequest)
return countOfItems
}

How to retrieve data from CloudKit using SwiftUI

I am using SwiftUI to make an app and storing the data on ICloud. Because all the code I can find relates to Swift and viewDidLoad and TableView, however this do not apply. I have written code and seems to retrieve it but will not return it to the ObservableObject to be able to display in the SwiftUI
The ObservableObject file:-
import SwiftUI
import Combine
final class ObservedData: ObservableObject {
#Published var venues = venuesData
}
The query to retrieve data
import SwiftUI
import CloudKit
var venuesData: [Venue] = loadVenues()
func loadVenues() -> [Venue] {
var data = [Venue]()
let pred = NSPredicate(value: true)
let sort = NSSortDescriptor(key: "id", ascending: true)
let query = CKQuery(recordType: "DeptfordHopData", predicate: pred)
query.sortDescriptors = [sort]
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["id","name", "address"]
operation.resultsLimit = 50
var newVenues = [Venue]()
operation.recordFetchedBlock = { record in
let venue = Venue()
venue.racordID = record.recordID
venue.id = record["id"]
venue.name = record["name"]
venue.address = record["address"]
newVenues.append(venue)
}
operation.queryCompletionBlock = {(cursor, error) in
DispatchQueue.main.async {
if error == nil {
data = newVenues
} else {
print(error!.localizedDescription)
}
}
}
CKContainer.default().publicCloudDatabase.add(operation)
return data
}
I have got data showing when do break in data but does not pass to venueData
The query operation runs asynchronously. When you add the operation to the database, the framework runs the query in the background and immediately returns control to your function, which promptly returns data, which is still set to the empty array.
To fix this, you either need to wait for the operation to complete using Grand Central Dispatch (not recommended if this is called directly from UI code, as it will block your application), or you need to rewrite loadVenues so it accepts a callback that you can then invoke inside queryCompletionBlock. The callback you provide to loadVenues should save the array of venues somewhere where SwiftUI will detect the change.

Core Data Object was written to, but never read

As I try to update an existing entry in my Core Data DB, I fetch the desired item by id, change it to a new item and save in context.
However, when I fetch the object and replace it, I get the warning "Core Data Object was written to, but never read." It does make sense since I'm not really using that object, but as I understand it, just giving it a value saves it in Core Data.
static var current: User? {
didSet {
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
CoreDataManager.saveInContext()
}
}
static func get(with id: String?) -> User? {
guard let id = id else { return nil }
let request: NSFetchRequest = User.fetchRequest()
let predicate = NSPredicate(format: "id = %#", id)
request.predicate = predicate
do {
let users = try CoreDataManager.managedContext.fetch(request)
return users.first
} catch let error {
print(error.localizedDescription)
return nil
}
}
I want to make sure, is this the recommended process to overwrite a value in Core Data, or am I doing something wrong?
This section
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
seems just updating local variable userInCoreData, not User object in Core Data.
So the warning says "you fetched data from core data and set to a variable, but you set another value to the variable soon, never use the first value from core data. Is it OK?"
What you really want to do is something like this?
if var userInCoreData = User.get(with: current?.id), let current = current {
userInCoreData.someValue = current.someValue
userInCoreData.anotherValue = current.anotherValue
}

How to Save to a Custom Join Table Core Data (many-to-many) without unique predicate - ManagedObjectID (Swift)?

I will be super thankful for any help. How can I save instances to a join table without a unique identifier as a predicate? Can I use the managed object id to check if the item exists already?
I'm building an app with different exercise plans. Each plan holds many exercise, and an exercise can belong to many plans. I have structured my data model to include a custom join table so that I can query the completion status of an exercise from within one plan.
I'm sourcing my data from a json file and would like to save it to core data. I'm able to correctly save my CoreExercise, and CorePlan tables, however am having difficulty understanding how to save the instance of the object in the intermediate join table, since I'm unsure of what predicate to use.
I've written a class function to check if the instance exists, and to save it if it doesn't.
class CoreExercisePlan: NSManagedObject {
class func coreExercisesForExercisePlan(exerciseInfo: Exercise, planName: String, inManagedObjectContext context: NSManagedObjectContext) -> CoreExercisePlan? {
let request = NSFetchRequest(entityName: "CoreExercisePlan")
request.predicate = NSPredicate() // Search for ObjectID here? / How?
if let exercisePlan = (try? context.executeFetchRequest(request))?.first as? CoreExercisePlan {
print("we have this exercise plan already saved")
return exercisePlan
} else if let exercisePlan = NSEntityDescription.insertNewObjectForEntityForName("CoreExercisePlan", inManagedObjectContext: context) as? CoreExercisePlan {
exercisePlan.status = 0
exercisePlan.progress = 0
print("we are creating new object")
return exercisePlan
}
return nil
}
private func updateDatabaseWithExercisePlans(){
managedObjectContext?.performBlock {
// Array of exercises for each plan:
let coffeePlanExercises = self.coffeeExercises
let subwayPlanExercises = self.subwayExercises
for exercise in coffeePlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "coffee", inManagedObjectContext: self.managedObjectContext!)
}
for exercise in subwayPlanExercises {
_ = CoreExercisePlan.coreExercisesForExercisePlan(exercise, planName: "subway", inManagedObjectContext: self.managedObjectContext!)
}
do {
try self.managedObjectContext?.save()
} catch let error {
print("printing error here: \(error)")
}
}
}
Is there a way to get the objectID of the instance in the join table, and use that as a predicate? Thanks!

"'[AnyObject]' is not convertible to '[Dog]?'" error when fetching data from CoreData

I'm having issues when fetching data from Core Data. I'm sorry if my terminology is not specific or precise, but I'm a newbie with Core Data.
I'm on Xcode 7 beta and Swift 2. Device OS is set at 8.4.
The line:
let result = try managedContext.executeFetchRequest(dogFetch) as [Dog]?
Is giving me this error: "'[AnyObject]' is not convertible to '[Dog]?'".
Full code is here:
do {
let dogEntity = NSEntityDescription.entityForName("Dog", inManagedObjectContext: managedContext)
let dog = Dog(entity: dogEntity!, insertIntoManagedObjectContext: managedContext)
let dogName = "Fido"
let dogFetch = NSFetchRequest(entityName: "Dog")
dogFetch.predicate = NSPredicate(format: "name == %#", dog)
let result = try managedContext.executeFetchRequest(dogFetch) as [Dog]?
if let dogs = result {
if dogs.count == 0 {
currentDog = Dog(entity: dogEntity!, insertIntoManagedObjectContext: managedContext)
currentDog.name = dogName
} else {
currentDog = dogs[0]
}
}
} catch let fetchError as NSError {
print("Could not fetch \(fetchError)")
}
}
I've attached an image to show what my Core Data model looks like:
Core Data classes are "standard", automatically created from Editor > Create NSManagedObjectSubclass:
Dog+CoreDataProperties.swift:
import Foundation
import CoreData
extension Dog {
#NSManaged var name: String?
#NSManaged var walks: NSOrderedSet?
}
Dog.swift:
import Foundation
import CoreData
#objc(Dog)
class Dog: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
and basically the same for Walk+CoreDataProperties.swift and Walk.swift.
--
Edit: the selected answer solves the question. My idea is that either this is a change in Swift 2.x vs Swift 1.x, or the tutorial from which I got the code was wrong.
You should do
managedContext.executeFetchRequest(dogFetch) as? [Dog]
It's because casting here is not guarateed so you should protect yourself against crash (by casting with as?. Above statement means that the executeFetchRequest return value should be casted to [Dog] (an array which holds Dog instances, and it can be empty by the way), but only when possible - otherwise casting will fail and you can handle it properly in catch statement.