indexPathForObject returns nil - ios

I am passing an object from my UITableViewController to a singleton, then back to my UITableViewController through a notification. The UITableViewController uses a NSFetchedResultsController. It then want the indexPath of the Object in the fetchedResultsController.
Object *obj = (Object *)notification.object;
NSIndexPath *index = [self.fetchedResultsController indexPathForObject:obj];
I set the notification in the AFNetworking/AFDownloadRequestOperation progress block:
- (void)downloadObject:(Object *)object
{
........
[operation1 setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
////// Sending Notification
////// Try to send notification only on significant download
totalBytesRead = ((totalBytesRead *100)/totalBytesExpectedToReadForFile);
NSLog(#"TOTAL BYTES IN PROGRESS BLOCK: %llu",totalBytesRead);
NSMutableDictionary *userDictionary = [[NSMutableDictionary alloc]init];
[userDictionary setObject:[NSNumber numberWithLongLong:totalBytesRead] forKey:#"progress"];
[userDictionary setObject:[NSNumber numberWithLongLong:totalBytesExpectedToReadForFile] forKey:#"totalBytes"];
[[NSNotificationCenter defaultCenter] postNotificationName:[NSString stringWithFormat:#"DownloadProgress%#",object.uniqueID] object:object userInfo:userDictionary];
}
There is only one object in [self.fetchedResultsController fetchedObjects]; and it matches the object the passed back in the notification. The index is always nil, but the fetchedResultsController is not nil, and the [self.fetchedResultsController fetchedObjects] returns the correct object. Why does indexPathForObject nil?

I had the same problem. In the configuration of my NSFetchedResultsController the sectionNameKeyPath was set to refer to an (optional) related object.
It turned out that this relation must be fulfilled (that is the related object must not be nil).
After ensuring, that every relation of the sectionNameKeyPath is fulfilled, the indexPathForObject method worked again as expected.

Looks to me like you are attempting to locate an object in a table view from the notification object...
You should be calling the index path method on your table view object, not your notification object.
Try matching your local variable:
Object *obj = (Object *)notification.object;
with:
[self.fetchedResultsController fetchedObjects];
(if it is the only one that should be relatively easy)...
NSArray *arrayFetchedObjects = [self.fetchedResultsController fetchedObjects];
Object *tableObject = [arrayObjects lastObject];
(if there is more than one then you might need to filter the arrayFetchedObjects but that is another question)
then locating the position of that fetched object in the table view:
NSIndexPath *indexPath = [self.fetchedResultsController indexPathForObject:tableObject];

Related

UIButton that increments an array from Core Data, done from another class

I have a button that whenever it is pressed is supposed to increment by one, which as a result will display a new string into a textfield. I have a Core Data manager class that contains a method which is supposed to retrieve my arrays, however whenever I press the button the app crashes. I'm left with an error stating ...
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UsefulCodes rangeOfCharacterFromSet:]: unrecognized selector sent to instance 0x6000000a3f60'"
"UsefulCodes" is the name of my Core Data entity, and "codeName" and "codeDescription" are the attributes fro my presetList and codeDescArray respectively. I think its not working because I'm trying to put a Core Data object into a textfield, but all the objects in the arrays are strings, how would I get it to pass successfully into the textfield? I'll post relevant code below.
From CoreDataManager -
#implementation CoreDataManager
+(CoreDataManager*)sharedInstance {
static CoreDataManager *sharedObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedObject = [[CoreDataManager alloc] init];
});
return sharedObject;
}
-(void)storeListFirstTime {
//http://morsecode.scphillips.com/morse.html
NSArray* presetList = [NSArray arrayWithObjects:#"AS",
#"BCNU",
#"CL",
#"CT",
#"CUL",
#"K",
#"QSL",
#"QSL?",
#"QRX?",
#"QRV",
#"QRV?",
#"QTH",
#"QTH?",
#"R",
#"SN",
#"SOS",
#"73",
#"88",
nil];
NSArray* codeDescArray = [NSArray arrayWithObjects:#"Wait",
#"Be seeing You",
#"Going off air",
#"Start Copying",
#"See you later",
#"Over",
#"I acknowledge receipt",
#"Do you acknowledge",
#"Should I wait",
#"Ready to copy",
#"Are you ready to copy?",
#"My location is ...",
#"What is your location?",
#"Roger",
#"Understood",
#"Distress message",
#"Best regards",
#"Love and kisses",
nil];
//Saves the initial list of items
for(int i = 0; i < presetList.count; i++) {
NSManagedObjectContext *context = [self context];
UsefulCodes *codeObj = [NSEntityDescription insertNewObjectForEntityForName:#"UsefulCodes"
inManagedObjectContext:context];
codeObj.codeName = [presetList objectAtIndex:i];
codeObj.codeDescription = [codeDescArray objectAtIndex:i];
NSLog(#"CODEOBJ: %#", codeObj);
}
[self saveContext];
}
//THIS IS THE METHOD I AM CALLING FOR MY BUTTON METHOD
-(NSArray*)fetchAllRecords {
NSManagedObjectContext *context = [self context]; //this context is conected to persistant container
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"UsefulCodes"];
NSArray *records = [context executeFetchRequest:request
error:&error];
if(error == nil && records.count > 0) {
return [records copy];
}
else {
//Handle error by returning new array
return [NSArray new];
}
}
The above code just sets the initial values I hard-coded into my arrays (I'm only interested in getting the values from presetList array to display in textfield) as well as accommodating any new codes and descriptions I've added to any arrays. Now here is the method in my "MainViewController" responsible for cycling through my array.
In header file I declare a property called num.
#property(nonatomic)int num;
This is the method in implementation file.
- (void)getQuickMorseCode {
NSArray *array = [[CoreDataManager sharedInstance] fetchAllRecords];
if(_num == 0 || _num > 0) {
if(_num >= array.count){
_num = 0;
}
//GETTING THE SPECIFIC INDEX OF THE ARRAY, THIS IS WHERE THE PROBLEM LIES
self.morseTextfield.text = array[_num];
[self convertInput:self.morseTextfield.text];
[self buttonAppear];
_num++;
}
}
My goal is to display the strings that are in the presetList array in my CoreDataManager file, but I'm unable to get that to pass into my textfield. Any insight to my problem is welcomed.
Your fetchAllRecords method returns an array of UsefulCodes objects. So each element of array is a UsefulCodes object. You cannot therefore assign the element to the text property of a textfield. That's the cause of your error.
You need instead to extract the relevant string from the UsefulCodes object. From your other code, you store the presetList values in the codename attribute of the UsefulCodes objects:
codeObj.codeName = [presetList objectAtIndex:i];
So, reverse that process to obtain the relevant string from the codename attribute:
UsefulCodes *codeObj = (UsefulCodes *)array[_num];
NSString *presetListValue = codeObj.codeName;
self.morseTextfield.text = presetListValue;

Getting the value of an object in an array

Thanks to a more expert user, I have implemented a method to sort core data objects inside a table view section. The sorting method inside the section is different to the sorting method of the table view. The latest is done by a NSFetchedResultsController. But now I don't know how to get the values from the sorted NSArray.
This is the method that sorts the core data objects inside a section:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"priority" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSLog(#"INDEX********* = %ld", (long)index);
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted;
}
And this is the code added to the cellForRowAtIndexPat method to get the NSArray from the sortedSectionForIndex method:
NSArray *sorted = [self sortedSectionForIndex:[indexPath section]];
id object = [sorted objectAtIndex:[indexPath row]];
NSLog(#"OBJECTV= %#",object);
Now, I would need to know how to show the values from the array to be put as cell.textlabel:
A second question is that I don't know if getting the core data object from the array will change the way to handle them for the rest of the methods. I mean, before implementing this sorting method, when the user clicks on a row, a detail view from the selected object was shown. Now getting the objects from an array and not from the NSFetchedResultsController, I am not sure if it will continue working as before.
I would change the line
id object = . . .
To
NSManagedObject *object = . . .
And then
cell.textLabel = [object valueForKey:#"todoName"];

Set a lastModificationDate attribute after NSManagedObjectsDidChangeNotification creates an infinite loop

I added a lastModifiedDate attribute to all my entities to avoid duplicates when syncing the UIManagedDocument with iCloud, something that I found it can happen if I create new entities with an offline device (iPad) and, at the same time, I create the same entities using another online device (iPhone).
I wanted to set this attribute whenever an object changes so I subscribed for NSManagedObjectContextObjectsDidChangeNotification. The code I wrote to set the lastModifiedDate creates an infinite loop because by setting the lastModificationDate attribute it creates a change that will be notified again by NSManagedObjectContextObjectsDidChangeNotification and so on...
Is it possible to fix it? Is there a better way to accomplish my goal? Should I subclass managedObjectContext and override willSave:?
//At init...
[[NSNotificationCenter defaultCenter] addObserver:applicationDatabase
selector:#selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:applicationDatabase.managedDocument.managedObjectContext];
(void) objectsDidChange: (NSNotification*) note
{
// creates an infinite loop
NSDate *dateOfTheLastModification = [NSDate date];
NSMutableArray *userInfoKeys = [[note.userInfo allKeys] mutableCopy];
for(int i=0; i< userInfoKeys.count;i++){
NSString *key = [userInfoKeys objectAtIndex:i];
if([key isEqualToString:#"managedObjectContext"]){
[userInfoKeys removeObject:key];
}
}
for(NSString *key in userInfoKeys){
NSArray *detail = [note.userInfo objectForKey:key];
for (id object in detail){
[object setValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
}
}
To avoid the infinite loop, you could set the last modification date using the
primitive accessor:
[object setPrimitiveValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
because that does not fire another "change" notification. But that also implies that
no observers will see the change.
Overriding willSave in the managed object subclass would suffer from the same problem.
The Apple documentation for willSave states:
For example, if you set a last-modified timestamp, you should check
whether either you previously set it in the same save operation, or
that the existing timestamp is not less than a small delta from the
current time. Typically it’s better to calculate the timestamp once
for all the objects being saved (for example, in response to an
NSManagedObjectContextWillSaveNotification).
So you should register for NSManagedObjectContextWillSaveNotification instead,
and set the timestamp on all updated and inserted objects in the managed object
context. The registered method could look like this:
-(void)contextWillSave:(NSNotification *)notify
{
NSManagedObjectContext *context = [notify object];
NSDate *dateOfTheLastModification = [NSDate date];
for (NSManagedObject *obj in [context insertedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
}
for (NSManagedObject *obj in [context updatedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
}
}
This assumes that all your entities have a lastModifiedDate attribute, otherwise
you have to check the class of the objects.

After executing fetch request properties of fetched object are nil

I am fetching an array of managed objects from some entity. After executeFetchRequest returns I NSLog elements and their properties and everything is fine. After I return my newly generated array and try to use it in a background thread or some other method the properties of managed objects inside the array become nil. This is the code:
Utakmice -NSManagedObject subclass
- (NSArray*)ucitajPodatke:(NSDate*)zaDatum drzavaId:(int)_drzavaId
{
NSManagedObjectContext *con = [[NSManagedObjectContext alloc] init];
[con setPersistentStoreCoordinator:persistentStoreCoordinator];
[con setStalenessInterval:0];
[con setUndoManager:nil];
// create request and predicate
// set return result type to NSManagedObjectResultType
...
...
return fetchedObjects; -> This works cause I can log everything and all values R OK...
}
-(void)SomeMethod
NSArray *array = [self ucitajPodatke:danas drzavaId:self.drzavaId];
Utakmice *tekma = [array objectAtIndex:0];
NSLog(#"%i", [tekma.uniqueId intValue]); -> everything is fine
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.35 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (array != nil && [array count]>0)
{
Utakmice *tekma1 = [array objectAtIndex:0];
DLog(#"%#", tekma1.uniqueId);
DLog(#"%i", [tekma1.uniqueId intValue]); -> all properties have nil value
...
...
Any suggestions?? I really dont know where to go from here...
One more thing. In ucitajPodatke method, when I set return result type to NSDictionaryResultType -> everything is ok... (I need managed objects cause I need relations)...
Thx in advanced
The first thing you need to do, to keep strong reference of fetched array,
after that you can do your dispatch.
That will be good to call
[self performSelector:#selector(someMethod) withObject:nil afterDelay:0.35];
instead of dispatching.
This issue also can be related with "Data faulting".
The data is being fetched when you access to object fields.
If you want to fully fetch objects without faulting you can use
NSFetchRequest *request = ...;// your fetch request here
[request setReturnsObjectsAsFaults:YES];
// Fetch here
Ok I finally google it:
Core Data - sharing NSManagedObjects among multiple threads
It appears that U can not pass managed objects between threads, Instead U should pass managed object id's

How do you pass core data corresponding to a row selected in a table?

I am learning how to use Core Data. I have an app that fills out all the variable of an entity labeled User. I then take these users and load them to a table. At this point the users can be selected and pushed to a new view controller at which point I generate a PDF file of the user selected. So I think I am misunderstanding what it is I have to pass to the view controller for it to access the core data selected in the table. Here is what I have in my table view controller.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.spinner startAnimating];
ReportPDFViewController *reviewViewController = [[ReportPDFViewController alloc] init];
reviewViewController.userInfo = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
[self.navigationController pushViewController:reviewViewController animated:YES];
}
Then the next view states this
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithTitle:#"Email PDF"
style:UIBarButtonItemStylePlain
target:self
action:#selector(emailPDF)];
self.navigationItem.rightBarButtonItem = barButton;
TrafficSignalProAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
context = [appDelegate managedObjectContext];
// Do any additional setup after loading the view.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entitydesc];
NSError *error;
NSArray *matchingData = [userInfo executeFetchRequest:request error:&error];
NSString *Intersection;
NSString *currentDay;
for (NSManagedObject *obj in matchingData) {
Intersection = sendIntersection;
currentDay = sendDate;
}
NSString* fileName = [self getPDFFileName];
[PDFRenderer drawPDF:fileName intersectionSearch:Intersection dateSearch:currentDay];
[self showPDFFile];
self.navigationItem.title = #"Report";
}
So I'm trying to pass the NSManagedObjectContext of the selected row to then load. I am really lost after that. I'm not sure if passing the managed object context is right and if it is I don't know what is wrong with the code in the ReportPDFViewController. I have looked through all the tutorials I can find. I have a limited programming background so any help would be greatly appreciated.
reviewViewController.userInfo = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
This sets userInfo to an object of type NSManagedObject (or a subclass).
NSArray *matchingData = [userInfo executeFetchRequest:request error:&error];
This is using userInfo as if it's a NSManagedObjectContext. I would imagine you get an invalid selector error here.
What is the actual type of the userInfo attribute? It should be NSManagedObject.
You do not need to do a fetch request in your viewDidLoad. Core Data is not a database. You do not always need to do a fetch request every time you want some information. Once you already have a managed object, you can get information related to it without a fetch request. If you've set up a custom class for it, you can treat it almost like it's an regular objective-C object.
for (NSManagedObject *obj in matchingData) {
Intersection = sendIntersection;
currentDay = sendDate;
}
This code just doesn't make sense. You're looping, but each time through you're assigning the same value to the variables. I don't know what that value is, since sendIntersection and sendDate are not referred to anywhere else in the code you posted. In any case you're not using the results of the fetch request at all.
I'm going to make a wild guess at what you need to do:
Intersection = [userInfo valueForKey:#"intersection"];
currentDay = [userInfo valueForKey:#"date"];
It's a total guess, because I don't know what your data model is. No loop is needed, since you only want and have one user object.

Resources