I got a custom UITableViewcell which should load an Image when use that code:
- (void)setImageWithURL:(NSURL *)URL
{
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(taskQ, ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
imageView.image = image;
NSLog(#"test");
});
}
It loads the Image very fast, so I get the response "test" very fast, but it needs to load IN the tableView. Also if I select the row it loads the Image.
How can I fix this issue?
Thanks
As long as setImageWithURL: is called as an effect of cellForRowAtIndexPath, then it should should show up as you expect... however, you need to call imageView.image = image; on the main thread. This goes for ALL UI related tasks. So you'll want:
- (void)setImageWithURL:(NSURL *)URL
{
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(taskQ, ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
NSLog(#"test");
});
});
}
EDIT:
Using NSOperationQueue.
Create an operation queue as a member of your view controller.
m_operationQueue = [NSOperationQueue new];
m_operationQueue.maxConcurrentOperationCount = 1 // if you want to load images in order;
Make sure you cancel operations when the view unloads and when the table will refresh using
[m_operationQueue cancelAllOperations];
Do your thing in setImageWithURL
- (void)setImageWithURL:(NSURL *)URL
{
[m_operationQueue addOperationWithBlock:^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
NSLog(#"test");
});
}];
}
EDIT again:
Sorry, forgot that setImage was in your custom table cell. You can either:
1) make it a static object that you initialize in the +(void)initialize; method of your cell
2) make it a static object available via class method on your view controller
2) make a singleton instance that is available to your table cell
The design preference is up to you and depends what other practices you've been using if you want to be consistent.
You should call [tableView reloadData] when you finish downloading the image.
There is a similar question with an accepted answer, but also it has a suggestion to use SDWebImage. Take a look: How to get UITableViewCell images to update to downloaded images without having to scroll UITableView
Related
Sorry for question title. I can not find a suitable title.
I have UITableView content images from url when i open the UITableView the View did not show until the images loaded and that takes along time.
I get the images from JSON by php.
I want to show the table and then images loading process.
This is code from my app:
NSDictionary *info = [json objectAtIndex:indexPath.row];
cell.lbl.text = [info objectForKey:#"title"];
NSString *imageUrl = [info objectForKey:#"image"];
cell.img.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]];
[cell.img.layer setBorderColor: [[UIColor blackColor] CGColor]];
[cell.img.layer setBorderWidth: 1.0];
return cell;
Sorry my english is weak.
Perform the web request on a separate thread, to not block the UI. Here is an example using NSOperation. Remember to only update the UI on the main thread, as shown with performSelectorOnMainThread:.
- (void)loadImage:(NSURL *)imageURL
{
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(requestRemoteImage:)
object:imageURL];
[queue addOperation:operation];
}
- (void)requestRemoteImage:(NSURL *)imageURL
{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:imageURL];
UIImage *image = [[UIImage alloc] initWithData:imageData];
[self performSelectorOnMainThread:#selector(placeImageInUI:) withObject:image waitUntilDone:YES];
}
- (void)placeImageInUI:(UIImage *)image
{
[_image setImage:image];
}
You have to use NSURLConnection and NSURLRequest. First create and show your empty table view (maybe with placeholder images, that are stored locally in the app). Then you start sending requests. These requests will run in the background and you (the delegate) will be notified when a request is completed. After that you can show the image to the user. Try not to load all the images at once if you have a lot of them. And don't load the ones that are invisible to the user, only load those if he scrolls down.
There is a UITableView lazy image loading example that Apple provided: https://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html
Hopefully it's what you were looking for
This is among very common thing we do in our application.
You simply can have store the URLs in a persistent store e.g array or db & can get the images using Operation queue to download faster. You can set the priorities, cancel operations at anytime etc. Also, the application respond time will be quicker.
NSURL * imageURL = [NSURL URLWithString:imageurldata];
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
image1 = [UIImage imageWithData:imageData];
[image1 retain];
I wrote above code for uploading the image in iPhone, and i m showing a new view in which i am showing this image but image takes time to appear till then the screen is blank. We are taking the image from url and storing it in an object. Is there any to show the image and view at the same time?
Try this async approach:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSLog(#"Screen %# - pauseBannerFileImage download starts", self.name);
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:newUrlForImage]]];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"!-Screen %#-!pauseBannerFileImage downloaded", self.name);
self.imageView.image = image;
});
});
how to handle tiling of images on the fly.
You will only need the TileImageView classes and use it(not UIImageView as it handles data downloading asynchronously) as follows....
TileImageView *tileImageView = [[TileImageView alloc]initWithFrame:<myFrameAsPerMyNeeds>];
[tileImageView setTag:<this is the identifier I use for recognizing the image>];
[myImageScrollView addSubView:tileImageView];
[tileImageView startImageDownloading:imageurldata];
[tileImageView release];
Thanks,
As far as my knowledge, it will take some time to download the data from server.there is one way for covering the time delay is show the UIActivityIndicatorView while downloading the image data
You may want to initialize the view before it is actually needed so the load may have already occurred by the time a user needs a view.
In my view controller, how can I know when a certain UIImageView has finished loading (large jpeg from documents directory)? I need to know so that I can then swap a placeholder low-res imageview with this hi-res imageview. Do I need to create a custom callback to know this? Any way is fine.
By the way, here is a snippet of code where I load the image:
NSString *fileName = [NSString stringWithFormat:#"hires_%i.jpg", currentPage];
NSString *filePath = [NSString stringWithFormat:#"%#/BookImage/%#", [self documentsDirectory], fileName];
hiResImageView.image = [[[UIImage alloc] initWithContentsOfFile:filePath] autorelease];
UIImageView isn't doing any loading at all. All the loading is being done by [[UIImage alloc] initWithContentsOfFile:filePath], and your thread is blocked while the file is loaded (so the load is already complete by the time that call finally returns).
What you want to do is something like this:
- (void)loadImage:(NSString *)filePath {
[self performSelectorInBackground:#selector(loadImageInBackground:) withObject:filePath];
}
- (void)loadImageInBackground:(NSString *)filePath {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];
[self performSelectorOnMainThread:#selector(didLoadImageInBackground:) withObject:image waitUntilDone:YES];
[image release];
[pool release];
}
- (void)didLoadImageInBackground:(UIImage *)image {
self.imageView.image = image;
}
You would set up self.imageView to display the low-res image and then call loadImage: to load the high-res version.
Note that if you call this repeatedly before didLoadImageInBackground: gets called from earlier calls, you may cause the device to run out of memory. Or you might have the image from the first call take so much longer to load than image from the second call that didLoadImageInBackground: gets called for the second image before it gets called for the first. Fixing those issues is left as an exercise for the reader (or for another question).
Any examples available that update UIImageView images dynamically at a high rate? These images are received on a IP connection in a NSThread. But they don't update when I do imageView.image = newImageRecd;
EDIT: All data is recd on main thread
Appreciate any help/pointers.
For iOS 4 and later, you can use Grand Central Dispatch (GCD) to load images on a background thread. Here's a link that explains this process fully:
http://blog.slaunchaman.com/2011/02/28/cocoa-touch-tutorial-using-grand-central-dispatch-for-asynchronous-table-view-cells/
The tutorial above provides this code:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
dispatch_sync(dispatch_get_main_queue(), ^{
[[cell imageView] setImage:image];
[cell setNeedsLayout];
});
});
I've been using this myself to load images from a remote server, and it's working great.
UI updates need to be performed on the main thread. If you are running the connection on a different thread, use:
[self performSelectorOnMainThread:#selector(updateImageView:) withObject:newImageRecd waitUntilDone:FALSE];
where updateImageView: is the method used to set the image view's image property:
-(void)updateImageView:(UIImage *)image
{
[self.imageView setImage:image];
}
In my viewForAnnotation delegate, I create a MKAnnotationView with leftCalloutaccessory as a info button. When the info button is tapped, I want to replace it with a UIActivityIndicator. So in the calloutAccessoryTapped delegate, I wrote
view.leftCalloutaccessoryView = [UIActivityIndicator indicatorWithStyle:UIActivityIndicatorWhite];
The callout accessory seems to change, but it seems like the view doesn't get updated immediately.
That's when the callout accessory gets hidden (by tapping another pin) and is re-opened, I see a spinner. But otherwise, I don't.
I tried calling [view setNeedsdisplay] and [view setNeedsLayout] and [view layoutsubviews] as well but in vain.
Any suggestions/pointers?
I would consider removing and re-adding your annotation. It seems a little heavy handed, but might work.
I was having a similar problem -- I needed to display an image for the leftCalloutAccessoryView if there was one associated with the annotation. Try setting it in mapView:didSelectAnnotationView instead of in mapView:viewForAnnotation
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)annotView
{
...
if(imgURL.length>1){
// set a thread that will load the image
// but don't set the leftCalloutAccessoryView until the image is loaded
dispatch_queue_t downloader = dispatch_queue_create("annotation image downloader",NULL);
dispatch_async(downloader, ^{
NSURL* photoURL = [NSURL URLWithString:imgURL];
NSData* photoData = [NSData dataWithContentsOfURL:photoURL];
UIImage* photoImage = [UIImage imageWithData:photoData];
dispatch_async(dispatch_get_main_queue(), ^{
annotView.leftCalloutAccessoryView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
[(UIImageView *) annotView.leftCalloutAccessoryView setImage:photoImage];
});
});
}
}