How to NSFetchedResultsController coding in Swift - objective-c

can't figure out how to rewrite the following method in swift
for a better learning curve, so i tried to translate this code. so lets select a more difficult method to do so. it has error handling, object init and parameter settings, an array of object pointers, selectors, nil, and self..
Objective-C:
-(NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *frC = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:#"Master"];
frC.delegate = self;
self.fetchedResultsController = frC;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
specially can't figure out how to call NSEntityDescription *entity.
while interpreting the specs i could not rewrite this correct..
swift:
var fetchedResultsController: NSFetchedResultsController {
var _fetchedResultsController: NSFetchedResultsController? = nil //??
var fetchRequest: NSFetchRequest? = nil // ??
var entity: NSEntityDescription = NSEntityDescription( /*. . .*/ ) //??
fetchRequest.entity(entity) //?? sure this is wrong
...
return _fetchedResultsController
}

This looks like the code you get when you set up a master-detail project with CoreData.
You can do the same, in Xcode 6, but choosing Swift as the language. In MasterViewController, you get :
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext)
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController? = nil
This looks like a line-to-line translation, I hope it helps.

try lazy:
lazy var fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest()
// Edit the entity name as appropriate.
let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext())
fetchRequest.entity = entity
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext(), sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
var error: NSError? = nil
if !aFetchedResultsController.performFetch(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
return aFetchedResultsController
}()

Related

Objective-C converting to Swift 3.1 getting error 'Any' is not convertible

I'm finally attempting to convert one of my Objective-C apps to Swift 3.1. I'm also taking a tutorial on Swift to help me out. However, I'm running into the following error when trying to convert a 'for in' loop to Swift that worked successfully in Obj-C. I've posted both the Swift and Objective-C code below and commented the line in Swift where I am getting the error. I am getting the error with
for managedObject: NSManagedObject in myResults{
The error states
'Any' is not convertible to 'MSManageObject'
Any help pointing me in the right direction is greatly appreciated.
//IN SWIFT 3.1
let context: NSManagedObjectContext? =
CoreDataHelper.shared().context
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
let entity = NSEntityDescription.entity(forEntityName:
"Exhibitors", in: context!)
fetchRequest.entity = entity
var myResults : NSArray = try!
CoreDataHelper.shared().context.fetch(fetchRequest) as NSArray
self.objects = myResults as! [Any]
if !(myResults != nil) || !((myResults.count) != nil) {
print("No Exhibitor objects found to be deleted!")
}
else {
//****Getting error 'Any' is not convertible to 'NSManagedObject'
for managedObject: NSManagedObject in myResults {
if !(managedObject.value(forKey: "fav") == "Yes") {
context?.deleteObject(managedObject)
var error: Error? = nil
// Save the object to persistent store
if !context?.save(error) {
print("Can't Save! \(error) \
(error?.localizedDescription)")
}
print("Exhibitor object deleted!")
}
}
}
//IN OBJECTIVE-C
NSManagedObjectContext *context = [[CoreDataHelper sharedHelper]
context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]
init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Exhibitors" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *myResults = [[[CoreDataHelper sharedHelper]
context] executeFetchRequest:fetchRequest error:nil];
self.objects = myResults;
if (!myResults || !myResults.count){
NSLog(#"No Exhibitor objects found to be deleted!");
}
else{
for (NSManagedObject *managedObject in myResults) {
if (![[managedObject valueForKey:#"fav"]
isEqualToString:#"Yes"]) {
[context deleteObject:managedObject];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error,
[error localizedDescription]);
}
NSLog(#"Exhibitor object deleted!");
}
}
}
The main issue is that your are annotating worse types than the compiler infers.
Don't do that. Don't annotate types unless the compiler tells you to do.
For example the managed object context is supposed to be non-optional
let context = CoreDataHelper.shared().context
Your fetch returns [NSManagedObject], never use NSArray and never cast distinct types up to more unspecified like [Any]
Simply write
let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
let entity = NSEntityDescription.entity(forEntityName: "Exhibitors", in: context)
fetchRequest.entity = entity
do { // its always recommended to do a good error handling !!
self.objects = try CoreDataHelper.shared().context.fetch(fetchRequest)
for managedObject in self.objects {
...
}
} catch { print(error) }
If objects is empty the loop will be skipped.
Instead of this:
var myResults : NSArray = try! CoreDataHelper.shared().context.fetch(fetchRequest) as NSArray
Do this:
let myResults : [NSManagedObject] = try! CoreDataHelper.shared().context.fetch(fetchRequest)
You should also use less ! operators, as they may crash your app, try using the guard let pattern or ? operators.

NSPredicate to fetch child objects from an entity

I have a simple data model with two entities. A parent entity called Character and a child entity called Statiscis. A Character can have multiple Statistics and each statistic can have only one parent, so the relationship is many to one.
From the view controller that displays the details of a Character I call to a new Table VC to list all the Statistics related to this Character. On this controller I have a nice SIGABRT when I try to build the fetchedResultsController: "Unable to generate SQL for predicate (character == currentCharacter) (problem on RHS)".
When I create the Table VC I send the managedObjectContext and the character displayed on the details VC through two properties (same name) on prepareForSegue, so in the table VC self.currentCharacter hosts an instance of a Character managed object.
#pragma mark - NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Statistic"];
// Stupid predicate :(
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"character == self.currentCharacter"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"statName"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
NSError *error = nil;
// Going to crash
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Core Data error: %#, %#", error, [error localizedDescription]);
abort();
}
return _fetchedResultsController;
}
Do not know how to create the predicate, and I tried unsuccessfully several ways
Perhaps:
[NSPredicate predicateWithFormat:#"character == %#", self.currentCharacter];
You want
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"character == %#", self.currentCharacter];

