Need to capture screen without delay - ios

So guys I'm using this function to take a picture of the UIWebView which loading an e-book and display it while the user render the pages
-(UIImage*)captureScreen:(UIView*) viewToCapture
{
UIGraphicsBeginImageContextWithOptions(viewToCapture.bounds.size, viewToCapture.opaque, 0.0);
[viewToCapture.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage;
}
but the problem there is a delay (0.5 second) happened to get the image .. when i test in the time profiler the instruments point to this line [viewToCapture.layer renderInContext:UIGraphicsGetCurrentContext()]; as the one which causing the delay .. so any advice , suggestion how could I come over this delay.
Thanks in advanced.
Notes :
1.Im using UIPageController for the book effect.
2.to check what I have tried please check this link

The best way of solving this is to use delegates. Meaning that if you tell a delegate method to make the image, it can do it in the background and tell your view "Hey, now I got you an image for XXX" (depending on how you implement it.
This way you can load your view with the e-books in it and just show a loader in the middle of the book with a default background. When the image is done, you update the view for the book with the correct image and remove the loader.
Much like how Apple's iBooks and any other good application does.
Example from one of my own projects (adapted to your need, yet for use in UITableViewController):
BookDelegate.h
#import <Foundation/Foundation.h>
#class Book;
#protocol BookDelegate <NSObject>
#optional
- (void)didRecieveImageForBook:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath;
#end
Book.h
#import <Foundation/Foundation.h>
#import "BookDelegate.h"
#interface Book : NSOperation <NSObject>
{
id <BookDelegate> delegate;
SEL didRecieveImageForBookSelector;
}
#property (strong, nonatomic) id delegate;
#property (assign) SEL didRecieveImageForBookSelector;
- (NSString*)getBookImageForBookId:(int)BookId externalRefference:(NSString*)url indexPath:(NSIndexPath*)indexPath;
- (id)delegate;
// Delegate methods
- (void)didRecieveImageForBook:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath;
#end
Book.m
#import "Book.h"
#import <objc/runtime.h>
#implementation Book
static char kAssociationKey;
#synthesize didRecieveImageForBookSelector;
#synthesize delegate;
- (id)init
{
if (self = [super init])
{
[self setDidRecieveImageForBookSelector:#selector(didRecieveImageForBook:indexPath:)];
}
return self;
}
#pragma mark -
#pragma mark The default delegate functions
- (void)didRecieveImageForBook:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath
{
NSLog(#"********************************************************");
NSLog(#"*** PLEASE IMPLEMENT THE FOLLOWING DELEGATE FUNCTION ***");
NSLog(#"*** didRecieveImageForBook:indexPath: ***");
NSLog(#"********************************************************");
}
#pragma mark -
#pragma mark Function for fechting images
// This method is not adapted to what YOU need, but left my code here in case it might help you out.
- (NSString*)getBookImageForBookId:(int)bookId externalRefference:(NSString*)url indexPath:(NSIndexPath*)indexPath
{
NSString *ext = [[url lastPathComponent] pathExtension];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *imagePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#%d.%#", APP_BookIMAGEPEFIX, BookId, ext]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:imagePath];
if (fileExists)
{
return imagePath;
}
else {
NSURL *theUrl = [NSURL URLWithString:url];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:theUrl];
[request setDidFinishSelector:#selector(BookImageFetched:)];
[request setDidFailSelector:#selector(processFailed:)];
[request setTimeOutSeconds:60];
[request setDownloadDestinationPath:imagePath];
[request setDelegate:self];
[request startAsynchronous];
objc_setAssociatedObject(request, &kAssociationKey, indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return #"";
}
}
- (void)BookImageFetched:(ASIHTTPRequest *)request
{
NSIndexPath *indexPath = objc_getAssociatedObject(request, &kAssociationKey);
NSString *imagePath = request.downloadDestinationPath;
[[self delegate] performSelector:self.didRecieveImageForBookSelector withObject:imagePath withObject:indexPath];
}
#pragma mark -
#pragma mark delegate functions
- (id)delegate
{
return delegate;
}
- (void)setDelegate:(id)newDelegate
{
delegate = newDelegate;
}
#pragma mark -
#end

You could use GCD - I noticed a a step missing from the GCD in the link you gave. This is what I use to asynchronously get an image and notify when its ready and it works fine:
dispatch_queue_t concurrent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(concurrent, ^{
__block UIImage *image = nil;
dispatch_sync(concurrent, ^{
//put code to grab image here
});
dispatch_sync(dispatch_get_main_queue(), ^{
//this gets called when the above is finshed
//you should also check if the image is nil or not
});
});
hope it helps
for the record - I use this for taking UIView snapshots and always try to put my target inside a parentview even if it is temporarily - it seems to speed it up.
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
not sure if it helps or if you are using the same approach. I hope you solve this soon :)

Related

Memory continuously increase then app crash when i display image from document directory during scrolling UITableview

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.

IOS block ui thread until JSON parse function in singleton class finishes

I'm making an IOS application which should gather data on startup from a json feed. Basicaly the initial view controller is a loading screen. In the background, during that loading screen, it should instantiate my singleton class, and call a json feed to fill an array with objects. When this process is finished, it should automatically segue to my main menu.
Here's the weird thing. I've set this up and it works just fine, but only 4 out of 5 times. Sometimes it'll just go straight to the menu and load the feed in the background, which isn't what i want.
Can anyone find the culprit?
Here's my view controller for the loading screen:
#import "loadingViewController.h"
#import "Singleton.h"
#import "MBProgressHUD.h"
#interface loadingViewController ()
#end
#implementation loadingViewController
- (void)viewDidLoad {
[super viewDidLoad];
//[self performSegueWithIdentifier:#"launchSegue" sender:self];
// Do any additional setup after loading the view.
}
- (void)viewWillAppear:(BOOL)animated {
Singleton *singletonManager = [Singleton singletonManager];
[singletonManager loadJsonData];
}
- (void)viewDidAppear:(BOOL)animated {
[self performSegueWithIdentifier:#"launchSegue" sender:self];
}
- (void)doSegue
{
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
And this is my implementation in the singleton class:
implementation Singleton
#synthesize acts;
#pragma mark Singleton Methods
+ (id)singletonManager {
static Singleton *singletonMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singletonMyManager = [[self alloc] init];
});
return singletonMyManager;
}
- (id)init {
if (self = [super init]) {
//someProperty = [[NSString alloc] initWithString:#"Default Property Value"];
self.acts = [[NSMutableArray alloc] init];
}
return self;
}
- (BOOL) loadJsonData
{
NSString *urlString = #"http://wwww.paaspop.nl/json/gettimetable";
NSString *callback = #"api";
NSDictionary *parameters = #{#"callback": callback};
AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy.allowInvalidCertificates = YES;
manager.responseSerializer = [CDLJSONPResponseSerializer serializerWithCallback:callback];
__block NSDictionary* response = nil;
AFHTTPRequestOperation* operation = [manager
GET: urlString
parameters: parameters
success:^(AFHTTPRequestOperation* operation, id responseObject){
response = responseObject;
[self parseJSONData:response];
}
failure:^(AFHTTPRequestOperation* operation, NSError* error){
NSLog(#"Error: %#", error);}
];
[operation waitUntilFinished];
return true;
}
-(void) parseJSONData:(id) JSON
{
NSArray *allActs = [JSON objectForKey:#"acts"];
for (NSDictionary* dict in allActs) {
//Create a pirate object where the json data can be stored
Act *act = [[Act alloc] init];
NSString *url;
//Get the JSON data from the dictionary and store it at the Pirate object
act.title = [dict objectForKey:#"title"];
act.subtitle = [dict objectForKey:#"add"];
act.jsontitle = [dict objectForKey:#"url_title"];
act.favorite = FALSE;
url = [dict objectForKey:#"photo"];
NSURL *imageURL = [NSURL URLWithString:[dict objectForKey:#"photo"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
act.photo = [self imageWithImage:[UIImage imageWithData:imageData] scaledToWidth:320];
NSLog(#"%f, %f",act.photo.size.width,act.photo.size.height);
//Add the pirates to the array
[self.acts addObject:act];
}
}
-(UIImage*)imageWithImage: (UIImage*) sourceImage scaledToWidth: (float) i_width
{
float oldWidth = sourceImage.size.width;
float scaleFactor = i_width / oldWidth;
float newHeight = sourceImage.size.height * scaleFactor;
float newWidth = oldWidth * scaleFactor;
UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight));
[sourceImage drawInRect:CGRectMake(0, 0, newWidth, newHeight)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (NSMutableArray *) getActs {
return self.acts;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
I think you can use Notification Center for this job if you find blocks hard to work with.
You post a notification like this
[[NSNotificationCenter defaultCenter] postNotificationName:#"notificationName" object:nil];
after the for loop in your parseJSONData method.
In your main view controller you should add an observer in viewDidLoad like this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodCalledWhenReady:)
name:#"notificationName"
object:nil];
Afterwards you just create methodCalledWhenReady in your main view controller which receives an NSNotificationObject(which you don't really need in this situation) like this
-(void) methodCalledWhenReady: (NSNotification *) notification
{
//this is where you place the code that you want to execute only after
you finished fetching the data in the singleton class, in your case:
[self performSegueWithIdentifier:#"launchSegue" sender:self];
}
You should check https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/index.html for more information regarding notifications.
Hope this helps.
There is nothing here to prevent it from going right to the new screen. Really it works 1 in 5 times and the other 4 are the oddities.
You have included MBProgressHud in your file but you are not doing anything with that. It would be better to leave the UI active then block the user from doing things and having the app appear un-responsive.
In the download class you are relying on the waitUntilFinished. This will wait for the network operation to complete and then return. This is not waiting for the json parsing and image manipulation to occur.
The AFHTTPRequest library is very outdated. It is a nice body of work but unless you have some very specific lower level needs it is far to involved. NSUrlSession is a newer tool from apple that very powerful and extremely simple to use.
I think that the best approach is to have your - (void)loadJsonData function accept a block that will be executed when the parsing operation is complete. That block can contain the code to perform a segue.

How to unarchive BOOL property with NSKeyedUnarchiver?

I have class for playing sound in app.
I implemented on/off switch(on GUI), for disabling and enablining sound play.
I am using BOOL property for that and this is working.
Now I am trying to implement saving that BOOL (is sound on/off) in file so that next time when app is started state is automatically restored.
For that I am using NSCoding protocol, archiving is working but I have problem with unarchiving.
My app will not start it will just show black screen.
This is my code, only part that I think it is important.
GTN_Sound.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h> // for playing sound
#interface GTN_Sound : NSObject <NSCoding>
#property(nonatomic, readwrite, unsafe_unretained) BOOL isSoundOn;
+ (id)sharedManager;
- (void)playWinSound;
- (void)playLoseSound;
#end
GTN_Sound.m
#pragma mark - NSCoding Methods
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.isSoundOn forKey:#"isSoundOn"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [GTN_Sound sharedManager];
if (self) {
_isSoundOn = [aDecoder decodeBoolForKey:#"isSoundOn"];
}
return self;
}
I think that code is so far so good ?
Continuation of GTN_Sound.m
#pragma mark - itemArchivePath Method
- (NSString *)itemArchivePath
{
// Make sure that the first argument is NSDocumentDirectory
// and not NSDocumentationDirectory
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
// Get the one document directory from that list
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingPathComponent:#"sound.archive"];
}
#pragma mark - custom seter Method
- (void)setIsSoundOn:(BOOL)theBoolean {
NSLog(#"My custom setter\n");
if(_isSoundOn != theBoolean){
_isSoundOn = theBoolean;
NSString *path = [self itemArchivePath];
[NSKeyedArchiver archiveRootObject:self toFile:path]; // this is doing save
}
}
It is done that for every time when switch on GUI is changed I do the savings.
This look fine from my side, because I am not expecting that user will change this many times.
Now the unarchiving comes and I thin that here are some problems.
#pragma mark - Singleton Methods
+ (id)sharedManager {
static GTN_Sound *sharedMyManager = nil;
// to be thread safe
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initPrivate];
});
return sharedMyManager;
}
- (instancetype)init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"Use +[GTN_Sound sharedManager]"
userInfo:nil];
return nil;
}
// Here is the real (secret) initializer
- (instancetype)initPrivate
{
self = [super init];
if (self) {
NSString *path = [self itemArchivePath]; // do as iVar, for futture
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
//_isSoundOn = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
}
return self;
}
I think that problem is in this line
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
But have no idea how to fix it.
I have read that when I am doing archiving, that I need to archive all properties of object.
Does this apply to private ivars also ?
Any help is appreciated.
Thanks
You can only archive NSObject inheriting objects. ie. NSNumber
[NSKeyedArchiver archiveRootObject:#YES toFile:path]; //to save
_isSoundOn = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] boolValue]; //to load

Downloading pdf file

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.

Load "Loading" image before laoding Image from server?

I am new to iOS development so please bear with me.
I am trying to create a basic photo gallery but ran into a problem.
When I started out with the project I just included all the images in my project.
Now after having a lot more images(400+) I started loading them from a server.
I made an array of images using the following line of code:
[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.testsite.com/testPic.png"]]]
Obviously making the user wait for an array of 400+ images to load from a server is unacceptable.
So my question is if I included one image in my project that said something like "Loading", how could I display that image until the actual image loaded from the server?
I'm making a basic grid-style photo gallery using a table-view and scroll-view. It loads up a few rows of small(thumbnail) images and when you click one it makes it full screen.
I'm using Xcode 4.3, ARC, and storyboards if that helps!
Sorry if this is confusing!
-Shredder2794
The simplest way of doing this is to use AFNetworking, which provides setImageWithURL:placeholderImage: in a category on UIImageView (also a method with success/failure handlers).
A method is to subclass UIImageView. When you allocate it you put a default image ( the loading one) or a UIActivityIndicator, download the image you want to display ( in a separate thread) and when the image is downloaded display it. Have a look to NSURLRequest and NSURLConnection for the image download.
* EDIT *
Here is an example of code I developed. You can use this as a start point to develop your own loading image class. This class can be improved by using NSThread for the image loading.
// ImageLoader.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
/**
* #brief Class that loads an UIImage from a server
* #author Nicolas
*/
#interface ImageLoader : UIView
{
#private
NSURLConnection *connection;
NSMutableData *data;
NSString *path;
UIActivityIndicatorView *loading;
UIImageView *imageView;
}
#property (nonatomic, retain) UIActivityIndicatorView *loading;
#property (nonatomic, retain) NSURLConnection *connection;
#property (nonatomic, retain) NSMutableData *data;
#property (nonatomic, retain) NSString *path;
#property (nonatomic, retain) UIImageView *imageView;
/**
* Load an image from a server an display it
* #param URL URL to get the image
* #param chemin path to save the image
* #author Nicolas
*/
- (void)loadImageFromUrl:(NSString *)URL forPath:(NSString *)chemin;
#end
// ImageLoader.m
#import "ImageLoader.h"
#implementation ImageLoader
#synthesize path, connection, data, loading, imageView;
- (id)init
{
self = [super init];
[self setUserInteractionEnabled:YES];
return self;
}
- (void)loadImageFromUrl:(NSString *)URL forPath:(NSString *)chemin
{
//if (connection != nil) [connection release];
//if (data != nil) [data release];
//if (path != nil) [path release];
self.path = chemin;
if ([[NSFileManager defaultManager] fileExistsAtPath:chemin])
{
if (imageView != nil)
{
[imageView removeFromSuperview];
[imageView release];
}
imageView = [[UIImageView alloc] initWithFrame:self.bounds];
imageView.image = [UIImage imageWithContentsOfFile:chemin];
[self addSubview:imageView];
}
else
{
loading = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20.0f, 20.0f)];
loading.center = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
[loading setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];
[loading startAnimating];
[self addSubview:loading];
NSURL *myURL = [NSURL URLWithString:[URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:myURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0f];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
}
#pragma mark -
#pragma mark NSURLConnection protocol
- (void)connection:(NSURLConnection *)_connection didReceiveData:(NSData *)_data
{
if (data == nil)
{
data = [[NSMutableData alloc] initWithCapacity:2048];
}
[data appendData:_data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)_connection
{
[data writeToFile:self.path atomically:YES];
if (imageView != nil)
{
[imageView removeFromSuperview];
[imageView release];
}
imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:data]];
imageView.frame = self.bounds;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
[loading stopAnimating];
[loading setHidden:YES];
[self addSubview:imageView];
}
- (void)connection:(NSURLConnection *)_connection didFailWithError:(NSError *)error
{
[loading stopAnimating];
[loading release];
}
#pragma mark - Memory management
- (void)dealloc
{
[connection cancel];
[connection release];
[imageView release];
[path release];
[data release];
[super dealloc];
}
#end

Resources