imgBiteSpot.clipsToBounds=YES;
NSData *imageData = [[[NSData alloc]init]autorelease];
imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ObjBitSpot.StrImagePath]];
if(imageData==0)
{
imgBiteSpot.image=[UIImage imageNamed:#"img-not-found.gif"];
}
else {
UIImage *imgs = [[UIImage alloc] initWithData:imageData];
UIGraphicsBeginImageContext(CGSizeMake(88,88));
[imgs drawInRect:CGRectMake(0.0, 0.0, 88.0, 88.0)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
imgBiteSpot.image=newImage;
[imgs release];
}
I have loaded an image to imgBiteSpot.
The image is loaded from the webservice. The problem is that it takes too much time (35 seconds to 1 minute).
If I remove image -> code / load, the reaction time is just 0.2 seconds.
What is the solution to reduce the time for the image loading process?
Thanks in advance for your kind help.
You're using NSData's method dataWithContentsOfURL:, which loads the data synchronously from the server, and thus blocks on the thread until the image is received.
What you should do instead is load a blank or "loading" image into your UIImage, then use NSURLConnection to download the image asynchronously and display it when it's done. See the NSURLConnection reference and the URL Loading System doc.
I've written RemoteImage class to load images over the network asynchronously. It also takes care of freeing the memory when necessary. See this post: http://www.dimzzy.com/blog/2009/11/remote-image-for-iphone/
Related
Here is the thing:
I have a scroll view, it did the lazy load for full screen image of user's photo:
[self.assetsLibrary assetForURL:[NSURL URLWithString:[[self.assets objectAtIndex:index] objectForKey:#"asset_url"]]
resultBlock:^(ALAsset *asset) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGImageRef cgImage = asset.defaultRepresentation.fullScreenImage;
UIImage *image = [UIImage imageWithCGImage:cgImage];
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
});
});
}
failureBlock:^(NSError *error) {
NSLog(#"error");
}];
I know it is expensive to load full screen image, so I put it in to the background thread, but it is still lag when I do the scroll. And still lag even I change it like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGImageRef cgImage = asset.defaultRepresentation.fullScreenImage;
UIImage *image = [UIImage imageWithCGImage:cgImage];
imageView.image = image;
dispatch_async(dispatch_get_main_queue(), ^{
});
});
Obviously, nothing to do in the main queue, but it still lag until I comment the line:
// CGImageRef cgImage = asset.defaultRepresentation.fullScreenImage;
So I am so confused, is there something wrong when I used GCD?
Somebody can help me to explain it? Any thing will be helpful.
Thank you, guys.
UPDATE
To #Fogmeister : The size of the photo is the full screen size, actuel imageView size is around half. Even I comment the line: "imageView.image = image;" it is still lag. Which means it is not from the resizing. I know where the time is being taken, here: "asset.defaultRepresentation.fullScreenImage;". When I comment it, everything fine, there is no more lag.
So, which I don't understand is, I've already put it in the background thread...
Ok, finally I solved problem:
Instead of getting image directly by
asset.defaultRepresentation.fullScreenImage
I use the method from Apple's Exemple PhotosByLocation (code below) to get the image in BG thread. That works great, no more lag when scroll. But I am still confused, I don't know exactly why. So I appreciate if someone can explain it to me.
- (UIImage *)fullSizeImageForAssetRepresentation:(ALAssetRepresentation *)assetRepresentation {
UIImage *result = nil;
NSData *data = nil;
uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*[assetRepresentation size]);
if (buffer != NULL) {
NSError *error = nil;
NSUInteger bytesRead = [assetRepresentation getBytes:buffer fromOffset:0 length:[assetRepresentation size] error:&error];
data = [NSData dataWithBytes:buffer length:bytesRead];
free(buffer);
}
if ([data length]) {
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceShouldAllowFloat];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailFromImageAlways];
[options setObject:(id)[NSNumber numberWithFloat:640.0f] forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
//[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailWithTransform];
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
if (imageRef) {
result = [UIImage imageWithCGImage:imageRef scale:[assetRepresentation scale] orientation:(UIImageOrientation)[assetRepresentation orientation]];
CGImageRelease(imageRef);
}
if (sourceRef) CFRelease(sourceRef);
}
return result;
}
You're solution taken from Apple's PhotosByLocation is actually grabbing the biggest resolution image, not the fullscreen image. IOW, it's essentially the same as calling fullResolutionImage instead of fullScreenImage. How that fixes your problem, I'm not sure. I'm struggling with the same performance issue. If I use fullScreenImage, I get lags in my scrolling. But switching to fullResolutionImage gets rid of the lags. fullResolutionImage takes about twice as long as fullScreenImage, but since this is always in the background, it shouldn't really matter how much time it takes. I suspect that fullScreenImage is returning an image that needs some sort of additional processing once it gets rendered to the screen in the main thread - hence the lag.
Do you know the actual size of the photo? What is very expensive is scrolling images that are being resized to fit the screen.
Seeing as you're already loading in a BG thread it might be worth resizing the image to the size you are displaying it at before sticking it on the screen.
You can see where the time is being taken by using the CoreAnimation tool in Instruments by profiling the app from Xcode. It will even tell you which line of code is causing the slow down and missed animation frames.
From the apple documentation:
DISPATCH_QUEUE_PRIORITY_DEFAULT
Items dispatched to the queue run at the default priority; the queue is scheduled for execution after all high priority queues have been scheduled, but before any low priority queues have been scheduled.DISPATCH_QUEUE_PRIORITY_BACKGROUNDItems dispatched to the queue run at background priority; the queue is scheduled for execution after all high priority queues have been scheduled and the system runs items on a thread whose priority is set for background status. Such a thread has the lowest priority and any disk I/O is throttled to minimize the impact on the system.
You're running it in a separate thread, but that's not necessarily a thread "in the background." A background thread loading something in my experience will be completely blocked by doing a UI update such as scrolling a UIScrollView. Have you tried using DISPATCH_QUEUE_PRIORITY_BACKGROUND?
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 iPhone app, I am using the iPhone's camera to take a photo and save it do disk (the application's documents folder). This is how i save it:
[UIImageJPEGRepresentation(photoTaken, 0.0) writeToFile:jpegPath atomically:YES];
Using the most compression, I figured reading the image from disk would be quick. But its not! I use the image as the background image for a button in one of my views. I load it like this:
[self.frontButton setBackgroundImage:[UIImage imageWithContentsOfFile:frontPath] forState:UIControlStateNormal];
When I navigate to the view with this button, it is slow and choppy. How do I fix this?
+imageWithContentsOfFile: is synchronous, so the UI on your main thread is being blocked by the image loading from disk operation and causing the choppiness. The solution is to use a method that loads the file asynchronously from disk. You could also do this in a background thread. This can be done easily by wrapping the +imageWithContentsOfFile: in dispatch_async(), then a nested dispatch_async() on the main queue that wraps -setBackgroundImage: since UIKit methods need to be run on the main thread. If you want the image to appear immediately after the view loads, you'll need to pre-cache the image from disk so it's in-memory immediately when the view appears.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
UIImage *image = [UIImage imageWithContentsOfFile:frontPath];
dispatch_async(dispatch_get_main_queue(), ^{
[self.frontButton setBackgroundImage:image forState:UIControlStateNormal];
});
});
As an aside, if the button image happens a gradient, consider using the following properties to ensure the image file loaded from disk is tiny:
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets
or (deprecated, only use if you need to support iOS 4.x):
- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight
This is the faster way I know. You'll need to import #import <ImageIO/ImageIO.h>
I use this code to download and compress images during a scroll, inside a scrollview and you barely notice the delay.
CGImageSourceRef src = CGImageSourceCreateWithData((CFDataRef)mutableData, NULL);
CFDictionaryRef options = (CFDictionaryRef)[[NSDictionary alloc] initWithObjectsAndKeys:(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform, (id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageIfAbsent, (id)[NSNumber numberWithDouble:200.0], (id)kCGImageSourceThumbnailMaxPixelSize, nil];
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
UIImage *image = [[UIImage alloc] initWithCGImage:thumbnail];
// Cache
NSString *fileName = #"fileName.jpg";
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:#"thumbnail"];
path = [path stringByAppendingPathComponent:fileName];
if ([UIImagePNGRepresentation(image) writeToFile:path atomically:YES]) {
// Success
}
I face a very similar issue where I had to load hundreds of images from the directory. My performance was quite slow if I used UIImage(contentsOfFile:) method. The below method increased my performance to 70 %.
class ImageThumbnailGenerator: ThumbnailGenerator {
private let url: URL
init(url: URL) {
self.url = url
}
func generate(size: CGSize) -> UIImage? {
guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil) else {
return nil
}
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: Double(max(size.width, size.height) * UIScreen.main.scale),
kCGImageSourceCreateThumbnailFromImageIfAbsent: true
]
return CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as NSDictionary).flatMap { UIImage(cgImage: $0) }
}
}
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).