I have NSDictionaries stored in an NSMutableArray. Each NSDictionary has a NSNumber inside of it. So right now, my tableview will show the cells in the way they are ordered in the array. What I want to do is reorder the cells from highest NSNumber to lowest NSNumber, so how would I do this with all the NSDictionaries in the NSMutableArray. I will probably need a for loop or something like that but I am not sure how to do it.
Also I have NSDate's (non-formatted) in my NSDictionaries, so how would I sort the NSDictionaries based upon the newest dates inside them?
Thanks!
You need to use the sortedArrayUsingSelector method of NSMutableArray and provide a sorting function, like this:
NSMutableArray *yourSortedArray;
yourSortedArray = [originalArray sortedArrayUsingSelector:#selector(yourCompareFunction:)];
...
- (NSComparisonResult)yourCompareFunction:(NSDictionary *)otherObject {
return [[self objectForKey:#"number_key"] compare:[otherObject objectForKey:#"number_key"]];
}
I didn't tested this but that's the idea.
Related
I need to fetch an object using Core Data. The object has a property datesArray(Array of NSDate objects stored as NSData) which I use to store array of dates. I need to check if the array contains todays date and then use the object.
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:#"MyEntity"];
NSError * error;
NSArray * fetchedArray = [context executeFetchRequest:request error:&error];
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"entity.datesArray CONTAINS %#",[NSDate date]];
for (MyEntity * entity in fetchedArray) {
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:entity.datesArray];
[array filterUsingPredicate:predicate];
if (array.count >0) {
[_myMutableArray addObject:entity];
}
}
You really should not be storing dates like this. You are losing the value of using Core Data. If you have an array of something then that should be on the other side of a relationship which you can then retrieve efficiently from the underlying persistent store.
I suspect that your predicate lacks precision as a date object is down to the nanosecond. If you are looking to match something from "today" then you need to work on something with less precision. Perhaps a string in a specific format or store it as a number and then search within a range (less than X and greater than Y type of search).
Again, storing the actual dates in managed objects makes this question much easier but you are still going to be dealing with a precision problem.
I think I'm just missing something obvious here, but it's one of those frustrating things that's somehow eluding me.
I have a Core Data Entity called ProjectEntry. The ProjectEntry objects are displayed in uitableviews, using various attributes, arranged by date (attributes include things like "dateAsNSDate"[NSDate], "month"[NSString], "year"[NSString], "dayOfWeek"[NSString]).
I'm using an NSFetchedResultsController to populate the table views.
When I initially create and save the ProjectEntry object, the "dateAsNSDate" attribute is parsed and converted into various NSStrings. One string, also an attribute, is called "concatMonthAndYear". It takes the "month" and "year" strings and just joins them. So I get things such as "January 2014", "February 2015", etc.
I use the "concatMonthAndYear" as my cell.textLabel.text string to display in my tableview cells.
I use the NSDate attribute to sort the tableview rows (sortDescriptor), and the "year" attribute as my section headers (sectionNameKeyPath).
So right now, I'd have a tableview section called "2014", with tableview rows each representing a Core Data object, named things like "January 2014", February 2014", etc, in said section.
I can tap on one of those rows, segue to another tableview, and list all objects created in January 2014, for example, by using an NSPredicate on the second tableview.
However, on my first tableview, each Core Data object created is represented by its own tableview row. So I'll get multiple rows reading "January 2014" or "May 2015" or whatever. They're valid saved objects, and I want them, but I'd like to prevent a new row from being created if that "concatMonthAndYear" already exists. If a row titled "January 2014" already exists, I don't want a new row created. I want the new Core Data object stored, just not a new tableviewrow representing it. I only need one row with "January 2014", for example, to segue into a table listing ALL the entities from January 2014.
I know how to use an NSPredicate to get ALL the January 2014 objects into the second table, but how do I get JUST ONE object into the first table?
Is NSPredicate not the right device for that? Should I be somehow preventing a new cell from being created in the UITableView delegate methods? Each tableview row should be unique, and I'm stuck on whether it should be handled with the NSFetchedResults controller or in the tableview delegate methods?
Or some other way?
Can someone point in the right direction?
EDITED TO INCLUDE CODE:
- (void)setupFetchedResultsController
{
// 1 - Decide which Entity
NSString *entityName = #"ProjectEntry";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
// 2 - Request Entity
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
[request setReturnsDistinctResults:YES];
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:#[#"monthYearTableSecHeader", #"year"]];
// 3 - Filter it
//request.predicate = [NSPredicate predicateWithFormat:#" "];
// 4 - Sort it
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"year"
ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:#"dateAsNSDate"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)], nil];
//5 - Fetch
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"year"
cacheName:nil];
[self performFetch];
}
You could use
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:#[#"concatMonthAndYear", #"year"]];
This will cause the fetch request to return distinct dictionary objects corresponding to "January 2014", etc. objects.
However, you cannot use a fetch request controller's delegate methods (to hear of updates to the data).
If you need to hear updates, I suggest you add a layer of indirection to your data, where MonthEntry is an object representing yearly months and have a one to many relationship with ProjectEntry, which is your normal entity. This way, you can set the fetch request entity to MonthEntry.
I have two entities:
Ticket
TicketResolved
Both entities have the same attributes. What would be the the most efficient way to copy a Ticket NSManagedObject to TicketResolved NSManagedObject?
I'm thinking using a Category: Ticket+Copy be the least expensive way? If so, I would have to #import both Ticket and TicketResolved in the Category file.
Here is what I came up with, can someone please advise if this is the right way of going about it. I'm using NSManagedObjectSubclass for each entity.
Method in Ticket+Copy:
-(TicketResolved *)copyObjects:(Ticket *)ticket
{
TicketResolved *ticketResolved = [NSEntityDescription insertNewObjectForEntityForName:#"TicketResolved" inManagedObjectContext:self.managedObjectContext];
ticketResolved.attribute = ticket.attribute;
// Assign rest of the ticketResolved attributes values this way
return ticketResolved;
}
Now calling the Method
#import Ticket;
#import Ticket+Copy;
#implementation
....
Ticket *ticket = [NSEntityDescription insertNewObjectForEntityForName:#"Ticket" inManagedObjectContext:self.managedObjectContext];
TicketResolved *newTicketResolved = [ticket copyObjects:ticket];
// 'newTicketResolved' now has all the keys/values that 'ticket' had.
Is this a right approach or is there a simpler way to do this?
If Ticket and TicketResolved actually have the same attributes, the most efficient option is to:
Get rid of TicketResolved
Add a boolean flag on Ticket named resolved that you can set to YES when the ticket is resolved.
Use this attribute it fetch requests to get either resolved or non-resolved tickets, whichever you need.
Then you don't actually need to copy any data, and not doing work is always more efficient than doing it.
If for some reason you really want two separate entities with the same attributes, basically you have it, you need to create a TicketResolved instance and have your code copy over every attribute value. The only major problem with your code is lines like this:
Ticket *ticket = [Ticket alloc]init];
You can't create managed objects like that, because you're not calling the designated initializer. You need to either use -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:] or else use +[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:].
Thanks to #Tom, I did correct my error in the question.
Below is the solution that worked for me:
TicketResolved *ticketResolved = [NSEntityDescription insertNewObjectForEntityForName:#"TicketResolved" inManagedObjectContext:self.managedObjectContext];
NSArray *keys = [[[ticket entity] attributesByName] allKeys];
NSDictionary *dict= [ticket dictionaryWithValuesForKeys:keys];
[ticketResolved setValuesForKeysWithDictionary:dict];
The code above does not copy the Relationships Objects. For that I had to use the code below:
ticketResolved.relationshipObject = ticket.relationshipObject;
I am currently working on a table list with categories and subcategories, where you can navigate using a drilldown to explore the child categories until you get a detail view.
I got inspiration from iphonesdkarticles.com.
The solution in this blog for the infinite drilldown was populating an array with a plist, and a single UITableView to do the drilldown.
I wanted to use core data with the NSFetchedResultsController instead of the plist.
I got the first list of categories, but when I click one of them, I got an empty table.
I don't know if using the NSFetchedResultsController in this scenario is the most appropriate solution. Maybe I am doing something wrong when I use didSelectRowatIndex:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Categories *category = (Categories *)[fetchedResultsController objectAtIndexPath:indexPath];
MasterViewController *theViewController = [[MasterViewController alloc] initWithStyle:UITableViewStylePlain];
theViewController.CurrentLevel += 1;
theViewController.CurrentTitle = categories.name;
detailViewController.category = category;
[self.navigationController pushViewController:theViewController animated:YES];
}
To create infinite drill down in Core Data, just make an entity a relationship of itself.
I am trying to parse a lot of text files and organize their contents as managed objects. There are a lot of duplicates in the text files, so one of the "collateral" tasks is to get rid of them.
What i am trying to do in this respect is to check whether an entity with the given content exists, and if it doesn't, i create one. However, i have different entities with different attributes and relationships. What i want is a kind of function that would take a number of attributes as an input and return a new NSManagedObject instance, and i wouldn't have to worry if it was inserted into the data store or fetched from it.
Is there one?
I must also say that i am a noob at core data.
Some more detail, if you want:
I am trying to write a sort of dictionary. I have words (Word{NSString *word, <<-> Rule rule}), rules (Rule{NSString name, <->>Word word, <<->PartOfSpeech partOfSpeech, <<-> Ending endings}), parts of speech (PartOfSpeech{NSString name, <<-> Rule rule}) (i hope the notation is clear).
Two words are equal, if they have the same word property, and "linked" to the same rule. Two rules are the same, if they have the same endings and part of speech.
So far i've written a method that takes NSPredicate, NSManagedObjectContext and NSEntityDescription as an input, and first queries the datastore and returns an entity if it finds one, or creates a new one, inserts it into the datastore and returns it. However, in this case I cannot populate the new entity with the necessary data (within that method), so i have to either pass an NSDictionary with the names of attributes and their values and insert them, or return by reference a flag as to whether i created a new object or returned an old one, so that i could populate it with the data outside.
But it looks kind of ugly. I'm sure there must be something more elegant than that, i just couldn't find it. Please, help me if you can.
Your basically on the right path. Core Data is an object graph. There not a lot of dynamic built in. There's also no "upsert". like you surmise, you have to fetch and if it doesn't exist, you insert one.
Here is what I have just started using to handle a fetch-or-create scenario. I am using a top level managed object which contains a few to-many relationships to subordinate objects. I have a class that houses a few arrays of data (those are not shown here). This class is responsible for saving and retrieving to and from core data. When the class is created, I do a fetch-or-create to access my top level NSManagedObject.
#implementation MyDataManagerClass
...
#synthesize MyRootDataMO;
- (MyDataManagerClass *) init {
// Init managed object
NSManagedObjectContext *managedObjectContext = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// Fetch or Create root user data managed object
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
if (result == nil) {
NSLog(#"fetch result = nil");
// Handle the error here
} else {
if([result count] > 0) {
NSLog(#"fetch saved MO");
MyRootDataMO = (MyRootDataMO *)[result objectAtIndex:0];
} else {
NSLog(#"create new MO");
MyRootDataMO = (MyRootDataMO *)[NSEntityDescription insertNewObjectForEntityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
}
}
return self;
}
...