Date Sectioned table? - iphone

I have a plist which contains an array of NSDictionary 's which represent a certain event each dictionary contains a few pieces of information about the event and a NSDate with the date of the event e.g.
I wish to create a sectioned table view with this date very much like in the Calendar app which ships with the iPhone when you click on the "List" view. You can see there are only sections for the dates where there are events.
So whats the best way to find out to begin with how many NSDictionary 's have the same date (so I know how many sections to create and how many rows in each section, as each section will have a different amount or rows).
Thanks

I did something very similar to this for Reconnected except my sections are for years (see the History screenshot).
Sort the array by the date key.
Start with the first item. The date of the first item represents the first segment. In my case, I only care about the year.
Create an array for the section with the date. Add the currently inspected to the section's array. Add the section's array to another array that will be the array of all sections in your table.
Move on to the next item. If the next item's date equals the previous item's date, add the current item to the current section's array. Otherwise, apply step #3 to the new date.
Repeat the previous step for rest of the array from your plist.
At the end of step 5, you should have an array of sections. From that section you can send it a message for the number of NSDictionary's you've added to the section which will represent each rows in your table.

After a bit of playing around this is what I have come up with, at the moment its just a foundation tool to keep it clear.
#import <Foundation/Foundation.h>
NSDate* normalizedDateWithDate(NSDate *date) {
NSCalendar *calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *comp = [calendar components:unitFlags fromDate:date];
return [calendar dateFromComponents:comp];
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *plistPath = #"flights.plist";
NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];
NSMutableSet *flightDates = [[NSMutableSet alloc] init];
for (NSDictionary *oneFlight in array)
[flightDates addObject:normalizedDateWithDate([oneFlight objectForKey:#"flightDate"])];
NSLog(#"Number of Sections Required: %d", [flightDates count]);
NSMutableDictionary *datesAndFlights = [[NSMutableDictionary alloc] init];
for (NSDate *fDate in flightDates) {
NSMutableArray *sectionFlights = [[NSMutableArray alloc] init];
for (NSDictionary *oneFlight in array) {
if ([normalizedDateWithDate([oneFlight objectForKey:#"flightDate"]) isEqualToDate: normalizedDateWithDate(fDate)])
{
[sectionFlights addObject:oneFlight];
}
}
[datesAndFlights setObject:sectionFlights forKey:normalizedDateWithDate(fDate)];
[sectionFlights release];
}
NSEnumerator *enumerator = [datesAndFlights keyEnumerator];
NSDate *key;
while ((key = [enumerator nextObject])) {
NSLog(#"Key: %#", key);
for (NSDictionary *oneFlight in [datesAndFlights objectForKey:key]) {
NSLog(#"flightNumber: %# and Total Time: %#", [oneFlight objectForKey:#"flightNumber"], [oneFlight objectForKey:#"totalTime"]);
}
}
[array release];
[flightDates release];
[datesAndFlights release];
[pool drain];
return 0;
}
This is just what I have managed to put together and it seems to work but if anyone can see a way to make this better or more concise please say so! Also the function at the top which I use to make sure the date is always at time 00:00:00 when I compare it I have seen the NSCalendar – rangeOfUnit:startDate:interval:forDate: method in the documentation does anyone know if its better to use this instead?
Thanks

Related

sorting NSMutableArray contain NSArray using sortedArrayUsingComparator

I got problem for sorting array of nsdate.
so i have array of date inside nsmutablearray
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
NSArray *arrString = [[NSArray alloc] initWithObjects:#"2012-05-01", #"2012-02-01", #"2012-22-03", #"2012-05-01", #"2012-15-01", nil];
NSMutableArray *arrDate = [[NSMutableArray alloc] initWithCapacity:arrString.count];
for (NSString *item in arrString) {
NSDate *date = [dateFormatter dateFromString:item];
[arrDate addObject:date];
}
as i saw in many cases they use code like this to sort array:
NSArray *sortedArray = [arrDate sortedArrayUsingComparator:^(id firstObject, id secondObject) {
return [((NSDate *)firstObject) compare:((NSDate *)secondObject)];
}];
i tried to put my array on it, but not worked.
Anyone can help me how actully best way to sort nsmutablearray contain nsdate. I want to put the latest date at top of the index array.
So the real question is, how can i sort data inside arrDate?
Thank you.
Your date format does not match the date strings you are using.
yyyy-MM-dd hh:mm:ss
means the dates you supply should look something like:
2012-05-09 15:54:21
^^ Day
^^ Month
i.e. your strings are missing the hours, minutes and seconds and you have some dates with month 22 and month 15. If a date formatter can't parse a date, I think it returns nil which would mean you are trying to put nil into an array, which will cause an exception.
Edit
To actually sort the dates, your sample code looks OK except that you want most recent first, so reverse the compare:
NSArray *sortedArray = [arrDate sortedArrayUsingComparator:^(id firstObject, id secondObject) {
return [secondObject compare: firstObject];
}];
Or with a mutable array, you can sort in place:
[arrDate sortUsingComparator:^(id firstObject, id secondObject) {
return [secondObject compare: firstObject];
}];

Extracting specific data from a Mutable Dictionary

I have a Mutable Dictionary which allows people to choose a selection of days of the week. Once a day has been selected the state is updated using numberWithBool.
When I NSLog the output it looks something like this:
{
day = Monday;
isSelected = 1;
},
{
day = Tuesday;
isSelected = 0;
},
{
day = Wednesday;
isSelected = 0;
},
{
day = Thursday;
isSelected = 0;
},
{
day = Friday;
isSelected = 0;
},
{
day = Saturday;
isSelected = 0;
},
{
day = Sunday;
isSelected = 1;
}
I would like to be able to extract the chosen days and produce the output in the form of a string. So in this example the output would be: Monday, Sunday
How can I do this?
My code for creating the dictionary is below:
NSMutableArray * tempSource = [[NSMutableArray alloc] init];
NSArray *daysOfWeek = [NSArray arrayWithObjects:#"Monday", #"Tuesday", #"Wednesday", #"Thursday", #"Friday", #"Saturday", #"Sunday",nil];
for (int i = 0; i < 7; i++)
{
NSString *dayOfWeek = [daysOfWeek objectAtIndex:i];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:dayOfWeek, #"day", [NSNumber numberWithBool:NO], #"isSelected",nil];
[tempSource addObject:dict];
}
[self setSourceArray:tempSource];
[tempSource release];
You can loop thru all of your items in the array and build a side-array only containing names of the day (1), or you can use a predicate and then KVC to extract the days directly (2).
Then join the components of the filtered array into a string.
Solution 1:
NSMutableArray selectedDays = [[NSMutableArray alloc] init];
for(NSDictionary* entry in sourceArray)
{
if (([entry objectForKey:#"isSelected"] boolValue]) {
[selectedDays addObject:[entry objectForKey:#"day"]];
}
}
NSString days = [selectedDays componentsJoinedByString:#", "];
[selectedDays release];
Solution 2:
NSPredicate* filter = [NSPredicate predicateWithFormat:#"SELF.isSelected == 1"]; // not sure about the exact format (no mac here to test right now so you may adapt a bit if it does not work directly)
// get the array of dictionaries but only the ones that have isSelected==1
NSArray selectedEntries = [sourceArray filteredArrayUsingPredicate:filter];
NSArray selectedDays = [selectedEntries valueForKey:#"day"]; // extract the "days" keys of the dictionaries. We have a NSArray of strings then.
NSString days = [selectedDays componentsJoinedByString:#", "];
As a side note, your way of doing this is quite strange. Why having an NSArray of NSDictionaries for this? As this is simple and a static-size array containing only BOOL, you may instead for this particular case simply use C array BOOL selected[7] and nothing more.
Then to have the name of the weekdays you should instead use the methods of NSCalendar/NSDateFormatter/NSDateComponents to get the standard names of the weekdays (automatically in the right language/locale of the user): create an NSDate using a NSDateComponent for which you simply define the weekday component, then use an NSDateFormatter to convert this to a string, choosing a string format that only display the weekday name.
-(void)tableView:(UITableView*)tv didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
// selectedDays is an instance variable in .h declared as
// BOOL selectedDays[7];
selectedDays[indexPath.row] = ! selectedDays[indexPath.row];
[tv reloadData];
}
-(UITableViewCell*)tableView:(UITableView*)tv cellForRowAtIndexPath:(NSIndexPath*)indexPath {
static NSString* kCellIdentifier = #"DayCell";
UITableViewCell* cell = [tv dequeueReusableCellWithIdentifier:kCellIdentifier];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:... identifier:kCellIdentifier]; autorelease];
// configure here every property that is common to all for your cells (text color, etc)
}
// configure here things that will change from cell to cell
cell.accessoryType = selectedDays[indexPath.row] ? UITableViewCellAccessoryTypeCheckmarck : UITableViewCellAccessoryTypeNone;
cell.textLabel.text = weekdayName(indexPath.row);
return cell;
}
// Simple C function to return the name of a given weekday
NSString* weekdayName(int weekday)
{
#if WAY_1
/**** Solution 1 ****/
// Optimization note: You may compute this once for all instead of recomputing it each time
NSDateComponents* comp = [[[NSDateComponents alloc] init] autorelease];
[comp setWeekday:weekday+1]; // weekdays start at 1 for NSDateComponents
NSDate* d = [[NSCalendar currentCalendar] dateFromComponents:comp];
NSDateFormatter* df = [[[NSDateFormatter alloc] init] autorelease];
[df setDateFormat:#"EEEE"]; // format for the weekday
return [[df stringFromDate:d] capitalizedString];
#else
/**** Solution 2 ****/
NSDateFormatter* df = [[[NSDateFormatter alloc] init] autorelease];
NSArray* weekdayNames = [df weekdaySymbols]; // or maybe one of its sibling methods? check what is returned here to be sure
return [weekdayNames objectAtIndex:weekday];
#endif
}
Is there a reason that you avoid NSMutableIndexSet?
It may simplify your code to:
NSArray *daysOfWeek = ...;
NSMutableIndexSet *indexesOfSelectedDays = ...;
NSArray *selectedDays = [daysOfWeek objectsAtIndexes:indexesOfSelectedDays];
Use NSPredicate to create a predicate that selects the items in the array where isSelected is true, and use that to filter the array using NSArray's -filteredArrayUsingPredicate: method. (#Miraaj posted a good example of using a predicate faster than I could type it.) Then take that filtered array and pick out the days, like this:
NSArray *selectedItems = [sourceArray filteredArrayUsingPredicate:somePredicate];
NSArray *days = [selectedItems valueForKey:#"day"];
NSString *daysString = [days componentsJoinedByString#", "];

Sort by Date: NSArray with NSArray with Date-Object

I have a simple NSArray which has some arrays as objects which has some dates and strings as objects:
NSArray (main array) ---------------> table view
NSArray (secondary array)---> table view cell
NSDate --------------------> table view cell text label
NSString -------------------> table view cell detail text label
etc.
I use the main array for my table view -> each cell got it's own 'secondary array'.
Now I want to sort the main array by the NSDate object. It sounds very easy but I have found no solution on the web for it.
I thought about using NSSortDesriptors but those just sort the array by the objects in the main array and not in the secondary array.
Hopefully you can help me
EDIT: Would it fix the problem if I use a NSDictionary as the secondary array?
You should be able to use NSArray sortedArrayUsingComparator if your app is targeted for iOS 4.0 or later:
NSArray *sortedArray = [mainArray sortedArrayUsingComparator: ^(id obj1, id obj2) {
return [[obj1 objectAtIndex:0] compare:[obj2 objectAtIndex:0]];
}];
This assumes that the date field is always in index 0 of the internal array. It would probably be a bit cleaner if you used a dictionary and keyed the date field by name, but if you are comfortable with the date field always remaining in index 0 then the above should work.
Assuming I've understood your data structure correctly, this should be close:
NSArray *sortedArray = [mainArray sortedArrayUsingComparator:^(id ary1, id ary2) {
NSArray *array1 = (NSArray *)ary1;
NSArray *array2 = (NSArray *)ary2;
NSDate *date1 = (NSDate *)[array1 objectAtIndex:0];
NSDate *date2 = (NSDate *)[array2 objectAtIndex:0];
return [date1 compare:date2];
}];

iphone uitableview grouped by date implementation

So currently I have a table that displays messages, but I'd like to group the messages by date, e.g.
Tuesday 4/5/98 //header
message 1
message 2
message 3
Wednesday 4/6/98
message 1
etc.
So right now, it's just one long NSMutableArray oldArr (sorted).
What I was thinking of doing, was creating another NSMutableArray (groupArr) of unique date objects (DateGroup), in which each DateGroup would have the following ivars:
int size; //number of messages for date
int index; //index of first message in the total array
//so I can easily retrieve the object when the section and row is asked
NSDate date; //need the date for the header
With these ivars, I can get all the sections with groupArr size, all the individual row sizes by accessing the DateGroup size, and the individual cell when given a section, row arguments by getting the index + row.
I think this is the best way to do it. However, I am having problems populating the groupArr from the oldArr (which will dynamically increase in size). I was thinking of going one by one through the oldArr with this psuedocode:
NSDate date = nil;
int size = 1;
int index = 0;
for (int i = 0; i < oldArr.size; i++) {
OldGroup* cur = [oldArr objectAt:i];
if (date is different from cur->date){ //i know, it's pseudocode
DateGroup* newGroup = [[DateGroup alloc] initWithDate:cur->date index:index];
[groupArr add:NewGroup];
date = cur->date;
index += size;
size = 1; <br/>
} else{ //the date is the same, so the object belongs in the group
[groupArr lastObject].size++;
}
}
Anyway, while I think this will work, it seems very unelegant to me. I was thinking about using the "indexOfObjectPassingTest" of NSMutableArry to find the next date, but can't seem to implement it conceptually. I'm trying to design a good way to do this. Any suggestions?
I haven't even compiled this so beware of errors and leaks, and it is perhaps not the most efficient way. But I think you can use the NSDateFormatter to chop the time component that you don't care about when sorting dates into groups. The result is an array of groups with each group being an array of your events, sorted into dates. Then you just need a sort predicate to arrange it in the order you choose to display it and it's ready for the UITableView.
- (void)addDatedEvent:(MyEventClass*)newEvent ToGroup:(NSMutableArray*)dateGroups
{
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
NSDate* eventDate = newEvent.evDate;
NSString* dateString = [dateFormatter stringFromDate:eventDate];
BOOL added = NO;
for(NSMutableArray* group in dateGroups)
{
MyEventlass* firstEvent = [group objectAtIndex:0];
NSDate* firstEventDate = firstEvent.evDate;
NSString* firstEventDateString = [dateFormatter stringFromDate:firstEventDate];
if([firstEventDateString isEqualToString:dateString])
{
// match - this event joins others in an existing group
[group addObject:newEvent];
added = YES;
}
}
if(added == NO)
{
// need to create a new group since this is the first date
NSMutableArray* newGroupArray = [NSMutableArray arrayWithObject:newEvent];
[dateGroups addObject:newGroupArray];
}
}

preparing a core data result set for a grouped uitableview

i've got a NSMutableArray created from a data source object
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[self setAmountArray: mutableFetchResults];
every object in my mutable array has the two instance variables name and timeadded.
now i want to display all names in a uitableview grouped by the day they are added. for that i wrote the following method
-(NSMutableArray*)arrangeTransfersByDate:(NSMutableArray*)transferArray {
// Setting up objects for this method
NSDate *oldDate = [NSDate dateWithTimeIntervalSince1970:0.0f];
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"yyyy-MM-dd"];
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for(transfers *transfer in transferArray) {
if( [[dateComparisonFormatter stringFromDate:[transfer timeadded]] isEqualToString:[dateComparisonFormatter stringFromDate:oldDate]] ) {
if([returnArray count] == 0) {
[returnArray addObject:[NSMutableArray arrayWithObject:transfer]];
} else {
[[returnArray objectAtIndex:[returnArray count]-1] addObject:transfer];
}
} else {
[returnArray addObject:[NSMutableArray arrayWithObject:transfer]];
oldDate = [transfer timeadded];
}
}
//[returnArray release];
[dateComparisonFormatter release];
return returnArray;
}
transferArray is my amountArray where my core data objects are stored.
so this works! but
is there a better way to do this? can you give me something like a "best practise" or simply have a look if there are some memory leaks?
thanks!
edit:
the right answer was NSFetchedResultController and its sectionNameKeyPath.
however i don't wanted to store my data twice a time.
so i created the following getter method in my NSManagedObject.
- (NSString *) pubDate {
[self willAccessValueForKey:#"pubDate"];
NSDateFormatter *dateComparisonFormatter = [[NSDateFormatter alloc] init];
[dateComparisonFormatter setDateFormat:#"dd.MM.YYYY"];
NSString *temp = [dateComparisonFormatter stringFromDate:[self pubTime]];
[dateComparisonFormatter release];
[self didAccessValueForKey:#"pubDate"];
return temp;
}
with this i can sort my tableviewcontroller by date using my FetchedResultController and my pubTime which is a timestamp.
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[self managedObjectContext]
sectionNameKeyPath:#"pubDate"
cacheName:#"transfersRoot"];
thanks to all
Generally when you want to display the results of a Core Data fetch in a UITableView, you use an NSFetchedResultsController. You can choose the attribute by which you want to group your results by specifying a sectionNameKeyPath during initialization.
That said, provided your method works then really it's up to you whether you want to change your code around or not.
But please make sure you [returnArray autorelease] before you return it. It's a generally accepted Objective-C practice that any method without "alloc", "new", or "copy" in the name will return an autoreleased object.