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.
Related
In my iPhone app I have to show the preview of the thumbnail image. That preview image actually we will get from remote server. Before loading that big image on screen I have to show preloading view but actually this preloading view is not appearing on the screen.
The code I used is:
zoomview=[[UIView alloc]initWithFrame:CGRectMake(0,0,320,460)];
imageloadview.backgroundColor=[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];
[self.view addSubview:imageloadview];
[activity startAnimating];
[self loadimageview];
Here instead of loading the zoom view on screen this loading view method is executing but I want to display preloading view before getting the big image from the server.
-(void)loadimageview
{
imageloader.image=[UIImage imageNamed:#""];
[self loadimage];
}
-(void)loadimage
{
NSData *data=[NSData dataWithContentsOfURL:[NSURL URLWithString:[picfullarray objectAtIndex:0]]];
if([data length]==0)
{
NSLog(#"data");
}
else
{
UIImage *image1=[UIImage imageWithData:data];
imageloader.image=image1;
[activity stopAnimating];
[loadlabel1 setText:#""];
}
}
How do I show preloaded view on iPhone screen before getting the big image from the server?
You have to load the image asynchronously with NSURLRequest.
Make the class implement NSURLRequestDelegate protocol. In the function - (void)connectionDidFinishLoading:(NSURLConnection *)connection of NSURLRequestDelegate, add the code to update the view when the loading is completed.
// You must declare NSMutableData somewhere to write received data in delegate method
// - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
// I assume the instance of NSMutableData is named data
// If you want to load multiple images, it is a bit tricky, but doable.
// I'll post the code if that is what you need.
- (void) connectionDidFinishLoading: (NSURLConnection *) connection {
// You may want to declare a instance member to store the image (UIImage*), so that you can restore the view if the view is unloaded due to memory warning.
UIImage* image = [UIImage imageWithData: data];
data = nil; // Important. You should clean the data, or it will take up space.
// You may want to check whether image != nil, since the incoming data may not be image
[self displayImage: image];
}
- (void) displayImage: (UIImage*) aImage {
imageloader.image = aImage;
// Depending on how UIImageView is initialized, you may need to call sizeToFit.
// Or set the frame accordingly
[activity stopAnimating];
[loadlabel1 setText: #""];
}
I'd suggest using SDWebImage framework. It already has async image loading capability, and it's super easy to use.
[imageView.image setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
You don't have to worry about messing with NSURLConnection, maintaining NSData, etc..
There is also AFNetworking which has a way to do this easily.
https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking
Check out the "Download and Display Images" section.
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.
I'm using some sample code I got from a tutorial to create basically a snapshot using AVCamRecorder. It doesn't save a picture, it just displays it in a little rect under the live camera view whenever I click a button. It seemed to be allocating more and more memory each time I clicked the button to update the image, so I put an if (image) {[image release]} and then continued with the rest of the code to capture the image. The problem I ran into there is that eventually I hit an EXC_BAD_ACCESS if I click the button fast enough repeatedly. I tried inserting an if (image) immediately before assigning it to my view, but I still get EXC_BAD_ACCESS. I tried adding an [NSThread sleepForTimeInterval:1] at the end, but that didn't help either. I still get the EXC_BAD_ACCESS after clicking the button several times. Is there a proper way to reuse this view? Thanks
if (image) {
[image release];
exifAttachments = nil;
}
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:stillImageConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
exifAttachments = CMGetAttachment(imageDataSamplebuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
// NSLog
} else {
// NSLog
}
NSData *imagedata = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
image = [[UIImage alloc] initWithData:imageData];
if (image) {
self.capturedPicView.image = image;
}
}];`
Is the image variable declared as __block? If not, you may get all sorts of weird things because you can't modify it within a block.
You probably don't need a separate image variable - just do:
self.capturedPicView.image = [[[UIImage alloc] initWithData:imageData] autorelease];
in your block.
P.S. And it looks like your original memory leak was due to not releasing the new image - you could have added autorelease to UIImage creation or just release it right after assigning (UIImageView.image retains it anyway):
image = [[UIImage alloc] initWithData:imageData];
if (image) {
self.capturedPicView.image = image;
[image release];
}
I'm having a problem displaying an image in an image view, which is generated from data.
My program works as follows:
The user selects a row from a UITableView.
Upon selection, a new view (Event Description) is generated and has 3 NSStrings and an NSData object passed to it.
This data is displayed in the view, however a thread is also spawned to convert the NSData into an image and display it as without that there is a slight delay in displaying the description view.
This code works fine on the simulator, however when I run it on my iPhone and select a row, the first selection works fine, then all corresponding selections have a noticeable delay in displaying the image..
Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
//Display the selected events data
name.text = eventName;
date.text = eventDate;
description.text = eventDescription;
//Set the title of the navigation bar
self.navigationItem.title = eventName;
/* Operation Queue init (autorelease) */
NSOperationQueue *queue = [NSOperationQueue new];
/* Create our NSInvocationOperation to call loadDataWithOperation, passing in nil */
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(loadDataWithOperation)
object:nil];
/* Add the operation to the queue */
[queue addOperation:operation];
[operation release];
}
//Threaded operation
- (void) loadDataWithOperation {
//Set the event image
UIImage *img = [UIImage imageWithData: eventImageURLData];
image.image = img;
}
Does anyone have any idea whats causing this?
Thanks,
Jack
Simulator doesn't have memory constraints so it rarely flags the low memory problem. As such you're leaking NSOperationQueues. I see that you've marked it as autorelease but see no autorelease message. You should probably fix that. In addition to this, you should be sending the image update code to the main thread.
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).