So I'm pulling down about 50 images from my API using NSURLConnection, its working great, except its locking up the UI when it runs. I'm assuming that is because I'm updating the UI in real time form the NSURLConnection self delegate. So I'm thinking what I need to do is put placeholder loading images in the UIImage, then update them somehow once the delegate has acquired all the data, but how do I do that, can someone give me some coding examples?
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
NSData *imageData = _dataDictionary[ [connection description] ];
if(imageData!=nil)
{
NSLog(#"%#%#",[connection description],imageData);
UIImageView *imageView = [[UIImageView alloc] initWithFrame: CGRectMake(self.x, 0, self.screenWidth, self.screenHight)];
// Process thi image
// resize the resulting image for this device
UIImage *resizedImage = [self imageScaleCropToSize:[UIImage imageWithData: imageData ]];
self.x = (self.x + imageView.frame.size.width);
if(self.x > self.view.frame.size.width) {
self.scrollView.contentSize = CGSizeMake(self.x, self.scrollView.frame.size.height);
}
[imageView setImage:resizedImage];
// add the image
[self.scrollView addSubview: imageView];
}
}
You can use SDWebImage library to achieve this.
Suppose imageArray have all the image url path.
You can use SDWebImageManager to download all the images and show them in ImageView. Also you can show downloading progress using this block.
- (void)showImages:(NSArray *)imageArray
{
SDWebImageManager *manager = [SDWebImageManager sharedManager];
for (NSString *imagePath in imageArray)
{
[manager downloadImageWithURL:[NSURL URLWithString:imagePath]
options:SDWebImageLowPriority
progress:^(NSInteger receivedSize, NSInteger expectedSize){}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL)
{
if(!error)
self.imgView_Image.image = image;
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"please check your Connection and try again" message:#"No Internet Connection" delegate:nil cancelButtonTitle:#"Cancel" otherButtonTitles: nil];
[alert show];
}
}];
}
}
First create protocol in that class .h, where you call NSURLConnection request for download image (Where you implement this method connectionDidFinishLoading).
#protocol YourClassNameDelegate <NSObject>
- (void)didFinishLoadingImage:(UIImage *)downloadImage;
#end
and create property for that protocol in same class,
#interface YourViewController : UIViewController
#property (nonatomic, retain) id<YourClassNameDelegate>delegate;
#end
then synthesise it in .m, #synthesize delegate;
After that call didFinishLoadingImage: in connectionDidFinishLoading,
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
NSData *imageData = _dataDictionary[ [connection description] ];
if(imageData!=nil)
{
NSLog(#"%#%#",[connection description],imageData);
UIImageView *imageView = [[UIImageView alloc] initWithFrame: CGRectMake(self.x, 0, self.screenWidth, self.screenHight)];
// Process thi image
// resize the resulting image for this device
UIImage *resizedImage = [self imageScaleCropToSize:[UIImage imageWithData: imageData ]];
self.x = (self.x + imageView.frame.size.width);
if(self.x > self.view.frame.size.width) {
self.scrollView.contentSize = CGSizeMake(self.x, self.scrollView.frame.size.height);
}
[self.delegate didFinishLoadingImage:resizedImage];
[imageView setImage:resizedImage];
// add the image
[self.scrollView addSubview: imageView];
}
}
and finally from where you push to YourViewController set delegate to self, like :
YourViewController *controller = [[YourViewController alloc] init];
controller.delegate = self;
//.....
in YourViewController.m, where you want to set downloaded image, in that class implement this method.
#pragma mark - YourClassName delegate method
- (void)didFinishLoadingImage:(UIImage *)downloadImage
{
//yourImageView.image = downloadImage;
}
Related
I'm trying to setup an asynchrone download of images on my application.
I'm using SDWebImage as suggested in this issue.
I put breakpoints on the progress and completed method and everything is normal. It's working perfectly but I have another problem coming from my logic directly. I don't know how to set my image asynchronously on my UIImageView.
Everything is dynamic and each image is called independently
Here is a part of my code:
[myMenu->_userAvatar setImage:[[CacheCache sharedInstance] getUIImageFromPath:currentUser.avatarPhoto.avatarURL]];
Note that CacheCache is my own cache method.
NSURL* myURL=[NSURL URLWithString:path];
//NSData* myData=[NSData dataWithContentsOfURL:myURL];
NSData* myData;
[SDWebImageDownloader.sharedDownloader downloadImageWithURL:myURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
DDLogInfo(#"Downloading...");
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished)
{
if (image && finished)
{
DDLogInfo(#"Downloaded !");
image = [[UIImage alloc] initWithData:myData];
}
}];
...
return image;
Thank you for your help.
Rather than going for that complex solution, you can try this one. Create NSObject file with name 'DownloadImagesAsynchronously' and replace .h file with following.
#import <Foundation/Foundation.h>
#protocol NotifyParentProtocol <NSObject>
-(void)ImageDownloaded:(BOOL)_isDownloaded;
#end
#interface DownloadImagesAsynchronously : NSObject{
NSMutableData *receivedData;
UIImageView *cCellImageView;
NSURLConnection *imgDownloadConnection;
id<NotifyParentProtocol> __weak delegate;
}
-(void) downloadImageAsynchornously:(NSString *)_imageURL andCellImage:(UIImageView *)_cellImgV;
#property(weak) id<NotifyParentProtocol> delegate;
#end
and replace .m with following code
#import "DownloadImagesAsynchronously.h"
#implementation DownloadImagesAsynchronously
#synthesize delegate;
- (void)loadWithURL:(NSURL *)url{
NSURLConnection *conect = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url]delegate:self];
[conect start];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[receivedData setLength:0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
cCellImageView.image = [UIImage imageWithData:receivedData];
[cCellImageView.layer setCornerRadius:14.0f];
[delegate ImageDownloaded:YES];
}
-(void) downloadImageAsynchornously:(NSString *)_imageURL andCellImage:(UIImageView *)_cellImage{
cCellImageView = _cellImage;
receivedData = [[NSMutableData alloc] init];
NSString *baseURL = #"http://example.com/abc/Gallary";
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",baseURL,_imageURL]];
[self loadWithURL:url];
}
#end
Now cal on your UIImageView like this, if using TableView then it will lazily download image to each tableview cell.
DownloadImagesAsynchronously *downloadAsynchImageObj = [[DownloadImagesAsynchronously alloc] init];
downloadAsynchImageObj.delegate = self;
[downloadAsynchImageObj downloadImageAsynchornously:model.ImageName1 andCellImage:self.mainImageView];
and implement delegate method. It will notify you when ever image is being downloaded. You can perform your desired action.
- (void)ImageDownloaded:(BOOL)_isDownloaded{
// Image Downloaded.
}
Hope this will work for you, if you have any question related this. Please let me know. Thanks
Thank you guys.
I mixed both of your answers. I simply passed my UIImageView to my method with asynchrone block.
Here is my code:
//view is an UIImageView coming directly from one of the parameters
if (view == nil) {
myData=[NSData dataWithContentsOfURL:myURL];
image = [[UIImage alloc] initWithData:myData];
}
else{
[SDWebImageDownloader.sharedDownloader downloadImageWithURL:myURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
DDLogInfo(#"Downloading...");
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished)
{
if (image && finished)
{
DDLogInfo(#"Downloaded !");
view.image = image;
}
}];
}
Now I only have a problem about the resize of my picture and its scale but my original issue is fixed.
I hope this will help someone else.
Hi I am saving an image in an NSObject called CaptureManager. in the .h I set the UIImage properties:
#property (nonatomic,strong) UIImage *captureImage;
and then in the .m set the image
- (void) captureStillImage
{
AVCaptureConnection *stillImageConnection = [[self stillImageOutput] connectionWithMediaType:AVMediaTypeVideo];
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:stillImageConnection
completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer != NULL) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
captureImage = [[UIImage alloc] initWithData:imageData];
[[NSNotificationCenter defaultCenter] postNotificationName: #"photoTaken" object:nil userInfo:nil];
}
if ([[self delegate] respondsToSelector:#selector(captureManagerStillImageCaptured:)]) {
[[self delegate] captureManagerStillImageCaptured:self];
}
}];
}
Now in my ViewController I am trying to display that Image like so:
CaptureManager *captureManager = [[CaptureManager alloc] init];
UIImage *captureBackgroundImage = captureManager.captureImage;
UIImageView *captureImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
[captureImageView setImage:captureBackgroundImage];
[self.view addSubview:captureImageView];
[self.view bringSubviewToFront:captureImageView];
Did I do everything correctly? Is the problem where I have the captureImage set? Could it be in the captureStillImage method with the completion handler messing this up? Nothing is getting displayed on the screen. Any help would be really appreciated!
I've been pulling my hair out a bit over this. I'm creating a very simple app, it simply downloads an rss feed and displays it in a UITableview, which is inside a UINavigationController. Whilst it's downloading the feed I'm presenting a Modal View.
In my modal view I'm displaying a UIImageView and a UIActivityIndicatorView that is set to spin. I'm using ASIHTTRequest to asynchronously grab the feed and then using the either the completion block to get the response string and stop the spinner or the failure block to get the NSError and display a alert View. This all works perfectly.
I've then created a protocol to dismiss the modal view from the tableview which is called inside the completion block. But the modal view is never dismissed! I've tried pushing it into the navigation controller but exactly the same problem occurs. I even have tried setting the modal view delegate to nil but still no luck.
I've checked it without blocks using the ASIHTTPRequest delegate methods and it's the same, and if I don't present the modal view the table view is displayed normally.
Any Ideas? I've skipped out all the tableview delegate and datasource methods as well as the dealloc and any unused functions.
#interface MainTableViewController ()
-(void)loadModalView;
#end
#implementation MainTableViewController
#synthesize tableView;
#synthesize modalView;
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
[super loadView];
tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) style:UITableViewStylePlain];
tableView.delegate = self;
tableView.dataSource = self;
[self.view addSubview:tableView];
[self loadModalView];
}
-(void)loadModalView
{
modalView = [[ModalViewController alloc]init];
modalView.delegate = self;
[self presentModalViewController:modalView animated:NO];
}
//Modal View Delegate
-(void)downloadComplete
{
modalView.delegate = nil;
[self dismissModalViewControllerAnimated:NO];
}
#end
#interface ModalViewController ()
- (void)loadView
{
[super loadView];
backgroundImage = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
[self.view addSubview:backgroundImage];
spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.frame = CGRectMake(160, 240, spinner.bounds.size.width, spinner.bounds.size.height);
spinner.hidesWhenStopped = YES;
[self.view addSubview:spinner];
[spinner startAnimating];
NSString* urlString = FEED_URL;
NSURL* url = [NSURL URLWithString:urlString];
ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
[spinner stopAnimating];
[delegate downloadComplete];
// Use when fetching binary data
}];
[request setFailedBlock:^{
NSError *error = [request error];
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:#"Error" message:error.description delegate:self cancelButtonTitle:#"Continute" otherButtonTitles: nil];
[alert show];
[alert release];
}];
[request startAsynchronous];
}
Matt
In my understanding.. you solution is quite complicated..
wouldn't it be better if the class MainTableViewController is the
one who downloads the Feeds.. for the ModalView it will just act as an ActivityIndicator and dismiss after downloading..
so inside your MainTableViewController loadview:
- (void)loadView
{
NSString* urlString = FEED_URL;
NSURL* url = [NSURL URLWithString:urlString];
ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:url];
[request startAsynchronous];
//after starting the request show immediately the modalview
modalView = [[ModalViewController alloc]init];
[self presentModalViewController:modalView animated:NO];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
//then when it is complete dissmiss the modal
[modalView dismissModalViewControllerAnimated:NO];
// Use when fetching binary data
}];
[request setFailedBlock:^{
NSError *error = [request error];
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:#"Error" message:error.description delegate:self cancelButtonTitle:#"Continute" otherButtonTitles: nil];
[alert show];
[alert release];
}];
}
i didnt use blocks in my projects, but i think it will work the same..
also I use a plain UIActivityIndicatorView (large) as subviews not modalViews.. sadly i cant test the code here now.. but i can check it later though
The only way I solved this error was to synchronously download the data and push and pop the download view onto the navigation stack. Not ideal but it works.
I have a problem getting a UIActivityIndicatorView to show when I collect data from a server with help from the NSURLConnection request.
The request I think is asynchronous, i.e., started in a new thread. I have copied from Apple's AdvancedTableViewCells example. And I run it in XCode in the iOS 4.3 iPhone simulator. I have not tested it on a real iPhone yet.
Also I have googled this problem and tried a lot of suggestions but the feeling is that I have forgotten something basic. Below is my code from the class RootViewController.
I just select a row, create and add the activityview, startanimating, and then create the NSUrlConnection object which starts to fetch data from the server in another thread, I believe.
Any ideas?
#interface RootViewController : UITableViewController {
NSMutableData *receivedData;
UIActivityIndicatorView *activityView;
}
#end
...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// In my rootviewcontroller
activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.view addSubview:activityView];
[activityView startAnimating];
…
NSMutableURLRequest *tUrlRequest = [tQuery createUrlRequest:tStatId];
NSURLConnection *tConnectionResponse = [[NSURLConnection alloc] initWithRequest: tUrlRequest delegate: self];
if (!tConnectionResponse) {
NSLog(#"Failed to submit request");
} else {
NSLog(#"Request submitted");
receivedData = [[NSMutableData data] retain];
}
return;
}
...
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
NSXMLParser *tParser = [[NSXMLParser alloc] initWithData: receivedData];
...
[tParser parse];
...
[connection release];
[receivedData release];
[NSThread sleepForTimeInterval: 2.0]; // Just to see if activity view will show up...
NSUInteger row = 1;
if (row != NSNotFound)
{
// Create the view controller and initialize it with the
// next level of data.
VivadataTViewController *vivaViewController = [[VivadataTViewController alloc] init];
if (activityView != nil) {
[activityView stopAnimating];
}
}
}
Had the same exact issue, try to change the color of the UIActivityIndicatorView under Attributes Inspector -> Style to Gray
I'm seeking a tutorial on how to cache images loaded from a url into cells of a uitableview.
I found an example here
http://www.ericd.net/2009/05/iphone-caching-images-in-memory.html#top
But the code is incomplete. I'm an objective c novice so I found it very difficult to fill in the missing pieces.
Here is a simple ImageCache implementation using NSCache. ImageCache is a singelton.
ImageCache.h
#import <Foundation/Foundation.h>
#interface ImageCache : NSObject
#property (nonatomic, retain) NSCache *imgCache;
#pragma mark - Methods
+ (ImageCache*)sharedImageCache;
//- (void) AddImage:(NSString *)imageURL: (UIImage *)image;
- (void) AddImage:(NSString *)imageURL withImage:(UIImage *)image;
- (UIImage*) GetImage:(NSString *)imageURL;
- (BOOL) DoesExist:(NSString *)imageURL;
#end
ImageCache.m
#import "ImageCache.h"
#implementation ImageCache
#synthesize imgCache;
#pragma mark - Methods
static ImageCache* sharedImageCache = nil;
+(ImageCache*)sharedImageCache
{
#synchronized([ImageCache class])
{
if (!sharedImageCache)
sharedImageCache= [[self alloc] init];
return sharedImageCache;
}
return nil;
}
+(id)alloc
{
#synchronized([ImageCache class])
{
NSAssert(sharedImageCache == nil, #"Attempted to allocate a second instance of a singleton.");
sharedImageCache = [super alloc];
return sharedImageCache;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil)
{
imgCache = [[NSCache alloc] init];
}
return self;
}
// - (void) AddImage:(NSString *)imageURL: (UIImage *)image
- (void) AddImage:(NSString *)imageURL withImage:(UIImage *)image
{
[imgCache setObject:image forKey:imageURL];
}
- (NSString*) GetImage:(NSString *)imageURL
{
return [imgCache objectForKey:imageURL];
}
- (BOOL) DoesExist:(NSString *)imageURL
{
if ([imgCache objectForKey:imageURL] == nil)
{
return false;
}
return true;
}
#end
Example
UIImage *image;
// 1. Check the image cache to see if the image already exists. If so, then use it. If not, then download it.
if ([[ImageCache sharedImageCache] DoesExist:imgUrl] == true)
{
image = [[ImageCache sharedImageCache] GetImage:imgUrl];
}
else
{
NSData *imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: imgUrl]];
image = [[UIImage alloc] initWithData:imageData];
// Add the image to the cache
//[[ImageCache sharedImageCache] AddImage:imgUrl :image];
[[ImageCache sharedImageCache] AddImage:imgUrl withImage:image];
}
A nice working example was found here
http://ezekiel.vancouver.wsu.edu/~wayne/yellowjacket/YellowJacket.zip
You could also try using the awesome EgoImage library written by the sharp fellows at enormego to accomplish this. It is very simple to use, makes efficient use of cache behind the scenes and is ideally suited to meet your requirements.
Here's the github path for the library which includes a demo app.
I wrote this (with concepts and some code taken from Lane Roathe's excellent UIImageView+Cache category) for an app I've been working on. It uses the ASIHTTPRequest classes as well, which are great. This could definitely be improved.. for example, by allowing requests to be canceled if no longer needed, or by utilizing the notification userInfo to allow for more precise UI updating.. but it's working well for my purposes.
#implementation ImageFetcher
#define MAX_CACHED_IMAGES 20
static NSMutableDictionary* cache = nil;
+ (void)asyncImageFetch:(UIImage**)anImagePtr withURL:(NSURL*)aUrl {
if(!cache) {
cache = [[NSMutableDictionary dictionaryWithCapacity:MAX_CACHED_IMAGES] retain];
}
UIImage* newImage = [cache objectForKey:aUrl.description];
if(!newImage) { // cache miss - doh!
ASIHTTPRequest *imageRequest = [ASIHTTPRequest requestWithURL:aUrl];
imageRequest.userInfo = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:anImagePtr] forKey:#"imagePtr"];
imageRequest.delegate = self;
[imageRequest setDidFinishSelector:#selector(didReceiveImage:)];
[imageRequest setDidFailSelector:#selector(didNotReceiveImage:)];
[imageRequest startAsynchronous];
}
else { // cache hit - good!
*anImagePtr = [newImage retain];
}
}
+ (void)didReceiveImage:(ASIHTTPRequest *)request {
NSLog(#"Image data received.");
UIImage **anImagePtr = [(NSValue*)[request.userInfo objectForKey:#"imagePtr"] pointerValue];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *newImage = [[UIImage imageWithData:[request responseData]] retain];
if(!newImage) {
NSLog(#"UIImageView: LoadImage Failed");
}
else {
*anImagePtr = newImage;
// check to see if we should flush existing cached items before adding this new item
if( [cache count] >= MAX_CACHED_IMAGES)
[cache removeAllObjects];
[cache setValue:newImage forKey:[request url].description];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName: #"ImageDidLoad" object: self userInfo:request.userInfo];
}
[pool drain];
}
You call this code as follows:
[ImageFetcher asyncImageFetch:&icon withURL:url];
I'm also using notifications, for better or worse, to let any owners of the corresponding UIImage know when they should redisplay- in this case, it's in a tableView context:
- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(imageDidLoad:) name:#"ImageDidLoad" object:nil];
}
- (void)imageDidLoad:(NSNotification*)notif {
NSLog(#"Received icon load notification.");
// reload table view so that new image appears.. would be better if I could
// only reload the particular UIImageView that holds this image, oh well...
[self.tableView reloadData];
}
- (void)dealloc {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
// ...
}
You also might wanna check HJCache. It comes with a UIImageView compatible view class that does all the caching transparently and is suitable to be used in UITableViewCells where scrolling performance is important.