Detecting # of Core Data objects in motionEnded:? - uitableview

I'm trying to detect the amount of objects I have in Core Data when the user shakes the device. When I try and call a NSFetchRequest inside of motionEnded:, the simulator crashes with an unknown error at main.
Is doing a fetch like this inside of motionEnded: possible?
The code I have so far:
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
// see if we have albums to upload
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Groups" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSUInteger group_count = [managedObjectContext countForFetchRequest:fetchRequest error:nil];
if (group_count == 0)
{
// show alertview
}
else
{
// show another alertview
}
}

This is perfectly fine. You can trigger a search and an alert view based on the search result triggered by a gesture, including a shake gesture.
Check what exactly your error is, so pass an error object to your countForFetchRequest method. Possibly your managedObjectContext is nil or you have some problem with your entity name. ("Groups" would be a bad name for an entity. "Group" is much more logical.)

Related

How to delete a chat message which was saved using xep-0136(Message Archiving)?

I want to provide user a functionality to delete single or multiple messages at a time using long-tap/select action.
I know you want to know what I have tried so far. But the thing is I haven't found anything regarding deleting messages to implement.
Any kind of help is appreciated!
You have to delete message from xmpp core database.
So xmpp had created "XMPPMessageArchiving_Message_CoreDataObject" named core database table and using this you can delete message.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:myAppdelObject.Obj_xmppManager.moc];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [mocObject executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *managedObject in items) {
[mocObject deleteObject:managedObject];
}
#Parthpatel1105's answer is right, though as #Bista says, it will not delete the messages permanently.
After performing the deletion, any deletion, either full deletion as #Parthpatel1105 does, or a single message, which would be the same but without the for loop and you'd have to find the single message to delete. You HAVE to save the storage context.
Which means, after doing:
for (NSManagedObject *managedObject in items) {
[mocObject deleteObject:managedObject];
}
You have to add a call to save,
In Swift (where I've used it):
mocObject.save()
In Objective-C, I think it would be something like:
[mocObject save:&error];

core data saving context

I am only seeing the saved context after I restart my app. I have two view controllers, one that allows a user to save a core data record and the other to display. When I save a new record, then go to the table view to see all the records created, I do not see the new record. This is how I create a record:
NSManagedObjectContext *context = [self managedObjectContext];
Property *myProperty = [NSEntityDescription
insertNewObjectForEntityForName:#"Property"
inManagedObjectContext:context];
myProperty.aptDescription = curProperty.description;
NSError *error;
[context save:&error];
[appDelegate saveContext];
And then i retreive it doing this:
ATAppDelegate *appDelegate = (ATAppDelegate *)[[UIApplication sharedApplication]delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Property" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
self.myProperties = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
But like I said the new object is not shown when I go back into the table view immediately after creating the record. What is the issue?
Sounds like you are not implementing the delegate methods of the NSFetchedResultsController properly. Although you did not share any of your view code so it is impossible to be certain.
In addition, after a save you are not checking the BOOL return from the -save:. You should always, always, always check that BOOL and at least log the error. You could be throwing an error and never know it.

Core data and multiple entites

Having a really hard time grasping the way core data works, and I'm hoping I can get some very basic help here.
I have two entities:
Profiles<-->>Events
I have successfully figured out how to add profiles, view profiles in table view and view events for a profile in a table view via a predicate fetch.
Now, here is where I am lost. Lets say I want to update an event in the Event entity. Do I have to do a fetch with predicate to create a Profiles object before I update the Event entity? Or can I just update the Event entity and somehow tell it which Profile it is associated with via the relationship?
Here is where I have hit the log jam:
// add new event
//NSLog(#"Adding New Event");
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Events"];
NSPredicate *predicate=[NSPredicate predicateWithFormat:#"ANY profile.profilename=[cd] %#",[self profilename]];
[fetchRequest setPredicate:predicate];
self.events = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
//insert event info
NSManagedObject *eventInfo = [NSEntityDescription insertNewObjectForEntityForName:#"Events" inManagedObjectContext:self.managedObjectContext];
///////// THIS IS WHERE I NEED HELP
}
// save the context
NSError *error = nil;
if (![managedObjectContext save:&error]){
NSLog(#"Error! %#",error);
}
I'm about ready just to create a flat file and work with that! It's driving me nuts!
EDIT - CHANGED CODE BELOW ***********************
// add new event
//NSLog(#"Adding New Event");
Events *newEvent = (Events *)[NSEntityDescription insertNewObjectForEntityForName:#"Events" inManagedObjectContext:managedObjectContext];
newEvent.eventdesc=self.eventDescTextField.text;
NSString *wkst = eventDescTextField.text;
NSNumber *wk = [NSNumber numberWithInteger: [wkst integerValue]];
newEvent.weeksout = wk;
So now I know I need to tell the Event entity to use the current profile..how do I access the relationship?
Looking at the code you've provided, I think you have a misconception about Core Data.
It looks like you are trying to get all the events related to a profile. You don't need to create ond perform a fetch request for this. Core Data is an object graph. Which means that if you have an object in an a managed object context, you get its related objects via it's relationships, you don't need to run a fetch request.

ManagedObjectContext cancel rollback not fully deleting inserted data

I've seen a bunch of responses on similar threads but after following their recommendations am still having a bizarre issue and am banging my head against the wall.
I have a table of people and have the option for the user to add an entry for another user by filling out a form. That constructor is shown here:
-(id)initWithContext:(NSManagedObjectContext *)context {
self = [super initWithNibName:#"FamilyMemberInfoViewController" bundle:[NSBundle mainBundle]];
if (self) {
self.managedObjectContext = context;
mainUser = [NSEntityDescription insertNewObjectForEntityForName:#"FamilyMember" inManagedObjectContext:context];
Details *userDetails = [NSEntityDescription insertNewObjectForEntityForName:#"Details" inManagedObjectContext:context];
mainUser.details = userDetails;
userDetails.familyMember = mainUser;
...etc.
and then if the user clicks the back button I call:
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
...
[self.managedObjectContext rollback];
[mainUser release];
self.managedObjectContext = nil;
...
And when it goes to the parent table view controller, it reloads the data with the below code and, as expected, the object is gone.
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (viewController == self) {
NSFetchRequest *familyMemberRequest = [NSFetchRequest new];
NSEntityDescription *familyDescription = [NSEntityDescription entityForName:#"FamilyMember" inManagedObjectContext:self.managedObjectContext];
familyMemberRequest.entity = familyDescription;
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"isMainUser == 0"];
[familyMemberRequest setPredicate:predicate];
NSError *error;
self.familyMembers = [managedObjectContext executeFetchRequest:familyMemberRequest error:&error];
NSLog(#"Retrieved family members! Count = %i", self.familyMembers.count);
[familyMemberRequest release];
}
}
HOWEVER - when I click away from the tab with that tableView and back to it, it runs the same code again and finds the cancelled object in the managed object context.
But - then when I re-run the app, it is nowhere to be found.
If I try creating another new person and backing out, it shows the correct number of entries initially (without any of the cancelled objects), and then when I click to another tab and back it shows only the most recent cancelled object in addition to the ones that should be there.
I've tried all manner of clearing the managed object context in the FamilyMemberInfoViewController: [context reset], [context rollback], [context deleteObject:], [context processPendingChanges] etc. I've tried experimentally loading the familyMembers array twice, and even when I click the back button it still finds the correct number of objects, but when I click a different tab and then back it shows the extra object (which then continues to show up if I go there and back).
Any ideas?

efficiently display 100,000 items using Core Data

I am using a NSFetchResultsController to display 100,000 + records in a UITableView. This works but it is SLOW, especially on an iPad 1. It can take 7 seconds to load which is torture for my users.
I'd also like to be able to use sections but this adds at least another 3 seconds onto the laod time.
Here is my NSFetchResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (self.clientsController != nil) {
return self.clientsController;
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Client" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
[request setPredicate:[NSPredicate predicateWithFormat:#"ManufacturerID==%#", self.manufacturerID]];
[request setFetchBatchSize:25];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"UDF1" ascending:YES];
NSSortDescriptor *sort2= [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObjects:sort, sort2,nil]];
NSArray *propertiesToFetch = [[NSArray alloc] initWithObjects:#"Name", #"ManufacturerID",#"CustomerNumber",#"City", #"StateProvince",#"PostalCode",#"UDF1",#"UDF2", nil];
[request setPropertiesToFetch:propertiesToFetch];
self.clientsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
return self.clientsController;
}
I have an index on ManufacturerID which is used in my NSPredicate. This seems like a pretty basic NSFetchRequest - anything I can do to speed this up? Or have I just hit a limitation? I must be missing something.
First: you can use the NSFetchedResultsController's cache to speed up display after the first fetch. This should quickly go down to a fraction of a second.
Second: you can try to display the only the first screenful and then fetch the rest in the background. I do this in the following way:
When the view appears, check if you have the first page cache.
If not, I fetch the first page. You can accomplish this by setting the fetch request's fetchLimit.
In case you are using sections, do two quick fetches to determine the first section headers and records.
Populate a second fetched results controller with your long fetch in a background thread.
You can either create a child context and use performBlock: or
use dispatch_async().
Assign the second FRC to the table view and call reloadData.
This worked quite well in one of my recent projects with > 200K records.
I know the answer #Mundi provided is accepted, but I've tried implementing it and ran into problems. Specifically the objects created by the second FRC will be based on the other thread's ManagedObjectContext. Since these objects are not thread safe and belong to their own MOC on the other thread, the solution I found was to fault the objects as they are being loaded. So in cellForRowAtIndexPath I added this line:
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
object = (TapCellar *)[self.managedObjectContext existingObjectWithID:[object objectID] error:nil];
Then you have an object for the correct thread you are in. One further caveat is that the changes you make to the objects won't be reflected in the background MOC so you'll have to reconcile them. What I did was make the background MOC a private queue MOC and the foreground one is a child of it like this:
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateManagedObjectContext setPersistentStoreCoordinator:coordinator];
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setParentContext:_privateManagedObjectContext];
}
Now when I make changes in the main thread, I can reconcile them easily by doing this:
if ([self.managedObjectContext hasChanges]) {
[self.managedObjectContext performBlockAndWait:^{
NSError *error = nil;
ZAssert([self.managedObjectContext save:&error], #"Error saving MOC: %#\n%#",
[error localizedDescription], [error userInfo]);
}];
}
I wait for it's return since I'm going to reload the table data at this point, but you can choose not to wait if you'd like. The process is pretty quick even for 30K+ records since usually only one or two are changed.
Hope this helps those who are stuck with this!

Resources