Objective-C iPhone - Ordering data within multiple sections of a UITableView dataSource - iphone

For the purpose of asking this question about ordering. The following MyObject class returns an instance with random generated category names.
I use the following dataSource methods:
numberOfSections accessed with [dataSource count].
titleForSection accessed with [[dataSource objectAtIndex:indexPath.section] valueForKey:#"categoryName"].
numberOfRowsInSection accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] count].
And finally, the MyObject for each row is accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] objectAtIndex:indexPath.row] on the cellForRowAtIndexPath method.
I use the following code to create a dataSource that displays 9 section categories, however I'm a little stuck on the ordering of these categories and the data within. Assume there's an NSDate property as part of the MyObject class.
Question: How would I go about using this to display the records in descending order?
- (void)createDatasource
{
NSInteger numberOfObjects = 10;
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:numberOfObjects];
NSMutableArray *categories = [NSMutableArray arrayWithCapacity:numberOfObjects];
for (int i = 0; i < numberOfObjects; i++)
{
MyObject *obj = [[MyObject alloc] init];
[objects addObject:obj];
[categories addObject:obj.category];
[obj release];
}
NSSet *set = [NSSet setWithArray:categories];
NSMutableArray *dataSource = [[NSMutableArray alloc] initWithCapacity:[set count]];
for (NSString *categoryString in set)
{
NSMutableDictionary *mainItem = [[NSMutableDictionary alloc] initWithObjectsAndKeys:nil, #"categoryName", nil, #"myObjects", nil];
NSMutableArray *mainItemMyObjects = [NSMutableArray array];
[mainItem setValue:categoryString forKey:#"categoryName"];
for (MyObject *obj in objects)
{
if ([obj.category isEqualToString:categoryString])
{
[mainItemMyObjects addObject:obj];
}
}
[mainItem setValue:mainItemMyObjects forKey:#"myObjects"];
[dataSource addObject:mainItem];
[mainItem release];
}
NSLog (#"objects = %#\ncategories = %#\nset = %#\ndatasource = %#", objects, categories, set, dataSource);
}

Easiest would be to sort your arrays, using NSMutableArray's sorting mutators or NSArray's sorting methods. Otherwise you'd have to construct some sort of mapping from input indices to dataSource indices for use by the various data source methods.
Edit Requested sample code for sorting, something like this should work. I assume you are wanting to sort everything by a property named date on the MyObject.
// First, sort the myObject mutable array in each category
for (NSDictionary *d in dataSource) {
[[d valueForKey:#"myObjects"] sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Compare dates. NSDate's 'compare:' would do ascending order, so if we just
// reverse the order of comparison they'll come out descending.
return [[o2 date] compare:[o1 date]];
}];
}
// Second, sort the categories by the earliest dated object they contain
[dataSource sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Extract the first object from each category's array, which must be the
// earliest it contains due to the previous sort.
MyObject *myObject1 = [[o1 valueForKey:#"myObjects"] objectAtIndex:0];
MyObject *myObject2 = [[o2 valueForKey:#"myObjects"] objectAtIndex:0];
// Compare dates, as above.
return [[myObject2 date] compare:[myObject1 date]];
}];

Related

Sort NSMutableArray with custom objects by another NSMutableArray [duplicate]

This question already has answers here:
How do I sort an NSMutableArray with custom objects in it?
(27 answers)
Sorting two NSArrays together side by side
(4 answers)
Closed 9 years ago.
I have 2 NSMutableArrays. First array contains custom object intances with property NSString *itemID, second array contains only from NSString objects with same values of itemID, but in another order. I need to sort first array by itemID property of each object, and it should be sorted like second array.
How I can do this?
guideArray = < YOUR SECOND ARRAY WITH STRING OBJECT >;
unsortedArray = < YOUR FIRST ARRAY WITH CUSTOM OBJECT >;
[unsortedArray sortUsingComparator:^(id o1, id o2) {
Items *item1 = o1;
Items *item2 = o2;
NSInteger idx1 = [guideArray indexOfObject:item1.ItemID];
NSInteger idx2 = [guideArray indexOfObject:item2.ItemID];
return idx1 - idx2;
}];
NSLog(#"%#",unsortedArray);
Store the custom objects in an dictionary with itemID as key, use this dictionary as lookup to sort the objects:
NSArray *objects; // your objects
NSMutableArray *hintArray; // your sorted IDs
NSMutableDictionary *lookupDict = [[NSMutableDictionary alloc] initWithCapacity:[objects count]];
NSMutableArray *sortedObjects = [[NSMutableArray alloc] initWithCapacity:[hintArray count]];
for (id object in objects) {
[lookupDict setValue:object forKey:[object itemID]];
}
for (id hint in hintArray) {
[sortedObjects addObject:[lookupDict valueForKey:hint]];
}
EDIT:
Solution with inplace sort of objects:
NSMutableArray *objects;
NSMutableArray *hintArray;
NSMutableDictionary *lookupDict = [[NSMutableDictionary alloc] initWithCapacity:[hintArray count]];
int i = 0;
for (NSString *itemID in hintArray) {
[lookupDict setValue:[NSNumber numberWithInt:i] forKey:itemID];
i++;
}
[objects sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [[lookupDict valueForKey:[obj1 itemID]] compare:[lookupDict valueForKey:[obj2 itemID]]];
}];
You can compare your two objects using following syntax :-
[items sortUsingComparator:^NSComparisonResult(Attribute *obj1, Attribute *obj2)
{
return [[NSNumber numberWithInt:[stringOrder indexOfObject:obj1.itemID]] compare:[NSNumber numberWithInt:[stringOrder indexOfObject:obj2.itemID]]]
}];
or else you can use following snippet :
NSArray* sortedKeys = [dict keysSortedByValueUsingComparator:^(id obj1, id obj2)
{
return [obj1 compareTo:obj2];
}
Enjoy Programming !

Use existing NSArray object properties to create a new NSArray for sectioned tableView

So I have the kind of classic situation where I want to group my tableView by Month/Year. I have a member of my conference object called beginDateSearchString that I use to put different conference into buckets; my problem is in the next part where I try and fail to use a NSSortDescriptor to sort each bucket by beginDate (which is a date).
I am getting an error related to unsorted not being able to receive sort descriptor type selectors.
Here is the disgusting code:
- (NSArray *)arrayOfDateSortedEvents {
NSMutableArray *sortedArray = [[NSMutableArray alloc] init];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
//place into buckets
for (WSConference *conference in self.arrayOfEvents) {
if (![dictionary objectForKey:[conference beginDateSearchString]]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:conference,nil];
[dictionary setObject:array forKey:[conference beginDateSearchString]];
}
else {
[[dictionary objectForKey:[conference beginDateSearchString]] addObject:conference];
}
}
//sort each bucket by descriptor beginDate
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"beginDate" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:descriptor];
for (NSMutableArray *unsorted in dictionary) {
[unsorted sortUsingDescriptors:sortDescriptors];
}
// now, unkey and add dictionary in order
while ([dictionary count] > 0) {
NSString *lowest = nil;
for (NSMutableArray *array in dictionary) {
if (!lowest)
lowest = [[dictionary allKeysForObject:array] objectAtIndex:0];
else {
if ([(WSConference *)[array objectAtIndex:0] beginDate] < [[dictionary objectForKey:lowest] beginDate])
lowest = [[dictionary allKeysForObject:array] objectAtIndex:0];
}
}
[sortedArray addObject:[dictionary objectForKey:lowest]];
[dictionary removeObjectForKey:lowest];
}
return sortedArray;
}
You want to probably filter the array in addition to sorting. See NSPredicate and the NSArray method -filteredArrayUsingPredicate: Then create an eventsByDateArray of the eventArrays created by the filter. Then in your table view delegate for creating the cells, if everything is ordered properly, the first section would represent the date of the events in the eventArray that is the first object of the eventsByDateArray and the table rows would consist of the events in the eventArray. And so on for each date.
Added
Your fast enumeration is incorrect. You enumerate through the keys of the dictionary. So in your code unsorted equals each of the keys as it enumerates. This is a GREAT lesson to everyone. It does not matter how you 'type' a variable. When Objective-C compiles it turns them all into id. So NSMutableArray *unsorted is not an NSMutableArray unless it is assigned to an NSMutableArray. If you assign unsorted to an NSString it will be an NSString. The fast enumerator for a dictionary works using the keys. So, in this case, unsorted becomes an NSString.
Instead of:
for (NSMutableArray *unsorted in dictionary) {
[unsorted sortUsingDescriptors:sortDescriptors];
}
you should have this:
for (id key in dictionary) {
NSMutableArray *unsorted = [dictionary objectForKey:key];
[unsorted sortUsingDescriptors:sortDescriptors];
}

How do I get the index of an object in an NSArray using string value?

I want to get the index of an object within the NSMutableArray of categories.
The category object has an attribute "category_title" and I want to be able to get the index by passing the value of category_title.
I have looked through the docs and can't find a simple way to go about this.
NSArray does not guarantee that you can only store one copy of a given object, so you have to make sure that you handle that yourself (or use NSOrderedSet).
That said, there are a couple approaches here. If your category objects implement isEqual: to match category_title, then you can just use -indexOfObject:.
If you can't do that (because the category objects use a different definition of equality), use -indexOfObjectPassingTest:. It takes a block in which you can do whatever test you want to define your "test" - in this case, testing category_title string equality.
Note that these are all declared for NSArray, so you won't see them if you are only looking at the NSMutableArray header/documentation.
EDIT: Code sample. This assumes objects of class CASCategory with an NSString property categoryTitle (I can't bring myself to put underscores in an ivar name :-):
CASCategory *cat1 = [[CASCategory alloc] init];
[cat1 setCategoryTitle:#"foo"];
CASCategory *cat2 = [[CASCategory alloc] init];
[cat2 setCategoryTitle:#"bar"];
CASCategory *cat3 = [[CASCategory alloc] init];
[cat3 setCategoryTitle:#"baz"];
NSMutableArray *array = [NSMutableArray arrayWithObjects:cat1, cat2, cat3, nil];
[cat1 release];
[cat2 release];
[cat3 release];
NSUInteger barIndex = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if ([[(CASCategory *)obj categoryTitle] isEqualToString:#"bar"]) {
*stop = YES;
return YES;
}
return NO;
}];
if (barIndex != NSNotFound) {
NSLog(#"The title of category at index %lu is %#", barIndex, [[array objectAtIndex:barIndex] categoryTitle]);
}
else {
NSLog(#"Not found");
}
Not sure that I understand the question but something like this might work (assuming the Mutable Array contains objects of Class "Category"):
int indx;
bool chk;
for (Category *aCategory in theArray)
{
chk = ([[aCategory category_title] isEqualToString:#"valOfCategoryTitle"])
if ( chk )
indx = [theArray indexOfObject:aCategory];
}
Try this code much more simpler:-
int f = [yourArray indexOfObject:#"yourString"];

Putting single attribute from group of entity objects into an array

If I have an NSArray of custom objects (in this case Core Data objects), how would I put all the items of a particular attribute in another NSArray. Is there a way I can use blocks?
For instance, if the class is People, and one of the attributes is age, and there are five objects in the array of people, then the final NSArray would just show the ages:
{ 12, 45, 23, 43, 32 }
Order is not important.
EDIT I have added a blocks based implementation too alongwith the selector based implementation.
What you are looking for is something equivalent to the "map" function from the functional world (something which, unfortunately, is not supported by Cocoa out of the box):
#interface NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector;
- (NSArray *)mapWithBlock:(id (^)(id obj))block;
#end
And the implementation:
#implementation NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:[object performSelector:selector]];
}
return [result autorelease];
}
- (NSArray *)mapWithBlock:(id (^)(id obj))block {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:block(object)];
}
return [result autorelease];
}
#end
Now, when you need to "map" from people to their ages, you can just do this:
ages = [people mapWithSelector:#selector(age)];
OR
ages = [people mapWithBlock:^(Person *p) { return [p age]; }];
The result, ages, will be a new NSArray containing just the ages of the people. In general, this will work for any sort of simple mapping operations that you might need.
One caveat: Since it returns an NSArray, the elements inside ages should be NSNumber, not a plain old integer. So for this to work, your -age selector should return an NSNumber, not an int or NSInteger.
Assuming that each object has a method called "age" that returns an NSNumber *, you should be able to do something like the following:
-(NSArray *)createAgeArray:(NSArray *)peopleArray {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[peopleArray count]];
[peopleArray enumerateObjectsUsingBlock:^(id person, NSUInteger i, BOOL *stop) {
[result addObject:[person age]];
}];
return [result autorelease];
}

Core Data - JSON (TouchJSON) on iPhone

I have the following code which seems to go on indefinitely until the app crashes. It seems to happen with the recursion in the datastructureFromManagedObject method. I suspect that this method:
1) looks at the first managed object and follows any relationship property recursively.
2) examines the object at the other end of the relationship found at point 1 and repeats the process.
Is it possible that if managed object A has a to-many relationship with object B and that relationship is two-way (i.e an inverse to-one relationship to A from B - e.g. one department has many employees but each employee has only one department) that the following code gets stuck in infinite recursion as it follows the to-one relationship from object B back to object A and so on.
If so, can anyone provide a fix for this so that I can get my whole object graph of managed objects converted to JSON.
#import "JSONUtils.h"
#implementation JSONUtils
- (NSDictionary*)dataStructureFromManagedObject:(NSManagedObject *)managedObject {
NSDictionary *attributesByName = [[managedObject entity] attributesByName];
NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];
//getting the values correspoinding to the attributes collected in attributesByName
NSMutableDictionary *valuesDictionary = [[managedObject dictionaryWithValuesForKeys:[attributesByName allKeys]] mutableCopy];
//sets the name for the entity being encoded to JSON
[valuesDictionary setObject:[[managedObject entity] name] forKey:#"ManagedObjectName"];
NSLog(#"+++++++++++++++++> before the for loop");
//looks at each relationship for the given managed object
for (NSString *relationshipName in [relationshipsByName allKeys]) {
NSLog(#"The relationship name = %#",relationshipName);
NSRelationshipDescription *description = [relationshipsByName objectForKey:relationshipName];
if (![description isToMany]) {
NSLog(#"The relationship is NOT TO MANY!");
[valuesDictionary setObject:[self dataStructureFromManagedObject:[managedObject valueForKey:relationshipName]] forKey:relationshipName];
continue;
}
NSSet *relationshipObjects = [managedObject valueForKey:relationshipName];
NSMutableArray *relationshipArray = [[NSMutableArray alloc] init];
for (NSManagedObject *relationshipObject in relationshipObjects) {
[relationshipArray addObject:[self dataStructureFromManagedObject:relationshipObject]];
}
[valuesDictionary setObject:relationshipArray forKey:relationshipName];
}
return [valuesDictionary autorelease];
}
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {
NSMutableArray *dataArray = [[NSArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
}
return [dataArray autorelease];
}
//method to call for obtaining JSON structure - i.e. public interface to this class
- (NSString*)jsonStructureFromManagedObjects:(NSArray*)managedObjects {
NSLog(#"-------------> just before running the recursive method");
NSArray *objectsArray = [self dataStructuresFromManagedObjects:managedObjects];
NSLog(#"-------------> just before running the serialiser");
NSString *jsonString = [[CJSONSerializer serializer] serializeArray:objectsArray];
return jsonString;
}
- (NSManagedObject*)managedObjectFromStructure:(NSDictionary*)structureDictionary withManagedObjectContext:(NSManagedObjectContext*)moc {
NSString *objectName = [structureDictionary objectForKey:#"ManagedObjectName"];
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:objectName inManagedObjectContext:moc];
[managedObject setValuesForKeysWithDictionary:structureDictionary];
for (NSString *relationshipName in [[[managedObject entity] relationshipsByName] allKeys]) {
NSRelationshipDescription *description = [[[managedObject entity]relationshipsByName] objectForKey:relationshipName];
if (![description isToMany]) {
NSDictionary *childStructureDictionary = [structureDictionary objectForKey:relationshipName];
NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
[managedObject setValue:childObject forKey:relationshipName];
continue;
}
NSMutableSet *relationshipSet = [managedObject mutableSetValueForKey:relationshipName];
NSArray *relationshipArray = [structureDictionary objectForKey:relationshipName];
for (NSDictionary *childStructureDictionary in relationshipArray) {
NSManagedObject *childObject = [self managedObjectFromStructure:childStructureDictionary withManagedObjectContext:moc];
[relationshipSet addObject:childObject];
}
}
return managedObject;
}
//method to call for obtaining managed objects from JSON structure - i.e. public interface to this class
- (NSArray*)managedObjectsFromJSONStructure:(NSString *)json withManagedObjectContext:(NSManagedObjectContext*)moc {
NSError *error = nil;
NSArray *structureArray = [[CJSONDeserializer deserializer]
deserializeAsArray:[json dataUsingEncoding:NSUTF32BigEndianStringEncoding]
error:&error];
NSAssert2(error == nil, #"Failed to deserialize\n%#\n%#", [error localizedDescription], json);
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
for (NSDictionary *structureDictionary in structureArray) {
[objectArray addObject:[self managedObjectFromStructure:structureDictionary withManagedObjectContext:moc]];
}
return [objectArray autorelease];
}
#end
I answered this question when you posted a comment on the original thread. You need to make some changes to how the recursion works so that it doesn't go into a loop. There are many ways to do this.
For example, you can change the call to get all relationships to instead call a method in your NSManagedObject subclasses that only returns the relationships that are downstream. In that design ObjectA would return the ObjectB relationship but Object B would not return any (or relationships to ObjectC, etc.). This creates a tree like hierarchy for the recursion to work through.
Follow the logic of the code. It process the object or objects you hand to it and then it walks through every object associated with that first set of objects. You already, from your post, showed that you understand it is a loop. Now you need to break that loop in your code with logic to change it from a loop to a tree.
Also, I realize this may sound like I am pimping my book, I explained how to do avoid this loop in my book in the Multi-threading chapter in the section on exporting recipes.
Update NSDate
That sounds like a bug in the JSON parser that you are using as it should be able to handle dates. However your workaround is viable except you need to convert it on both sides which is a PITA. I would look into your parser and see why it is not translating dates correctly as that is a pretty big omission.
I just wanted to point out a small typo, that caused the code to crash, and hopefully this will save you a few min.
- (NSArray*)dataStructuresFromManagedObjects:(NSArray*)managedObjects {
NSMutableArray *dataArray = [[NSArray alloc] init];
for (NSManagedObject *managedObject in managedObjects) {
[dataArray addObject:[self dataStructureFromManagedObject:managedObject]];
}
return [dataArray autorelease];
}
The NSMutableArray *dataArray = [[NSArray alloc] init]; // This should be NSMutableArray
really should be NSMutableArray *dataArray = [[NSMutableArray alloc] init];
that is all.
thank you