This is my code to download the pdf files using NSUrlConnection and i downloading them to the document directories.
-(void)startDownloadFile:(UIButton *)sender
{
ResourceTelephones * resource = [array objectAtIndex:sender.tag];
NSString * currentURL = resource.website;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:currentURL]];
NSURLConnection *conn = [[NSURLConnection alloc] init];
(void)[conn initWithRequest:request delegate:self];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:currentURL]];
dispatch_sync(dispatch_get_main_queue(), ^{
});
NSString * filePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:[NSString stringWithFormat:#"myPDF%ld.pdf",(long)sender.tag]];
NSLog(#"PDF path: %#",filePath);
[data writeToFile:filePath atomically:YES];
});
}
and am viewing the file using below code
-(void)viewAction:(UIButton *)sender
{
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [searchPaths objectAtIndex:0];
NSURL *url = [NSURL fileURLWithPath:[documentPath stringByAppendingPathComponent:[NSString stringWithFormat:#"myPDF%ld.pdf",(long)sender.tag]]];
if (url) {
// Initialize Document Interaction Controller
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:url];
// Configure Document Interaction Controller
[_documentInteractionController setDelegate:self];
// Preview PDF
[_documentInteractionController presentPreviewAnimated:YES];
}
}
now i want to put the progress bar for each cell corresponding to each pdf file for downloading..and after downloading the file..i want to change the name of the button "download" to "view" and change the corresponding action..and one more thing i should able to view the file after re opening the application because it is already downloaded..
For progress bar updates, use NSURLConnection delegates,
Example:
(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//here you will get total length
totalLength = [response expectedContentLength];
}
(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// here calculate the downloaded length
[self.pdfData appendData:data];
//calculate downloaded percent
progress = ([self.pdfData length] * 100) / totalLength;
//and update the progress bar
}
(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//save file here
//flip the corresponding button to view here
}
And to view the downloaded pdf file, check the file is already downloaded
Example:
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
// show the view button
}
else
//start download
To calculate the progress of downloading using NSURLconnection use the following delegate function.
-(void) connection:(NSURLConnection *) connection
didReceiveResponse:(NSURLResponse *) response
{
//In this function you will get the total size of PDF data you are receiving
fTotalSize=(float)[response expectedContentLength];
if(!datWebServiceData)
datWebServiceData =[[NSMutableData alloc]init];
//datWebServiceData = [[NSMutableData data] retain];
[datWebServiceData setLength: 0];
}
-(void) connection:(NSURLConnection *) connection
didReceiveData:(NSData *) data
{
//Here calculate the progress
float val=(((float) [datWebServiceData length] / (float) fTotalSize))*100;
lblProgress.text=[NSString stringWithFormat:#"%0.0f%%",val];
}
lblProgress.text will show the progress of downloading and datWebServiceData is the NSData which will contain the whole data of PDF.
Now to show progress for each cell, save the connection object in NSMutableArray. Then check for the connection object of delegate and all connection objection in array, if they matched then take the array index and then update the entry of that cell accordingly.
firstly create a property of UIButton as below-
#property (strong,nonatomic) IBOutlet UIButton *btn;
Then make the connectivity of it. Also in viewDidLoad method do as follow-
[_btn setTitle:#"DOWNLOADING" forState:UIControlStateNormal];
[_btn addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
Therefore ,in your startDownloadFile method add following below-
NSLog(#"PDF path: %#",filePath);
[data writeToFile:filePath atomically:YES];
/* ADD BELOW LINE */
[_btn setTitle:#"VIEW" forState:UIControlStateNormal];
Then for buttonClicked: method as-
-(void)buttonClicked:(UIButton *)sender
{
if([sender.titleLabel.text isEqualToString:#"VIEW"])
{
/* Now as title is VIEW so call viewAction method */
[self viewAction:sender]
}
else
{
/*When tapped in DOWNLOADING mode nothing will happen */
}
}
Related
I have an iOS app that has a watchOS extension, both of which are build in Objective-C. The app needs to send over an array when it loads up (viewDidLoad) or appears (viewDidAppear:(BOOL)animated). When the app loads up, nothing happens, but if I somehow add a file to the app (either through the built-in adder or by importing one from my desktop or AirDrop), the watch app updates. Pressing the refresh button does not make the app work. However, once I add a file, it updates fine and starts updating perfectly. I'm not sure why this happens, but here is some code I used. I cut out some less useful code.
In viewDidLoad:
- (void)viewDidLoad
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *appFolderPath = [path objectAtIndex:0];
NSString *inboxAppFolderPath = [appFolderPath stringByAppendingString:#"/Inbox"]; //changes the directory address to make it for the inbox
//move all files from inbox to documents root
//get to inbox directory
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSArray *inboxContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSString stringWithFormat:inboxAppFolderPath,documentsDirectory] error:nil];
//move all the files over
for (int i = 0; i != [inboxContents count]; i++)
{
NSString *oldPath = [NSString stringWithFormat:#"%#/%#", inboxAppFolderPath, [inboxContents objectAtIndex:i]];
//NSLog(oldPath);
NSString *newPath = [NSString stringWithFormat:#"%#/%#", appFolderPath, [inboxContents objectAtIndex:i]];
//NSLog(newPath);
NSError* error;
[[NSFileManager defaultManager] moveItemAtPath:oldPath toPath:newPath error:&error];
NSLog(#"error: %#", error.localizedDescription);
}
//end of moving all files
//Turn every file inside the directory into an array
//add directory in case it doesn't already exist
if (![[NSFileManager defaultManager] fileExistsAtPath: inboxAppFolderPath])
{
[[NSFileManager defaultManager] createDirectoryAtPath: inboxAppFolderPath withIntermediateDirectories:NO attributes:nil error:nil];
NSLog(#"directory created");
}
//a bunch of NSPredicates go here to filter out the array
[self sendStuffToWatch];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self connectToWatch];
}
In -(void)connectToWatch, there is
-(void)connectToWatch
{
//set up WatchConnectivity session
if([WCSession isSupported])
{
self.watchSession = [WCSession defaultSession];
self.watchSession.delegate = self;
[self.watchSession activateSession];
if([WCSession defaultSession].paired){
NSLog(#"Watch session active");
}
else
{
NSLog(#"WATCH NOT CONNECTED");
}
}
}
In -(void)sendStuffToWatch
-(void)sendStuffToWatch
{
if(self.watchSession)
{
NSError *error;
NSDictionary *applicationDict = #{#"array":recipes};
[self.watchSession updateApplicationContext:applicationDict error:&error];
NSLog(#"sent info to watch");
}
}
In -(void)viewDidAppear:(BOOL)animated, there's only this
-(void)viewDidAppear:(BOOL)animated
{
[self sendStuffToWatch];
}
The code for the refresh button is
-(IBAction)Refresh:(id)sender
{
[recipeTable reloadData];
[self sendStuffToWatch];
[self viewDidLoad];
}
Your methods are all out of order. You want to first call super, then setup connectivity, filter the array, and then finally send the data to the watch.
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self connectToWatch];
// rest of code here to create recipe array
[self sendStuffToWatch];
Also, you shouldn't be calling viewDidLoad in your own code. It should only be called once, for you, not by you, when the view controller is first loaded into memory.
If you have code you want to rerun, then move it out of viewDidLoad into its own method, and just call that method.
My Requirement is download all images in application memory and display it from local if its available.
Below is my code to access image from local and if its not available then it will download then display.
[cell.imgProfilePic processImageDataWithURLString:cData.PICTURE];
I have made custom UIImageView class
DImageView.h
#import <UIKit/UIKit.h>
#interface DImageView : UIImageView
#property (nonatomic, strong) UIActivityIndicatorView *activityView;
- (void)processImageDataWithURLString:(NSString *)urlString;
+ (UIImage *)getSavedImage :(NSString *)fileName;
#end
DImageView.m
#import "DImageView.h"
#define IMAGES_FOLDER_NAME #"DImages"
#implementation DImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{ }
return self;
}
- (void)dealloc
{
self.activityView = nil;
[super dealloc];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self initWithFrame:[self frame]];
}
return self;
}
- (void)processImageDataWithURLString:(NSString *)urlString
{
#autoreleasepool
{
UIImage * saveImg =[DImageView getSavedImage:urlString];
if (saveImg)
{
#autoreleasepool
{
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_async(callerQueue, ^{
#autoreleasepool{
[self setImage:saveImg];
}
});
}
}
else
{
[self showActivityIndicator];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
__block NSError* error = nil;
dispatch_async(downloadQueue, ^{
#autoreleasepool
{
NSData * imageData = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
if (!error)
{
dispatch_async(callerQueue, ^{
#autoreleasepool {
UIImage *image = [UIImage imageWithData:imageData];
[self setImage:image];
[self hideActivityIndicator];
[self saveImageWithFolderName:IMAGES_FOLDER_NAME AndFileName:urlString AndImage:imageData];
}
});
}
}
});
dispatch_release(downloadQueue);
}
}
}
- (void) showActivityIndicator
{
self.activityView = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
self.activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
self.activityView.hidesWhenStopped = TRUE;
self.activityView.backgroundColor = [UIColor clearColor];
self.activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self addSubview:self.activityView];
[self.activityView startAnimating];
}
- (void) hideActivityIndicator
{
CAAnimation *animation = [NSClassFromString(#"CATransition") animation];
[animation setValue:#"kCATransitionFade" forKey:#"type"];
animation.duration = 0.4;;
[self.layer addAnimation:animation forKey:nil];
[self.activityView stopAnimating];
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
}
- (void)saveImageWithFolderName:(NSString *)folderName AndFileName:(NSString *)fileName AndImage:(NSData *) imageData
{
#autoreleasepool{
NSFileManager *fileManger = [[NSFileManager defaultManager] autorelease];
NSString *directoryPath = [[NSString stringWithFormat:#"%#/%#",[DImageView applicationDocumentsDirectory],folderName] autorelease];
if (![fileManger fileExistsAtPath:directoryPath])
{
NSError *error = nil;
[fileManger createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
}
fileName = [DImageView fileNameValidate:fileName];
NSString *filePath = [[NSString stringWithFormat:#"%#/%#",directoryPath,fileName] autorelease];
BOOL isSaved = [imageData writeToFile:filePath atomically:YES];
if (!isSaved)DLog(#" ** Img Not Saved");
}
}
+ (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
+ (UIImage *)getSavedImage :(NSString *)fileName
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
fileName = [DImageView fileNameValidate:fileName];
NSFileManager * fileManger = [[NSFileManager defaultManager] autorelease];
NSString * directoryPath = [[NSString stringWithFormat:#"%#/%#",[DImageView applicationDocumentsDirectory],IMAGES_FOLDER_NAME] autorelease];
NSString * filePath = [[NSString stringWithFormat:#"%#/%#",directoryPath,fileName] autorelease];
if ([fileManger fileExistsAtPath:directoryPath])
{
UIImage *image = [[[UIImage imageWithContentsOfFile:filePath] retain]autorelease];
if (image)
return image;
else
return nil;
}
[pool release];
return nil;
}
+ (NSString*) fileNameValidate : (NSString*) name
{
name = [name stringByReplacingOccurrencesOfString:#"://" withString:#"##"];
name = [name stringByReplacingOccurrencesOfString:#"/" withString:#"#"];
name = [name stringByReplacingOccurrencesOfString:#"%20" withString:#""];
return name;
}
#end
Everything is working fine with smooth scrolling as well as asyncImage download in background.
The issue is when i scroll UITableview application memory is continuously increase and after some time i got Receive memory waring 2/3 time then application crash.
When i use AsyncImageView class that time memory not increase and its working fine. But due to app requirement i saved all images to Document Directory and display from it if its available.
i have tried with #autoreleasepool and release some variable but not getting success.
I appreciated if any one have the solution to manage memory management.
**ARC is off in my application.**
It's possible that UIImagePNGRepresentation returns non-autoreleased object - you can try to release it and see if that results in a crash. Obviously you are not releasing something, but nothing other than the image representation appears obvious.
A few other comments:
run your app in Instruments, using the ObjectAlloc tool, and it should be immediately obvious what objects are not dealloced. If you don't know Instruments, well, its time now to learn it.
you can 'track' objects and get a message when they are dealloced using ObjectTracker - however it was designed for ARC so you may need to tweak it. If you use it you would see a message when each of your objects are dealloced
when the table view is done with a cell, there is a delegate method that you can receive that tells you so, and you can then nil (release) and objects the cell retains
your use of downloadQueue is bizarre - create it once in your instance as an ivar, use it as you need, and in dealloc release it
you hide the activity spinner on the main queue, but don't start it on the main queue
you command the activity view to remove itself from its superview, but then look for in in the subviews and try to remove it there:
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
In the end, Instruments is what you want. You can read up more about it here, or just google and you will surely find a slew of blogs to read.
Yes Finally i have resolved it.
The code which is in Question is working fine now. but Without release some objects and #autoreleasepool block which is in code, memory was increase continuously during scroll UITableView.
From the Instrument i found that memory increase in UILableView and UIImageView. I am using Custom UITableViewCell and in that file i havnt implement dealloc method. So When i have implement dealloc method in UITableViewCell .m file and release & nil all object.
After that memory not increase during scroll TableView and its Resolved the issue.
As per my Understanding there is an issue in your "getSavedImage" Method you have to manage memory Manually instead of 'autorelease' so as My suggestion is use
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath]
and also release it after use of it. means after '[self setImage:saveImg];'
[saveImg release]
instead of this.
[[UIImage imageWithContentsOfFile:filePath] retain];
'Don't Use Autorelease because it has staying in memory until pool not drain' and just because of this you got an memory issue.
I am filling a TableView from a text file. I want to enable the user to download an updated text file and replace the existing content of the TableView with the content of the downloaded file. I am able to download the file and replace the original file. If I close the application and open it again, it loads the updated file.
But the TableView doesn't change while the app is running. When I execute the method to load data from the file into the TableView, I can see, using NSLog, that the method is getting the original data from the file.
What am I doing incorrectly? How can I get the method to see the updated text file instead of the original text file?
Thanks.
#interface
#property (strong, nonatomic) NSArray *tableViewData;
#end
#implementation
/*
When user presses button, IBAction method
- downloads text file
- saves the downloaded file, replacing the original text file
- loads the text file into the TableView data (this is what doesn't work)
- sends a reload message to the TableView
*/
- (IBAction)buttonUpdateTextFile:(UIBarButtonItem *)sender
{
NSString *contentsOfTextFile = [self downloadTextFileFromURL:#"http://www.apple.com/index.html"];
[self saveContentsOfTextFile:contentsOfTextFile toFile:#"tableViewData.txt"];
[self loadDataFromFileWithFileName:#"tableViewData" fileExtension:#"txt"];
[self.tableView reloadData];
}
- (NSString *)downloadTextFileFromURL:(NSString *)textFileURLstring
{
NSURL *textFileURL = [NSURL URLWithString:textFileURLstring];
NSError *error = nil;
NSString *contentsOfTextFile = [NSString stringWithContentsOfURL:textFileURL encoding:NSUTF8StringEncoding error:&error];
return contentsOfTextFile;
}
- (void)saveContentsOfTextFile:(NSString *)contentsOfTextFile toFile:(NSString *)fileName
{
NSString *pathName = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *fileNameWithPath = [pathName stringByAppendingPathComponent:fileName];
if (![[NSFileManager defaultManager] fileExistsAtPath:fileNameWithPath]) {
[[NSFileManager defaultManager] createFileAtPath:fileNameWithPath contents:nil attributes:nil];
[[contentsOfTextFile dataUsingEncoding:NSUTF8StringEncoding] writeToFile:fileNameWithPath atomically:NO];
}
- (void)loadDataFromFileWithFileName:(NSString *)fileName fileExtension:(NSString *)fileExtension
{
NSString *path = [[NSBundle mainBundle] pathForResource:fileName
ofType:fileExtension];
NSString *content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:NULL];
NSString *remainingText = [content mutableCopy];
NSMutableArray *data = [[NSMutableArray alloc] init];
NSRange *substringRange;
while (![remainingText isEqualToString:#""]) {
substringRange = [remainingText rangeOfString:#"/n"];
if (substringRange.location == NSNotFound)
{
currentLine = remainingText;
remainingText = #"";
} else {
substringRange.length = substringRange.location;
substringRange.location = 0;
currentLine = [[remainingText substringWithRange:substringRange] mutableCopy];
// - strip line from remainingText
substringRange.location = substringRange.length + 1;
substringRange.length = remainingText.length - substringRange.length - 1;
remainingText = [[remainingText substringWithRange:substringRange] mutableCopy];
}
[data addObject:currentLine];
}
self.tableViewData = [data copy];
}
I think
self.tableViewData = [data copy];
may be the problem.
I would make data a "private" property of the class. Only init once and then manually add and remove objects to it. Don't use copy.
I am trying to download a pdf file from a server to the device. Here is the code that I am using
- (id)initwithURL:(NSString*)remoteFileLocation andFileName:(NSString*)fileName{
//Get path to the documents folder
NSString *resourcePathDoc = [[NSString alloc] initWithString:[[[[NSBundle mainBundle]resourcePath]stringByDeletingLastPathComponent]stringByAppendingString:#"/Documents/"]];
localFilePath = [resourcePathDoc stringByAppendingString:fileName];
BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPath:localFilePath];
if (fileExists == NO) {
NSURL *url = [NSURL URLWithString:remoteFileLocation];
NSData *data = [[NSData alloc] initWithContentsOfURL: url];
//Write the data to the local file
[data writeToFile:localFilePath atomically:YES];
}
return self;
}
where remoteFileLocation is a NSString and has the value http://topoly.com/optimus/irsocial/Abs/Documents/2009-annual-report.pdf
On running the app crashes, just on NSData giving a SIGABRT error. The only useful information it gives is
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSURL length]: unrecognized selector sent to instance 0xc87b600'
How can this be fixed ?
As your PDF file is too large in size so if you do Synchronous Download, it will take too Long to download, so i insist you to create an Asynchronous Downloader and Use it. I have put code for the same.
Step 1 :Create a file 'FileDownloader.h'
#define FUNCTION_NAME NSLog(#"%s",__FUNCTION__)
#import <Foundation/Foundation.h>
#protocol fileDownloaderDelegate <NSObject>
#optional
- (void)downloadProgres:(NSNumber*)percent forObject:(id)object;
#required
- (void)downloadingStarted;
- (void)downloadingFinishedFor:(NSURL *)url andData:(NSData *)data;
- (void)downloadingFailed:(NSURL *)url;
#end
#interface FileDownloader : NSObject
{
#private
NSMutableURLRequest *_request;
NSMutableData *downloadedData;
NSURL *fileUrl;
id <fileDownloaderDelegate> delegate;
double totalFileSize;
}
#property (nonatomic, strong) NSMutableURLRequest *_request;
#property (nonatomic, strong) NSMutableData *downloadedData;
#property (nonatomic, strong) NSURL *fileUrl;
#property (nonatomic, strong) id <fileDownloaderDelegate> delegate;
- (void)downloadFromURL:(NSString *)urlString;
#end
Step 2 : Create a .m file with FileDownloader.m
#import "FileDownloader.h"
#implementation FileDownloader
#synthesize _request, downloadedData, fileUrl;
#synthesize delegate;
- (void)downloadFromURL:(NSString *)urlString
{
[self setFileUrl:[NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
self._request = [NSMutableURLRequest requestWithURL:self.fileUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0f];
NSURLConnection *cn = [NSURLConnection connectionWithRequest:self._request delegate:self];
[cn start];
}
#pragma mark - NSURLConnection Delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if([delegate respondsToSelector:#selector(downloadingStarted)])
{
[delegate performSelector:#selector(downloadingStarted)];
}
totalFileSize = [response expectedContentLength];
downloadedData = [NSMutableData dataWithCapacity:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[downloadedData appendData:data];
if([delegate respondsToSelector:#selector(downloadProgres:forObject:)])
{
[delegate performSelector:#selector(downloadProgres:forObject:) withObject:[NSNumber numberWithFloat:([downloadedData length]/totalFileSize)] withObject:self];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if([delegate respondsToSelector:#selector(downloadingFailed:)])
{
[delegate performSelector:#selector(downloadingFailed:) withObject:self.fileUrl];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if([delegate respondsToSelector:#selector(downloadingFinishedFor:andData:)])
{
[delegate performSelector:#selector(downloadingFinishedFor:andData:) withObject:self.fileUrl withObject:self.downloadedData];
}
}
#end
Step 3 : Import file #import "FileDownloader.h" and fileDownloaderDelegate in your viewController
Step 4: Define following Delegate methods in .m file of your viewCOntroller
- (void)downloadingStarted;
- (void)downloadingFinishedFor:(NSURL *)url andData:(NSData *)data;
- (void)downloadingFailed:(NSURL *)url;
Step 5 : Create Object of FileDownloader and set URL to Download thats it.
FileDownloader *objDownloader = [[FileDownloader alloc] init];
[objDownloader setDelegate:self];
[objDownloader downloadFromURL:#"Your PDF Path URL here];
Step 6 : Save your file where you want in
- (void)downloadingFinishedFor:(NSURL *)url andData:(NSData *)data; method.
It appears that your remoteFileLocation parameter value is really an NSURL object and not an NSString. Double check how you get/create remoteFileLocation and verify it really is an NSString.
There are also several other issues with this code. The proper way to create a path to the Documents directory is as follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths[0];
NSString *localFilePath = [resourcePathDoc stringByAppendingPathComponent:fileName];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:localFilePath];
if (!fileExists) {
NSURL *url = [NSURL URLWithString:remoteFileLocation];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
//Write the data to the local file
[data writeToFile:localFilePath atomically:YES];
}
GCD can be used for massive files. You can download the file synchronous on a second thread and post back to the main thread if you like. You can also use Operation queues.
You can indeed also use the delegate method from NSURLConnection allowing you to handle the callbacks on the main thread. It is however obsolete to define your own delegate since you can just implement the delegate from NSURLConnection itself.
I can't make my table view show my data, the array has valid data by the NSLog output. I put a breakpoint at the beginning of tableView:cellForRowAtIndexPath: and it never get there. Any ideas why?
#import "ViewController.h"
#import "Ride.h"
#interface ViewController ()
#property (nonatomic, strong) NSMutableData *responseData;
#end
#implementation ViewController
#synthesize rideIds = _rideIds;
#synthesize rideNames = _rideNames;
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"viewdidload");
self.responseData = [NSMutableData data];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// http://www.strava.com/api/v1/segments/229781/efforts?best=true
// Efforts on segment by athlete limited by startDate and endDate
//NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.strava.com/api/v1/segments/229781/efforts?athleteId=11673&startDate=2012-02-01&endDate=2012-02-28"]];
//Leader Board on Segment all Athletes
//NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.strava.com/api/v1/segments/229781/efforts?best=true"]];
//Rides by Athlete
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.strava.com/api/v1/rides?athleteId=10273"]];
//Twitter Example
//NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://api.twitter.com/1/trends"]];
//Efforts by Ride
//NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.strava.com/api/v1/rides/77563/efforts"]];
//Effort Detail
//NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.strava.com/api/v1/efforts/688432"]];
//Google API Call
//NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"https://maps.googleapis.com/maps/api/place/search/json?location=-33.8670522,151.1957362&radius=500&types=food&name=harbour&sensor=false&key=AIzaSyAbgGH36jnyow0MbJNP4g6INkMXqgKFfHk"]];
/* dispatch_async(dispatch_get_main_queue(),^ {
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
} ); */
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if(theConnection){
self.rideIds = [[NSMutableArray alloc]init];
self.rideNames = [[NSMutableArray alloc] init];
} else {
NSLog(#"No Connection");
}
}
//Delegate methods for the NSURLConnection
//In order to download the contents of a URL, an application needs to provide a delegate object that, at a minimum, implements the following delegate methods: connection:didReceiveResponse:, connection:didReceiveData:, connection:didFailWithError: and connectionDidFinishLoading:.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"didReceiveResponse");
//This message can be sent due to server redirects, or in rare cases multi-part MIME documents.
//Each time the delegate receives the connection:didReceiveResponse: message, it should reset any progress indication and discard all previously received data.
[self.responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"didFailWithError");
NSString *errorDescription = [error description];
// NSLog([NSString stringWithFormat:#"Connection failed: %#", errorDescription]);
NSLog(#"Connection failed: %#", errorDescription);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"connectionDidFinishLoading");
NSLog(#"Succeeded! Received %d bytes of data",[self.responseData length]);
// convert to JSON
NSError *myError = nil;
//NSDictionary *jsonRes = [NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:&myError];
NSDictionary *jsonResult = [NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:&myError];
NSDictionary *jsonRides =[jsonResult objectForKey:#"rides"];
// Show all values coming out of "rides" key
// Store ride id's and names on arrays for later display on tableview
for (NSDictionary *rides in jsonRides) {
[self.rideIds addObject:[rides objectForKey:#"id"]];
NSLog(#"id = %#", [rides objectForKey:#"id"]);
//NSLog(#"%#",self.rideIds);
[self.rideNames addObject:[rides objectForKey:#"name"]];
NSLog(#"name = %#", [rides objectForKey:#"name"]);
//NSLog(#"%#",self.rideNames);
}
NSLog(#"%#",self.rideIds);
NSLog(#"%#",self.rideNames);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// Show all values coming out of NSKSONSerialization
for(id key in jsonResult) {
id value = [jsonResult objectForKey:key];
NSString *keyAsString = (NSString *)key;
NSString *valueAsString = (NSString *)value;
NSLog(#"key: %#", keyAsString);
NSLog(#"value: %#", valueAsString);
}
// extract specific value...
// NSArray *results = [res objectForKey:#"results"];
/*NSArray *results = [res objectForKey:#"rides"];
for (NSDictionary *result in results) {
NSData *athleteData = [result objectForKey:#"name"];
NSLog(#"Ride name: %#", athleteData);
}*/
/* dispatch_async(dispatch_get_main_queue(),^ {
[self.rideTableView reloadData];
} ); */
[self.rideTableView reloadData];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"tableView:numberOfRowsInSection: ");
//return self.rideIds.count;
NSLog(#"%u",self.rideNames.count);
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tableView:cellForRowAtIndexPath: ");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if( nil == cell ) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text= [self.rideNames objectAtIndex:indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tv
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tv deselectRowAtIndexPath:indexPath animated:YES];
}
#end
NSLog content:
2012-08-18 18:47:29.497 WebServiceCall[10387:c07] viewdidload
2012-08-18 18:47:29.503 WebServiceCall[10387:c07] tableView:numberOfRowsInSection:
2012-08-18 18:47:29.503 WebServiceCall[10387:c07] 0
2012-08-18 18:47:29.504 WebServiceCall[10387:c07] tableView:cellForRowAtIndexPath:
2012-08-18 18:47:29.506 WebServiceCall[10387:c07] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
*** First throw call stack:
(0x14b6022 0xeb6cd6 0x14a2d88 0x395d 0xb3c54 0xb43ce 0x9fcbd 0xae6f1 0x57d42 0x14b7e42 0x1d87679 0x1d91579 0x1d164f7 0x1d183f6 0x1da5160 0x17e84 0x18767 0x27183 0x27c38 0x1b634 0x13a0ef5 0x148a195 0x13eeff2 0x13ed8da 0x13ecd84 0x13ecc9b 0x17c65 0x19626 0x22fd 0x2265 0x1)
terminate called throwing an exception(lldb)
UPDATE: It seems that after passing through viewDidLoad it jumps right into tableview:numberOfRowsInSection method skipping all the 4 methods for handling NSURLConnection (where I updated my arrays).
My view controller is both delegate of my NSURLConnection AND my tableView. It seems that it's running first the tableView methods. Any suggestions as to how to make it run the NSURLConnection methods first ?
Two things you could try -- First, log self.rideIds.count in your numberOfRowsInSection method to make sure it's not returning 0. Second, at the end of your connectionDidFinishLoading method, put in a [tableView reloadData] (or whatever the outlet to your table view is), that should take care of the problem of the table view methods being called before your connection is done.
After Edit: The error "-[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array" is being caused by the "return 3" in your numberOfRowsInSection method. When the app starts up the table view will try to populate itself before your connection returns any results, so numberOfRowsInSection should return 0 not 3 the first time through, which it will do if you put back the return self.rideIds.count line. If you do the reloadData at the end of the connection delegate methods, then the array will be populated and the table view should work properly.
Where is tableView:numberOfSectionsInTableView:? Perhaps that is returning 0 although the default is 1 if not set; You also need to set delegate and dataSource on your tableView.