Sending multiple incrementing csv's from my app through e-mail - ios

I've been racking my brain over this for the past week now and can't quite figure out how to go about this.
I currently have an app that takes in survey data, saves it as a csv file in the form of surveydata-mm-dd-yyyy. This usually goes out to events that last multiple days so a normal event weekend would be
surveydata-09-13-2014
surveydata-09-14-2014
surveydata-09-15-2014
Now I want the person who is at these events to be able to simply click a button that will prepend an e-mail with all those files which are being stored in the apps documents folder.
I have it all pretty much setup and functioning minus being able to tell the app to look for those files with those names and to include them in the e-mail.
Here is the code I have
- (IBAction)emailButton:(id)sender {
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MM-dd-yyyy"];
NSString *dateString = [dateFormat stringFromDate:[NSDate date]];
NSString *path = [[self applicationDocumentsDirectory].path
stringByAppendingPathComponent:[NSString stringWithFormat:#"_SurveyData_%#.csv",dateString]];
/* if(![[NSFileManager defaultManager] fileExistsAtPath:path]) {*/
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
[mailer setSubject:#"CSV File"];
[mailer setToRecipients:toRecipents];
[mailer addAttachmentData:[NSData dataWithContentsOfFile:#"_SurveyData_%#.csv"]
mimeType:#"text/csv"
fileName:#"FileName"];
[self presentModalViewController:mailer animated:YES];
//}
}
If somebody could please help me out as I feel like I'm so close I'm just waiting for it to all click and make sense.
Please let me know if I'm missing something or my code is too vague.
This is the code that i'm using to write the data to the CSV for better understanding of how it's all going down.
-(void)writeSurveyDataToCSV:(NSString *)text {
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MM-dd-yyyy"];
NSString *dateString = [dateFormat stringFromDate:[NSDate date]];
NSString *path = [[self applicationDocumentsDirectory].path
stringByAppendingPathComponent:[NSString stringWithFormat:#"_SurveyData_%#.csv",dateString]];
if(![[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSString *header = #" gender,age,zip code,duration(sec), own a bike?,response1,response2,response3,response4,response5,response6, response7, response8, response9\n";
[header writeToFile:path atomically:YES
encoding:NSUTF8StringEncoding error:nil];
}
text = [text stringByAppendingString:#"\n"];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
}
EDIT: Thanks to Danh's guidance here's my solution
- (IBAction)emailButton:(id)sender {
NSArray *toRecipents = [NSArray arrayWithObject:#"reflex#ilovetheory.com"];
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
[mailer setSubject:#"CSV File"];
[mailer setToRecipients:toRecipents];
NSArray *filenames = [self filesNamesStartingAt:[NSDate date] count:165];
[self attachFilesNamed:filenames toMailer:mailer];
[self presentModalViewController:mailer animated:YES];
}
// answer count strings, named for days starting at date and the count-1 following days
- (NSArray *)filesNamesStartingAt:(NSDate *)date count:(NSInteger)count {
NSMutableArray *result = [NSMutableArray array];
static NSDateFormatter *dateFormat;
if (!dateFormat) {
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MM-dd-yyyy"];
}
for (int i=0; i<count; ++i) {
NSString *dateString = [dateFormat stringFromDate:date];
NSString *path = [[self applicationDocumentsDirectory].path
stringByAppendingPathComponent:[NSString stringWithFormat:#"_SurveyData_%#.csv",dateString]];
[result addObject:path];
date = [self addDayToDate:date];
}
for (int i=0; i<count; ++i) {
NSString *dateString = [dateFormat stringFromDate:date];
NSString *path = [[self applicationDocumentsDirectory].path
stringByAppendingPathComponent:[NSString stringWithFormat:#"_SurveyData_%#.csv",dateString]];
[result addObject:path];
date = [self subDayToDate:date];
}
return result;
}
// answer a new date, advanced one day from the passed date
- (NSDate *)addDayToDate:(NSDate *)date {
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:1];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
return [gregorian dateByAddingComponents:components toDate:date options:0];
}
- (NSDate *)subDayToDate:(NSDate *)date {
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:-1];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
return [gregorian dateByAddingComponents:components toDate:date options:0];
}
- (void)attachFilesNamed:(NSArray *)paths toMailer:(MFMailComposeViewController *)mailer {
for (NSString *path in paths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSData *data = [NSData dataWithContentsOfFile:path];
[mailer addAttachmentData:data mimeType:#"text/csv" fileName:path];
} else {
NSLog(#"warning, no file at path %#", path);
}
}
}
- (NSURL *)applicationDocumentsDirectory {
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
}

It does look like you're very close. Maybe decomposing the problem a little more is what's needed:
Start with a method that will create the file names for several days starting at a given day...
// answer count strings, named for days starting at date and the count-1 following days
- (NSArray *)filesNamesStartingAt:(NSDate *)date count:(NSInteger)count {
NSMutableArray *result = [NSMutableArray array];
static NSDateFormatter *dateFormat;
if (!dateFormat) {
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MM-dd-yyyy"];
}
for (int i=0; i<count; ++i) {
NSString *dateString = [dateFormat stringFromDate:date];
NSString *path = [[self applicationDocumentsDirectory].path
stringByAppendingPathComponent:[NSString stringWithFormat:#"_SurveyData_%#.csv",dateString]];
[result addObject:path];
date = [self addDayToDate:date];
}
return result;
}
// answer a new date, advanced one day from the passed date
- (NSDate *)addDayToDate:(NSDate *)date {
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:1];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
return [gregorian dateByAddingComponents:components toDate:date options:0];
}
Now, add a method that will attach a set of named files to a mail controller:
- (void)attachFilesNamed:(NSArray *)paths toMailer:(MFMailComposeViewController *)mailer {
for (NSString *path in paths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSData *data = [NSData dataWithContentsOfFile:path];
[mailer addAttachmentData:data mimeType:#"text/csv" fileName:path];
} else {
NSLog(#"warning, no file at path %#", path);
}
}
}
The rest practically writes itself (I hope)...
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
[mailer setSubject:#"CSV File"];
[mailer setToRecipients:toRecipents];
NSArray *filenames = [self fileNamesStartingAt:[NSDate date] count:3];
[self attachFilesNamed:filenames toMailer:mailer];
Note that, as written, this will use today and the next two days for filenames. If this isn't your requirement, you can tweak the addDay method to create a subtract days method, then work with those in tandem.

You're creating a filepath, but in the [mailer addAttachmentData] call, you're not passing in that path as I assume you intended--instead it's just getting the string that you used to build the path, so your NSData is going to be nil because it's not a complete path to anything on disk. Try changing that line to [mailer addAttachmentData:[NSData dataWithContentsOfFile:path]]

Related

Parse filename for label Xcode

The code below returns weather data. All works fine except for the current weather description. For example, if todaysimage file name is partlycloudyv3.png. The description shows as partlycloudy in my app. How can I make it appear as partly cloudy?
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *theView = [[NSBundle mainBundle] loadNibNamed:#"HipSkin01" owner:self options:nil];
UIView *nv = [theView objectAtIndex:0];
NSString *filePathDocArray = [DOCUMENTS stringByAppendingPathComponent:#"r.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePathDocArray]) {
NSLog(#"The file exists");
dataz = [[NSMutableDictionary alloc] initWithContentsOfFile:filePathDocArray];
NSLog(#"The file exists %#",dataz);
NSLog(#"The array: %i",(int) [dataz count]);
}
NSString *todaysimage = [dataz valueForKey:#"icon"];
[self.weatherIcon setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%#v3.png", todaysimage]]];
NSArray *arr1 = [dataz objectForKey:#"display_location"];
NSString *plasez = [arr1 valueForKey:#"full"];
//NSString *plasez = [arr1 valueForKey:#"city"];
[self.plase setText:plasez];
//DataFormater
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM dd, YYYY"];
[formatter setDateStyle:NSDateFormatterMediumStyle];
NSString *dateToday = [formatter stringFromDate:[NSDate date]];
[self.dataLable setText:dateToday];
[self.nameWeather setText:todaysimage];
//temp
NSString *tempzC = [dataz valueForKey:#"feelslike_c"];
NSString *tempzF = [dataz valueForKey:#"feelslike_f"];
NSString *terC = [NSString stringWithFormat:#"%#°C",tempzC];
NSString *terF = [NSString stringWithFormat:#"%#°F",tempzF];
[self.temprC setText:terC];
[self.temprF setText:terF];
[self addSubview:nv];
}
return self;
}
I added these lines and the parsing issue was solved. I was referencing the incorrect item in the weather API.
NSString *todaysimage2 = [dataz valueForKey:#"weather"];
[self.weatherIcons setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%#v3.png", todaysimage]]];
[self.weatherData setText:dateToday];
[self.weatherText setText:todaysimage2];

how to assign a sqlite3 table to nsarray

I am developing a chatting app in that i am using sqlite for storing and retrieving chat history i am able to insert the values but while retrieving i am getting the nil in array here is my code
messageArray=[DBObject AllRowFromTableName:#"chatTable" withUserID:myJID withFriendID:chatWithUser];
for (int i=0; i<[messageArray count]; i++) {
Message *msg=[[Message alloc] init];
msg.text=[[messageArray objectAtIndex:i] msg];
if ([[messageArray objectAtIndex:i] lft_rght]==0) {
msg.fromMe=YES;
}
else{
msg.fromMe=NO;
}
msg.type=SOMessageTypeText;
NSString *dateString =[[messageArray objectAtIndex:i] tim_dte];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm,yyyy/MM/dd"];
NSDate *dateFromString = [[NSDate alloc] init];
dateFromString = [dateFormatter dateFromString:dateString];
msg.date=dateFromString;
[self.chatHistoryArray addObject:msg];
this is my retrieving class ad database class is
-(NSMutableArray *)AllRowFromTableName:(NSString *)tableName withUserID:(NSString *)userid withFriendID:(NSString *)friendid
{
NSMutableArray *array=[[NSMutableArray alloc] init];
NSString *sqlString=[NSString stringWithFormat:#"select *from chatTable where toid='%#' and fromid='%#'",userid,friendid];
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(db, [sqlString UTF8String], -1, &statement, nil)==SQLITE_OK) {
while (sqlite3_step(statement)==SQLITE_ROW) {
Message *tempSkeleton=[[Message alloc] init];
tempSkeleton.mToID =[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
tempSkeleton.mFromID=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
tempSkeleton.text=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 3)];
//tempSkeleton.fromMe=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 3)];
tempSkeleton.fromMe=((int)sqlite3_column_int(statement, 4)) ? YES : NO;
tempSkeleton.tim_dte=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 5)];
[array addObject:tempSkeleton];
// NSLog(#"IN LOOP=>%#",array);
}
}
i am getting msg.text empty can ay body help me please
IN your loop that is processing the returned array, these lines...
Message *msg=[[Message alloc] init];
msg.text=[[messageArray objectAtIndex:i] msg];
It should just be...
Message *msg=[[Message alloc] init];
msg.text=[[messageArray objectAtIndex:i] text];
You should also create the NSDateFormatter once, outside of the loop, and use that, as its not a "quick" object to create.
You might also want to refactor the constant calls to [messageArray objectAtIndex:i]. Create a local variable that gets hold of the reference once and then use that. So refactored code would look something like...
messageArray=[DBObject AllRowFromTableName:#"chatTable" withUserID:myJID withFriendID:chatWithUser];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm,yyyy/MM/dd"];
for (int i=0; i<[messageArray count]; i++) {
Message * baseMessage = [messageArray objectAtIndex:i];
Message *msg=[[Message alloc] init];
msg.text= baseMessage.text;
msg.fromMe= (baseMessage.left_rght == 0);
msg.type=SOMessageTypeText;
msg.date=[dateFormatter dateFromString:baseMessage.tim_dte];
[self.chatHistoryArray addObject:msg];
}
You could also use fast enumerators and maybe check out modern objective c https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html

Performing Core Data Operations in self created multiple threads to save processing time

I have surfed a lot about performing core data operations in multiple threads but no good luck to solve my problem.
My code is such that I have to download a csv file after every ten minutes which contains each entry of 10 seconds. This file once downloaded is parsed and the contents are saved in database and then files are removed as then, when needed, I can fetch data from database.
Now, I have a huge existing content of more than a month for now which may extend to years also as time passes by, performing this huge task of saving new files to database and fetching objects from core data into an array for already downloaded files using a single thread is causing a huge processing time. Also, views in app needs to be adjusted with all previous data (They are basically plots of quantity vs time).
How can I achieve this in multiple threads and optimize my code processing time and reduce UI Blockage to minimum?
Please note that : Performing the task in background thread is not my concern as I in any case have to show graphs on the basis of total data. Please provide valuable advices.
EDIT: PERFORMED SOME MULTITHREADING
HERE IS THE NEW CODE,
- (void) downloadFiles:(NSString *)dataPath{
__block AppDelegate *appD = (AppDelegate *)[[UIApplication sharedApplication] delegate];
backgroundMOC = [[NSManagedObjectContext alloc] init];
[backgroundMOC setPersistentStoreCoordinator:[[appD managedObjectContext] persistentStoreCoordinator]];
if (parsedDetailsDataArrayForCurrentDay) {
parsedDetailsDataArrayForCurrentDay = nil;
}
if (parsedDetailsDataArrayForCurrentMonth) {
parsedDetailsDataArrayForCurrentMonth = nil;
}
if (parsedDetailsDataArrayForCurrentYear) {
parsedDetailsDataArrayForCurrentYear = nil;
}
parsedDetailsDataArrayForCurrentDay = [[NSMutableArray alloc] init];
parsedDetailsDataArrayForCurrentMonth = [[NSMutableArray alloc] init];
parsedDetailsDataArrayForCurrentYear = [[NSMutableArray alloc] init];
dispatch_group_t d_group = dispatch_group_create();
for (NSInteger i = 0; i <= (self.filesListArray.count - 1); i++) {
NSString *filePathOnPhone = [dataPath stringByAppendingString:[NSString stringWithFormat:#"/%#", [[self.filesListArray objectAtIndex:i] objectForKey:#"kCFFTPResourceName"]]];
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"ParsedInfiDetails"];
NSString *nameToGet = [[self.filesListArray objectAtIndex:i] objectForKey:#"kCFFTPResourceName"];
NSLog(#"File Check: %#", nameToGet);
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"fileName = %#", nameToGet];
[fetch setPredicate:predicate];
NSError *error = nil;
NSArray *results = [backgroundMOC executeFetchRequest:fetch error:&error];
NSArray *definedResults = [results copy];
if(definedResults && (definedResults.count !=0)) {
NSLog(#"Entities with that name: %#", results);
#autoreleasepool {
NSArray *result = [[definedResults sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modDate" ascending:YES]]] copy];
NSDate *specificDate = [NSDate date];
NSMutableArray *sortedDateArray = [[NSMutableArray alloc] init];
NSMutableArray *sortedDateCurrentYearArray = [[NSMutableArray alloc] init];
NSMutableArray *sortedDateCurrentMonthArray = [[NSMutableArray alloc] init];
for (int i = 0; i < result.count; i++) {
NSManagedObject *obj = [result objectAtIndex:i];
NSDate *objDate = [obj valueForKey:#"modDate"];
NSCalendar *gregorian = [NSCalendar currentCalendar];
NSDateComponents *components = [gregorian componentsInTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"] fromDate:objDate];
NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];
NSDateComponents *specificComps = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:specificDate];
NSInteger specificDay = [specificComps day];
NSInteger specificMonth = [specificComps month];
NSInteger specificYear = [specificComps year];
if(day == 24){
}
if (day == specificDay && month == specificMonth && year == (specificYear-2000)) {
[sortedDateArray addObject:obj];
}
NSDate *todayDate = [NSDate date];
NSDateComponents *componentsForToday = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:todayDate];
NSInteger currentMonth = [componentsForToday month];
NSInteger currentYear = [componentsForToday year];
if (year == (currentYear-2000)) {
[sortedDateCurrentYearArray addObject:obj];
}
if (year == (currentYear -2000) && month == currentMonth) {
[sortedDateCurrentMonthArray addObject:obj];
}
}
NSMutableArray *sortedTimedArray = [[NSMutableArray alloc] initWithArray:[sortedDateArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modTime" ascending:YES]]]];
NSMutableArray *sortedTimedCurrentYearArray = [[NSMutableArray alloc] initWithArray:[sortedDateCurrentYearArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modTime" ascending:YES]]]];
NSMutableArray *sortedTimedCurrentMonthArray = [[NSMutableArray alloc] initWithArray:[sortedDateCurrentMonthArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modTime" ascending:YES]]]];
[parsedDetailsDataArrayForCurrentDay addObjectsFromArray:sortedTimedArray];
[parsedDetailsDataArrayForCurrentYear addObjectsFromArray:sortedTimedCurrentYearArray];
[parsedDetailsDataArrayForCurrentMonth addObjectsFromArray:sortedTimedCurrentMonthArray];
}
} else {
NSLog(#"Error: %#", error);
NSString *threadName = [NSString stringWithFormat:#"%ld THREAD", (long)i];
dispatch_queue_t myQueue = dispatch_queue_create([threadName UTF8String], NULL);
dispatch_group_async(d_group, myQueue, ^{
NSManagedObjectContext *backgroundMOC1;
backgroundMOC1 = [[NSManagedObjectContext alloc] init];
[backgroundMOC1 setPersistentStoreCoordinator:[[appD managedObjectContext] persistentStoreCoordinator]];
NSLog(#"Entered Thread ");
BOOL success = [appD.ftpManager downloadFile:[[self.filesListArray objectAtIndex:i] objectForKey:#"kCFFTPResourceName"] toDirectory:[NSURL URLWithString:dataPath] fromServer:srv];
if (success) {
// dispatch_group_async(d_group, myQueue, ^{
NSMutableDictionary *dict = [appD.ftpManager progress];
NSString *filePath = [dataPath stringByAppendingString:[NSString stringWithFormat:#"/%#", [[self.filesListArray objectAtIndex:i] objectForKey:#"kCFFTPResourceName"]]];
CHCSVParser *parser = [[CHCSVParser alloc] initWithContentsOfCSVURL:[NSURL fileURLWithPath:filePath]];
[parser parse];
NSMutableArray *currentFileComponentsArray = [NSArray arrayWithContentsOfCSVURL:[NSURL fileURLWithPath:filePath]];
NSMutableArray *parsedDetailsEntitiesArray = [[NSMutableArray alloc] init];
for (int j = 1; j <= (currentFileComponentsArray.count-1); j++) {
NSArray *detailsArray = [currentFileComponentsArray objectAtIndex:j];
if (!(detailsArray.count < 32)) {
NSManagedObject *parsedDetails = [NSEntityDescription
insertNewObjectForEntityForName:#"ParsedInfiDetails"
inManagedObjectContext:[appD managedObjectContext]];
NSString *totalDateString = [NSString stringWithFormat:#"%# %#", [detailsArray objectAtIndex:0], [detailsArray objectAtIndex:1]];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"dd/MM/yyyy HH:mm:ss";
NSTimeZone *gmt = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
[dateFormatter setTimeZone:gmt];
NSDate *startDate = [dateFormatter dateFromString:totalDateString];
[parsedDetails setValue:startDate forKey:#"modDate"];
[parsedDetails setValue:startDate forKey:#"modTime"];
———————————————————————PERFORM PARSEDDETAILS STATEMENTS----------------
NSError *error;
NSLog(#"Saved File in Database: %#", [[self.filesListArray objectAtIndex:i] objectForKey:#"kCFFTPResourceName"]);
NSLog(#"Saved thread");
if (![backgroundMOC1 save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
else{
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
//if the download fails, we try to delete the empty file created by the stream.
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
//when data is stored in coredata remove the downloaded file.
}
[parsedDetailsEntitiesArray addObject:parsedDetails];
}
}
}
NSSet *set = [[NSSet alloc] initWithArray:parsedDetailsEntitiesArray];
[self.relevantInverId setValue:set forKey:#"infiDetails"];
NSDate *specificDate = [NSDate date];
#autoreleasepool {
NSMutableArray *sortedDateArray = [[NSMutableArray alloc] init];
NSMutableArray *sortedDateCurrentYearArray = [[NSMutableArray alloc] init];
NSMutableArray *sortedDateCurrentMonthArray = [[NSMutableArray alloc] init];
for (int i = 0; i < parsedDetailsEntitiesArray.count; i++) {
NSManagedObject *obj = [parsedDetailsEntitiesArray objectAtIndex:i];
NSDate *objDate = [obj valueForKey:#"modDate"];
NSCalendar *gregorian = [NSCalendar currentCalendar];
NSDateComponents *components = [gregorian componentsInTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"] fromDate:objDate];
NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];
NSDateComponents *specificComps = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:specificDate];
NSInteger specificDay = [specificComps day];
NSInteger specificMonth = [specificComps month];
NSInteger specificYear = [specificComps year];
if(day == 24){
}
if (day == specificDay && month == specificMonth && year == (specificYear-2000)) {
[sortedDateArray addObject:obj];
}
NSDate *todayDate = [NSDate date];
NSDateComponents *componentsForToday = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:todayDate];
NSInteger currentMonth = [componentsForToday month];
NSInteger currentYear = [componentsForToday year];
if (year == (currentYear-2000)) {
[sortedDateCurrentYearArray addObject:obj];
}
if (year == (currentYear-2000) && month == currentMonth) {
[sortedDateCurrentMonthArray addObject:obj];
}
}
NSMutableArray *sortedTimedArray = [[NSMutableArray alloc] initWithArray:[sortedDateArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modTime" ascending:YES]]]];
[parsedDetailsDataArrayForCurrentDay addObjectsFromArray:sortedTimedArray];
NSMutableArray *sortedTimedCurrentYearArray = [[NSMutableArray alloc] initWithArray:[sortedDateCurrentYearArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modTime" ascending:YES]]]];
NSMutableArray *sortedTimedCurrentMonthArray = [[NSMutableArray alloc] initWithArray:[sortedDateCurrentMonthArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modTime" ascending:YES]]]];
[parsedDetailsDataArrayForCurrentYear addObjectsFromArray:sortedTimedCurrentYearArray];
[parsedDetailsDataArrayForCurrentMonth addObjectsFromArray:sortedTimedCurrentMonthArray];
}
// });
}
});
}
BOOL isFileAlreadyPresent = [[NSFileManager defaultManager] fileExistsAtPath:filePathOnPhone];
}
NSMutableArray *sortedParsedDetailsArrayForCurrentDay = [[NSMutableArray alloc] initWithArray:[parsedDetailsDataArrayForCurrentDay sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"modTime" ascending:YES]]]];
NSDate *startDate ;
NSDate *endaDate;
dispatch_group_notify(d_group, dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self stopLogoSpin];
[hud dismiss];
});
NSLog(#"All background tasks are done!!");
});
}
Now, logs #"Entered Thread " is visible in log but, #"Saved File in Database: %#", [[self.filesListArray objectAtIndex:i] and #"Saved thread" are not called.
Also,
BOOL success = [appD.ftpManager downloadFile:[[self.filesListArray objectAtIndex:i] objectForKey:#"kCFFTPResourceName"] toDirectory:[NSURL URLWithString:dataPath] fromServer:srv];
helps download file with their seperate thread in the download method.
Should uncommenting below help?
// dispatch_group_async(d_group, myQueue, ^{
Main thread is intentionally made to wait but this block below is also never called:
dispatch_group_notify(d_group, dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self stopLogoSpin];
[radialHUD dismiss];
});
NSLog(#"All background tasks are done!!");
});
Please advice any modification and solution to problems above.
Optimised the code by moving all what is not needed in loop outside and the performing the looping operation using GCD.
dispatch_queue_t myOwnQueue = dispatch_queue_create([#"MyOwnQueue" UTF8String], NULL);
dispatch_apply(self.filesListArray.count, myOwnQueue, ^(size_t i) { });
This lead to reduction in time by half. However any further optimisations need to be seen.

Using UICollectionReusableView to group Cells

I'm having trouble updating my old code which made synchronous JSOn calls to a new one which makes asynchronous calls using AFNetworking.
In my old code I was grouping cells with a UICollectionReusableView using a date string ("release_date"), all of this was done in the viewDidLoad. Now with AFNetworking I moved everything out of the viewDidLoad, so I'm stuck trying to figure out how to merge my old code with my new one.
This is the new code I have to parse my JSON with AFNetworking:
- (void)viewDidLoad
{
[super viewDidLoad];
self.upcomingReleases = [[NSMutableArray alloc] init];
[self makeReleasesRequests];
[self.collectionView registerClass:[ReleaseCell class] forCellWithReuseIdentifier:#"ReleaseCell"];
}
-(void)makeReleasesRequests //AFNetworking Call
{
NSURL *url = [NSURL URLWithString:#"http://www.soleresource.com/upcoming.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"#");
self.upcomingReleases = [responseObject objectForKey:#"upcoming_releases"];
[self.collectionView reloadData];
} failure:nil];
[operation start];
}
Code I had in my viewDidLoad before I started using AFNetworking to make JSON calls and "group" my cells:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *upcomingReleaseURL = [NSURL URLWithString:#"http://www.soleresource.com/upcoming.json"];
NSData *jsonData = [NSData dataWithContentsOfURL:upcomingReleaseURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
NSArray *upcomingReleasesArray = [dataDictionary objectForKey:#"upcoming_releases"];
//This is the dateFormatter we'll need to parse the release dates
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:#"EST"];
[dateFormatter setTimeZone:est];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US"]]; //A bit of an overkill to avoid bugs on different locales
//Temp array where we'll store the unsorted bucket dates
NSMutableArray *unsortedReleaseWeek = [[NSMutableArray alloc] init];
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];
for (NSDictionary *upcomingReleaseDictionary in upcomingReleasesArray) {
//We find the release date from the string
NSDate *releaseDate = [dateFormatter dateFromString:[upcomingReleaseDictionary objectForKey:#"release_date"]];
//We create a new date that ignores everything that is not the actual day (ignoring stuff like the time of the day)
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components =
[gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:releaseDate];
//This will represent our releases "bucket"
NSDate *bucket = [gregorian dateFromComponents:components];
//We get the existing objects in the bucket and update it with the latest addition
NSMutableArray *releasesInBucket = [tmpDict objectForKey:bucket];
if (!releasesInBucket){
releasesInBucket = [NSMutableArray array];
[unsortedReleaseWeek addObject:bucket];
}
UpcomingRelease *upcomingRelease = [UpcomingRelease upcomingReleaseWithName:[upcomingReleaseDictionary objectForKey:#"release_name"]];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
[releasesInBucket addObject:upcomingRelease];
[tmpDict setObject:releasesInBucket forKey:bucket];
}
[unsortedReleaseWeek sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDate* date1 = obj1;
NSDate* date2 = obj2;
//This will sort the dates in ascending order (earlier dates first)
return [date1 compare:date2];
//Use [date2 compare:date1] if you want an descending order
}];
self.releaseWeekDictionary = [NSDictionary dictionaryWithDictionary:tmpDict];
self.releaseWeek = [NSArray arrayWithArray:unsortedReleaseWeek];
[self.collectionView registerClass:[ReleaseCell class] forCellWithReuseIdentifier:#"ReleaseCell"];
}
CollectionViewCell
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
ReleaseCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
// Part of my new code AFNetworking
NSDictionary *upcomingReleaseDictionary = [self.upcomingReleases objectAtIndex:indexPath.row];
//
// I had this in my old code
UpcomingRelease *upcomingRelease = [self.releaseWeekDictionary objectForKey:self.releaseWeek[indexPath.section]][indexPath.row];
//
cell.release_name.text = upcomingRelease.release_name;
return cell;
}
This is the rest:
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return [self.releaseWeek count];
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [[self.releaseWeekDictionary objectForKey:self.releaseWeek[section]] count];
}
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
ReleaseWeek *releaseWeek = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:#"releaseWeek" forIndexPath:indexPath];
//We tell the formatter to produce a date in the format "Name-of-the-month day"
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMMM dd"];
NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:#"EST"];
[dateFormatter setTimeZone:est];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US"]];
//We read the bucket date and feed it to the date formatter
NSDate *releaseWeekDate = self.releaseWeek[indexPath.section];
releaseWeek.releaseDate.text = [[dateFormatter stringFromDate:releaseWeekDate] uppercaseString];
return releaseWeek;
}
I'm basically trying to figure out how to take the code that grouped my cells be the date string and integrate it with my new code.
Thanks.
Think MVC. Separate your model (the stuff that comes from the network) into a separate class that does the network operations, and grouping things into arrays and dictionaries. Your view controller should simply observe the model. You can use delegation or (my favorite) KVO to know when the model has updated data available. Then you just update your collection view. Your view controller should simply be an interface between the model and the views. If you separate things out this way you will find that it is much more natural, and you aren't fighting against the system.
You are closer than you think.
Simply put everything you used to do in viewDidLoad: (everything between the assignment of jsonData to the registration of your collection view class) into the callback block of your AFNetworking call (where jsonData is now called responseObject).
At the end of the callback block, simply invoke [self.collectionView reloadData], and the your collection view will reload itself (i.e. call numberOfItems and cellForItemAtIndexPath for each item).
In your UICollectionViewDataSource methods that return the number of sections and items, simply return 0 if the properties that hold your model are nil or empty.
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
// Correctly returns 0 if nil or empty.
return [self.releaseWeek count];
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if(!self.releaseWeekDictionary[section] || !self.releaseWeek[section]) {
return 0;
}else{
return [self.releaseWeekDictionary[self.releaseWeek[section]] count];
}
}
Below should be the gravy code in your completion block.
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"#");
NSArray *upcomingReleasesArray = [dataDictionary objectForKey:#"upcoming_releases"];
//This is the dateFormatter we'll need to parse the release dates
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:#"EST"];
[dateFormatter setTimeZone:est];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US"]]; //A bit of an overkill to avoid bugs on different locales
//Temp array where we'll store the unsorted bucket dates
NSMutableArray *unsortedReleaseWeek = [[NSMutableArray alloc] init];
NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init];
for (NSDictionary *upcomingReleaseDictionary in upcomingReleasesArray) {
//We find the release date from the string
NSDate *releaseDate = [dateFormatter dateFromString:[upcomingReleaseDictionary objectForKey:#"release_date"]];
//We create a new date that ignores everything that is not the actual day (ignoring stuff like the time of the day)
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components =
[gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:releaseDate];
//This will represent our releases "bucket"
NSDate *bucket = [gregorian dateFromComponents:components];
//We get the existing objects in the bucket and update it with the latest addition
NSMutableArray *releasesInBucket = [tmpDict objectForKey:bucket];
if (!releasesInBucket){
releasesInBucket = [NSMutableArray array];
[unsortedReleaseWeek addObject:bucket];
}
UpcomingRelease *upcomingRelease = [UpcomingRelease upcomingReleaseWithName:[upcomingReleaseDictionary objectForKey:#"release_name"]];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:#"release_date"];
[releasesInBucket addObject:upcomingRelease];
[tmpDict setObject:releasesInBucket forKey:bucket];
}
[unsortedReleaseWeek sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSDate* date1 = obj1;
NSDate* date2 = obj2;
//This will sort the dates in ascending order (earlier dates first)
return [date1 compare:date2];
//Use [date2 compare:date1] if you want an descending order
}];
self.releaseWeekDictionary = [NSDictionary dictionaryWithDictionary:tmpDict];
self.releaseWeek = [NSArray arrayWithArray:unsortedReleaseWeek];
[self.collectionView reloadData];
} failure:nil];
Edit: In your code, you were no longer assigning anything to upcomingReleases (block of commented code lines 91-102), and you were crashing referencing the index that didn't exist in the array. The fix is easy:
109: self.upcomingReleases = [dataDictionary objectForKey:#"upcoming_releases"];
122: for (NSDictionary *upcomingReleaseDictionary in self.upcomingReleases) {

Combining 2 NSDateFormatters into one

Currently I have two NSDateFormatters in my app and I want to somehow "combine" them so I only have, since they're parsing the same date.
I have a NSObject called UpcomingReleases, thats where all my JSON info gets stored.
UpcomingRelease.h
- (NSString *) formattedDate;
UpcomingRelease.m
- (NSString *) formattedDate {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSDate *readableDate = [dateFormatter dateFromString:self.release_date];
[dateFormatter setDateFormat:#"MMMM dd"];
return [dateFormatter stringFromDate:readableDate];
}
My UICollectionViewController (UpcomingReleasesViewController.m)
if([upcomingReleaseDictionary objectForKey:#"release_date"] != NULL)
{
NSString *readableDate = [upcomingReleaseDictionary objectForKey:#"release_date"];
UpcomingRelease *upcoming = [[UpcomingRelease alloc] init];
upcoming.release_date = readableDate;
cell.release_date.text = [NSString stringWithFormat:#"%#", upcoming.formattedDate];
}
My detailedViewController (ReleaseViewController.m)
(_singleRelease is a NSDictionary)
- (void)viewDidLoad
{
[super viewDidLoad];
if([_singleRelease objectForKey:#"release_date"] != NULL)
{
NSString *readableDate = [_singleRelease objectForKey:#"release_date"];
UpcomingRelease *singleRelease = [[UpcomingRelease alloc] init];
singleRelease.release_date = readableDate;
self.release_date.text = [NSString stringWithFormat:#"%#", singleRelease.formattedDate];
}
}
This was working fine, until I added a share on twitter action and I had to add another NSDateFormatter so it could show the readable date inside the tweet (I would get an error saying "No visible interface for ReleaseViewController declares the selected 'formattedDate'" otherwise).
- (NSString *) formattedDate:(NSString *)jsonDateString {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSDate *readableDate = [dateFormatter dateFromString:jsonDateString];
[dateFormatter setDateFormat:#"MMMM dd"];
return [dateFormatter stringFromDate:readableDate];
}
#pragma mark - Share on twitter
- (IBAction)shareOnTwitter:(id)sender {
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])
{
SLComposeViewController *tweetSheet = [SLComposeViewController
composeViewControllerForServiceType:SLServiceTypeTwitter];
NSString *formattedDate = [self formattedDate:[_singleRelease objectForKey:#"release_date"]];
[tweetSheet setInitialText:[NSString stringWithFormat:#"%#", formattedDate]];
[self presentViewController:tweetSheet animated:YES completion:nil];
}
}
How can I combine both these NSDateFormatters into one? They're both parsing the same string in the same way (also if there's a better way to show the formattedDate string than the one I'm currently doing would be great).
This is how my JSON shows the date string:
release_date: "2013-11-16T00:00:00.000Z"
Thanks.
As #Hot Licks says:
Make the date formatter a class method:
+ (NSString *) formattedDate:(NSString *)jsonDateString {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
NSDate *readableDate = [dateFormatter dateFromString:jsonDateString];
[dateFormatter setDateFormat:#"MMMM dd"];
return [dateFormatter stringFromDate:readableDate];
}
Put it in a utility class and use it in both cases. In the first case just pass in self.release_date.

Resources