How to update existing object in Core Data?

When I insert new object I do with following code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
Favorits *favorits = [NSEntityDescription insertNewObjectForEntityForName:#"Favorits" inManagedObjectContext:context];
favorits.title = #"Some title";
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops");
}
How can I update existing object in core data?
Updating is simple as creating a new one.
To update a specific object you need to set up a NSFetchRequest. This class is equivalent to a SELECT statetement in SQL language.
Here a simple example:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Favorits" inManagedObjectContext:moc]];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];
// error handling code
The array results contains all the managed objects contained within the sqlite file. If you want to grab a specific object (or more objects) you need to use a predicate with that request. For example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title == %#", #"Some Title"];
[request setPredicate:predicate];
In this case results contains the objects where title is equal to Some Title. Setting a predicate is equal to put the WHERE clause in a SQL statement.
For further info I suggest you to read the Core Data programming guide and NSFecthRequest class reference.
Core Data Programming Guide
NSFecthRequest Class Reference
Hope it helps.
EDIT (snippet that can be used to update)
// maybe some check before, to be sure results is not empty
Favorits* favoritsGrabbed = [results objectAtIndex:0];
favoritsGrabbed.title = #"My Title";
// save here the context
or if you are not using a NSManagedObject subclass.
// maybe some check before, to be sure results is not empty
NSManagedObject* favoritsGrabbed = [results objectAtIndex:0];
[favoritsGrabbed setValue:#"My title" forKey:#"title"];
// save here the context
In both cases if you do a save on the context, data will be updated.
You have to fetch the object from the context, change the properties you desire, then save the context as you are in your example.
I hope this will help u. as it works for me.
NSMutableArray *results = [[NSMutableArray alloc]init];
int flag=0;
NSPredicate *pred;
if (self.txtCourseNo.text.length > 0) {
pred = [NSPredicate predicateWithFormat:#"courseno CONTAINS[cd] %#", self.txtCourseNo.text];
flag=1;
} else {
flag=0;
NSLog(#"Enter Corect Course number");
}
if (flag == 1) {
NSLog(#"predicate: %#",pred);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]initWithEntityName:#"Course"];
[fetchRequest setPredicate:pred];
results = [[self.context executeFetchRequest:fetchRequest error:nil] mutableCopy];
if (results.count > 0) {
NSManagedObject* favoritsGrabbed = [results objectAtIndex:0];
[favoritsGrabbed setValue:self.txtCourseName.text forKey:#"coursename"];
[self.context save:nil];
[self showData];
} else {
NSLog(#"Enter Corect Course number");
}
}
if you are a swift programmer this can help you :
if you want to delete a NSManagedObject
in my case ID is a unique attribute for entity STUDENT
/** for deleting items */
func delete(identifier: String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "STUDENT")
let predicate = NSPredicate(format: "ID = '\(identifier)'")
fetchRequest.predicate = predicate
do
{
let object = try context.fetch(fetchRequest)
if object.count == 1
{
let objectDelete = object.first as! NSManagedObject
context.delete(objectDelete)
}
}
catch
{
print(error)
}
}
if you want to update a NSManagedObject :
/** for updating items */
func update(identifier: String,name:String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "STUDENT")
let predicate = NSPredicate(format: "ID = '\(identifier)'")
fetchRequest.predicate = predicate
do
{
let object = try context.fetch(fetchRequest)
if object.count == 1
{
let objectUpdate = object.first as! NSManagedObject
objectUpdate.setValue(name, forKey: "name")
do{
try context.save()
}
catch
{
print(error)
}
}
}
catch
{
print(error)
}
}
I saw an answer in Objective-C which helped me. I am posting an answer for Swift users -
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let updateCont = appDelegate?.persistentContainer.viewContext
let pred = NSPredicate(format: "your_Attribute_Name = %#", argumentArray : [your_Arguments])
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "your_Entity_Name")
request.predicate = pred
do {
let resul = try updateCont?.fetch(request) as? [NSManagedObject]
let m = resul?.first
m?.setValue(txtName.text, forKey: "your_Attribute_Name_Whose_Value_Should_Update")
try? updateCont?.save()
} catch let err as NSError {
print(err)
}

Getting the maximum value of a value in an Entity

I'm trying to get the maximum value for an attribute in an Entity in core data. Apple has a nice example here of how to do this; however, it doesn't work for me. I have 10 objects in my moc and the following code always returns an array of size 0. Can anyone tell me what I am doing wrong? Thanks!
NSManagedObjectContext* moc = [self managedObjectContext];
// set the idx to the maximum value
NSFetchRequest* request = [[NSFetchRequest alloc] init];
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Transaction"
inManagedObjectContext:moc];
[request setEntity:entity];
// Specify that the request should return dictionaries.
[request setResultType:NSDictionaryResultType];
// Create an expression for the key path.
NSExpression* keyPathExpression = [NSExpression expressionForKeyPath:#"idx"];
// Create an expression to represent the minimum value at the key path 'creationDate'
NSExpression* maxExpression = [NSExpression expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:keyPathExpression]];
// Create an expression description using the minExpression and returning a date.
NSExpressionDescription* expressionDescription = [[NSExpressionDescription alloc] init];
// The name is the key that will be used in the dictionary for the return value.
[expressionDescription setName:#"maxIdx"];
[expressionDescription setExpression:maxExpression];
[expressionDescription setExpressionResultType:NSInteger32AttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Execute the fetch.
NSError* error = nil;
NSArray* objects = [moc executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error.
}
else {
if ([objects count] > 0) {
int newIdx = [[[objects objectAtIndex:0] valueForKey:#"maxIdx"] intValue] + 1;
[self setPrimitiveIdx:[NSNumber numberWithInt:newIdx]];
} else {
[self setPrimitiveIdx:[NSNumber numberWithInt:1]];
}
}
Your code looks right to me, so check the following
You actually have Transaction objects in your store. Just do a normal fetch from the same context.
Could you be doing this before you set up the context?
Check to see if there's anything in error -- just log [error localDescription]
Is NSInteger32AttributeType right for idx?
Is idx spelled correctly? Is Transaction? Meaning, they match your model.
PS: It won't matter for the result, but hopefully you have the releases from the code sample

Cocoa Core Data efficient way to count entities

I read much about Core Data.. but what is an efficient way to make a count over an Entity-Type (like SQL can do with SELECT count(1) ...). Now I just solved this task with selecting all with NSFetchedResultsController and getting the count of the NSArray! I am sure this is not the best way...
I don't know whether using NSFetchedResultsController is the most efficient way to accomplish your goal (but it may be). The explicit code to get the count of entity instances is below:
// assuming NSManagedObjectContext *moc
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:entityName inManagedObjectContext:moc]];
[request setIncludesSubentities:NO]; //Omit subentities. Default is YES (i.e. include subentities)
NSError *err;
NSUInteger count = [moc countForFetchRequest:request error:&err];
if(count == NSNotFound) {
//Handle error
}
[request release];
To be clear, you aren't counting entities, but instances of a particular entity. (To literally count the entities, ask the managed object model for the count of its entities.)
To count all the instances of a given entity without fetching all the data, the use -countForFetchRequest:.
For example:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: [NSEntityDescription entityForName: entityName inManagedObjectContext: context]];
NSError *error = nil;
NSUInteger count = [context countForFetchRequest: request error: &error];
[request release];
return count;
Swift
It is fairly easy to get a count of the total number of instances of an entity in Core Data:
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "MyEntity")
let count = context.countForFetchRequest(fetchRequest, error: nil)
I tested this in the simulator with a 400,000+ object count and the result was fairly fast (though not instantaneous).
I'll just add that to make it even more efficient... and because its just a count, you don't really need any property value and certainly like one of the code examples above you don't need sub-entities either.
So, the code should be like this:
int entityCount = 0;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"YourEntity" inManagedObjectContext:_managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesPropertyValues:NO];
[fetchRequest setIncludesSubentities:NO];
NSError *error = nil;
NSUInteger count = [_managedObjectContext countForFetchRequest: fetchRequest error: &error];
if(error == nil){
entityCount = count;
}
Hope it helps.
I believe the easiest and the most efficient way to count objects is to set NSFetchRequest result type to NSCountResultType and execute it with NSManagedObjectContext countForFetchRequest:error: method.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:entityName];
fetchRequest.resultType = NSCountResultType;
NSError *fetchError = nil;
NSUInteger itemsCount = [managedObjectContext countForFetchRequest:fetchRequest error:&fetchError];
if (itemsCount == NSNotFound) {
NSLog(#"Fetch error: %#", fetchError);
}
// use itemsCount
I wrote a simple utility method for Swift 3 to fetch the count of the objects.
static func fetchCountFor(entityName: String, predicate: NSPredicate, onMoc moc: NSManagedObjectContext) -> Int {
var count: Int = 0
moc.performAndWait {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: entityName)
fetchRequest.predicate = predicate
fetchRequest.resultType = NSFetchRequestResultType.countResultType
do {
count = try moc.count(for: fetchRequest)
} catch {
//Assert or handle exception gracefully
}
}
return count
}
It's really just this:
let kBoat = try? yourContainer.viewContext.count(for: NSFetchRequest(entityName: "Boat"))
"Boat" is just the name of the entity from your data model screen:
What is the global yourContainer?
To use core data, at some point in your app, one time only, you simply go
var yourContainer = NSPersistentContainer(name: "stuff")
where "stuff" is simply the name of the data model file.
You'd simply have a singleton for this,
import CoreData
public let core = Core.shared
public final class Core {
static let shared = Core()
var container: NSPersistentContainer!
private init() {
container = NSPersistentContainer(name: "stuff")
container.loadPersistentStores { storeDescription, error in
if let error = error { print("Error loading... \(error)") }
}
}
func saveContext() {
if container.viewContext.hasChanges {
do { try container.viewContext.save()
} catch { print("Error saving... \(error)") }
}
}
}
So from anywhere in the app
core.container
is your container,
So in practice to get the count of any entity, it's just
let k = try? core.container.viewContext.count(for: NSFetchRequest(entityName: "Boat"))
In Swift 3
static func getProductCount() -> Int {
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Product")
let count = try! moc.count(for: fetchRequest)
return count
}
If you want to find count for specific predicated fetch, i believe this is the best way:
NSError *err;
NSUInteger count = [context countForFetchRequest:fetch error:&err];
if(count > 0) {
NSLog(#"EXIST");
} else {
NSLog(#"NOT exist");
}
Swift 5 solution:
var viewContext: NSManagedObjectContext!
do {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "insertName")
let count = try viewContext.count(for: fetchRequest)
print("Counted \(count) objects")
}
catch {
print("Error")
}