I have a ALAssetRepresentation question. There is a table which displays gallery images in high resolution. The fullScreenImage method takes too much time and the table works slowly. How can I implement a faster loading? For example, gallery images loading occurs immediately in the https://itunes.apple.com/us/app/print-studio-print-photos/id601882801?mt=8, how could they have done this?
ALAsset *result = photos[_index];
UIImageView *photoView = (UIImageView*)[contentView viewWithTag:PHOTO_VIEW];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
ALAssetRepresentation *represintation = [result defaultRepresentation];
NSURL* aURL = represintation.url;
[library assetForURL:aURL resultBlock:^(ALAsset *asset){
UIImage *copyOfOriginalImage = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage] scale:0.3 orientation:UIImageOrientationUp];
copyOfOriginalImage = [copyOfOriginalImage imageByScalingAndCroppingForSize:CGSizeMake(600, 600)];
dispatch_async(dispatch_get_main_queue(), ^{
[photoView setImage:newImg];
[photoView setNeedsDisplay];
});
}
failureBlock:nil];
});
You can preload images before they are scrolled onto screen. The images you are preloading can be done on a background thread and saved into an NSCache instance. The cache will automatically purge (if you set the countLimit or the device runs low on memory) so when you preload, check for an item in the cache and load on a background thread if required.
Related
The problem is that I am calling multiple webservices on my homepage and the webservice is returning me the images and text from the server. During this process the UI become fully unresponsive for say 1-2 minutes which is looking very bad as I cant do anything. I heard about dispatch and tried to implement it but I dont get any results.May be I am doing something wrong.
What I want now that I want to that I want to run this process in background So that a user can interact with the UI during the fetching operation from the server. I am implementing my code just tell me where to use dispatch.
-(void)WebserviceHomeSlider{
if([AppDelegate appDelegate].isCheckConnection){
//Internet connection not available. Please try again.
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Internate error" message:#"Internet connection not available. Please try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
return;
}else {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
[manager GET:ServiceUrl#"fpMainBanner" parameters:nil success:^(AFHTTPRequestOperation *operation,id responseObject)
{
//NSLog(#"JSON: %#", responseObject);
arrSlider = [responseObject objectWithJSONSafeObjects];
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
}
Just tell me where to use dispatch or edit my code using dispatch if possible. I have gone through some examples but still my concept is not clear. Which dispatch method id best (DEFAULT or BACKGROUND). I will be very thankful to You.
This is the code you are looking for . Just tell me where to edit it using dispatch
-(NSArray *)getText{
NSMutableArray *textArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
texxt=[[arrSlider objectAtIndex:i ]valueForKey:#"title" ];
[textArr addObject:[texxt uppercaseString]];
}
return textArr;
}
-(NSArray *)getsubText{
NSMutableArray *subtext = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
subbtext=[[arrSlider objectAtIndex:i ]valueForKey:#"tagline_value" ];
if(i==8)
{
subbtext=#"MAKE YOURSELF STAND OUT GET YOUR FREE CARDS!";
}
NSLog(#"subtext is,,.,.,,.,%#.%#",#"k",subbtext);
[subtext addObject:[subbtext uppercaseString]];
}
return subtext;
}
-(NSArray *)getImages
{
NSMutableArray *mArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
}
return mArr;
}
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
The problem here is that you didn't showed implementation of addImagesFromResources method. You probably have to implement GCD in this method because i guess you are fetching images and setting on UI by this your main thread is getting blocked.
You are trying to access the UIView from thread other than the main which causes the UI unresponsiveness.
Please use this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
});
What I think is that your UI hangs due to downloading of image.
So you should use SDWebImage library that can help you to cache image and also prevent the UI getting hang. Using SDWebImage you can show the loader for the time the image is not loaded and it will cache the image. So from next time no image will be downloaded and also the UI will not hang. The link for complete reference to SDWebImage is :https://github.com/rs/SDWebImage
This block blocking your main UI to perform tasks so update this block as follow,
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
dispatch_async(dispatch_get_main_queue(), ^{
// If you want to refresh your UI simultaniously, you can write statement for that i.e
// yourImageView.image = img;
// Otherwise remove this queue
});
});
}
The main thread should never be used for long processing. Its there for the UI.
AFNetworking provides asynchronous functionality for downloading files. You should also be careful of thrashing the network layer with too many simulatanous downloads. I think 4 or 5 is the max the last time I tested.
So the flow of execution should be as follows.
The controller sets up the slideshow, sends the downloading of the images to a background thread(automatically handled by AFNetworking), passing a completion block to it. In this completion block you need to dispatch the work back to the main thread using GCD, dispatch_async to dispatch_get_get_main_queue
Keep your main thread's run loop available for user interaction.
It is because the images are loading in another thread and you are populating your slider right after getting the data.
It is better to implement this protocol method of KASlidershow to use the slideshow in a more memory efficient way.
(UIImage *)slideShow:(KASlideShow *)slideShow imageForPosition:(KASlideShowPosition)position
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
return img;
});
}
i didn't try the code but i hope it will work with little changes in variable names. Good luck
I am creating an iphone app, where I am trying to fetch multiple (5-7) hi-res images from backend server using URL. All fetched images I need to set in a slideshow, now the problem is that very first time if I am fetching images from server then my images are not displaying. Instead of images, am getting white background. Again if I go back and fetching the same images then it's displaying correctly.
Here is my code :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
for (int i=0; i<[end.slideShowArray count]; i++) {
SlideShow *pro = [[SlideShow alloc] init];
pro = [end.slideShowArray objectAtIndex:i];
NSURL *url = [NSURL URLWithString:pro.imageUrl];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:imageData];
dispatch_async (dispatch_get_main_queue (),
^{
[slideshow addImage:img];
});
}
});
You're creating a new SlideShow every time around the loop. I think you intended to create a single slide show then add all the images to it.
before i had been pulling the same image twice from my server by using, which worked fine but i need to cut down network usage
NSString *friendAvatar = [NSString stringWithFormat:#"%#%#%#", #"http://www.mydomain.com/images/users/", myWords[0], #".jpg"];
[imageFile setImageWithURL:[NSURL URLWithString:friendAvatar]];
[bgImageFile setImageWithURL:[NSURL URLWithString:friendAvatar]]; //this is a zoomed in version of the friends photo
now i am using this way to attempt to pul the image of the UIImageView that already pulled the photo, that way i dont have to be pulling the same photo twice...
NSString *friendAvatar = [NSString stringWithFormat:#"%#%#%#", #"http://www.mydomain.com/images/users/", myWords[0], #".jpg"];
[imageFile setImageWithURL:[NSURL URLWithString:friendAvatar]];
[bgImageFile setImage:imageFile.image];
when trying to use my new method. nothing happens. no error in the debugger, the background image just simply is blank.
Based on your comment I discovered that because you are using AFNetworking when you call
[imageFile setImageWithURL:[NSURL URLWithString:friendAvatar]];
it is being executed on a background thread, but the next line
[bgImageFile setImage:imageFile.image];
is not a AFNetworking call so it is being executed before the previous line has completed and thus there is no imageFile.image to use...
So, yes, my previous answer requires you to do the async code yourself, or you could wait for the image to load before setting bgImageFile.image (Which might be done with KVO???)
Try creating a UIImage first then set UIImageView.image to the created UIImage...
UIImage *avatarImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:friendAvatar]]];
[imageFile setImage:avatarImage];
[bgImageFile setImage:avatarImage];
And a better way would be...
dispatch_queue_t myQueue = dispatch_queue_create("com.myProgram.myQueue", NULL);
dispatch_async(myQueue, ^{
UIImage *avatarImage = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:friendAvatar]]];
dispatch_async(dispatch_get_main_queue(), ^{
[imageFile setImage:avatarImage];
[bgImageFile setImage:avatarImage];
});
});
This will load the file from the internet on a background thread and then update the ImageViews on the main thread when the image is done loading. The benefit is that your app won't freeze up during downloading.
I hope that helps
I'm developing a photo gallery application using AssetsLibrary to load my device photos. When presenting a random image in another VC I've noticed the following : it takes about 1 or 2 seconds for my full res image to load on the imageView (way much longer than the native photosApp) and I also get from the log "Received memory warning" after loading a few images. If I set my representation to fullScreenImage the warnings stop but I don't want this. What must I change for a smooth performance and high quality images on the view ?
Here's the code,hope you can tell me what's the problem :
This is the VC where I want to present my image on the screen
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#",assetsController);
detailImageView = [[UIImageView alloc]initWithFrame:self.view.bounds];
[self.view addSubview:detailImageView];
detailImageView.image = smallImage; //small image is my asset thumbnail and is passed as an argument in my init function
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ALAsset *asset = [assetsController.albumPics objectAtIndex:assetsController.index];
ALAssetRepresentation *representation = [asset defaultRepresentation];
bigImage = [[UIImage imageWithCGImage:[representation fullResolutionImage]]retain];
dispatch_async(dispatch_get_main_queue(), ^{
detailImageView.image = bigImage;
});
[pool release];
});
}
UPDATE 1
{
UIImageView *detailImageView = [[UIImageView alloc]initWithFrame:self.view.bounds];
[self.view addSubview:detailImageView];
detailImageView.image = smallImage;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ALAsset *asset = [assetsController.albumPics objectAtIndex:assetsController.index];
ALAssetRepresentation *representation = [asset defaultRepresentation];
UIImage *bigImage = [UIImage imageWithCGImage:[representation fullResolutionImage]];
dispatch_async(dispatch_get_main_queue(), ^{
detailImageView.image = bigImage;
});
[pool release];
});
}
Is bigImage an instance variable? Is it used in any place other than here? If it is not used anywhere else, then it should be a local variable, and you shouldn't retain it. If it is an instance variable that you retain, you need to release the previous value before assigning a new value to it.
Same discussion applies to detailImageView
I try to modify FGallery (https://github.com/gdavis/FGallery-iPhone).
I need it to read images from the camera roll, but I get memory leak.
Old code (path is file location):
#autoreleasepool {
NSString *path = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] bundlePath],_thumbUrl];
_thumbnail = [UIImage imageWithContentsOfFile:path];
_hasThumbLoaded = YES;
_isThumbLoading = NO;
[self performSelectorOnMainThread:#selector(didLoadThumbnail) withObject:nil waitUntilDone:YES];
}
My code (path is assert library url):
#autoreleasepool {
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset) {
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
_thumbnail = [UIImage imageWithCGImage:iref];
_hasThumbLoaded = YES;
_isThumbLoading = NO;
[self performSelectorOnMainThread:#selector(didLoadThumbnail) withObject:nil waitUntilDone:YES];
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror) {
NSLog(#"booya, cant get image - %#",[myerror localizedDescription]);
};
NSURL *asseturl = [NSURL URLWithString:_thumbUrl];
[assetslibrary assetForURL:asseturl
resultBlock:resultblock
failureBlock:failureblock];
}
}
For the same images, I get a big memory allocation (-didReceiveMemoryWarning) that crash the program in my code, but not while using the original code.
Any ideas why?
P.S. I use ARC, and did the automatic transition for FGallery. It works fine for local app images, but as said, I can't make it to work for camera roll images.
edit 1: the program crash
i think i got it.
the "ALAssetsLibraryAssetForURLResultBlock resultblock" is running on a different thread.
and so the "#autoreleasepool" does not apply to it. (each thread have it's own autorelease pool). hence, the memory footprint is much higher due to lot of "autoreleased" allocations (images).
adding "#autoreleasepool" inside the block stopped the crashs and big memory allocations.
in short:
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset) {
#autoreleasepool {
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
_thumbnail = [UIImage imageWithCGImage:iref];
_hasThumbLoaded = YES;
_isThumbLoading = NO;
[self performSelectorOnMainThread:#selector(didLoadThumbnail) withObject:nil waitUntilDone:YES];
}
}
};
thanks to all who replied.
Unless there is some need for the full resolution image, you would likely be better off using:
CGImageRef iref = [rep fullScreenImage];
This call return a CGImage of the representation that is appropriate for displaying full screen rather than the biggest, best representation available, unadjusted in any way.
Doing so will save loads of memory.
Getting a memory warning (-didReceiveMemoryWarning) is not the same as having a memory leak.It just means you have a lot of memory allocated and it's putting pressure on the system to where the OS interprets this as a potential problem that may occur soon.
A memory leak happens when you have unreferenced objects that did not get released. You can use the compiler analysis tool to see where potential leaks are. That won't find them all, so you can use instruments to see where any others may be happening. But until you have checked with those tools, you can't say for sure that you have a leak that isn't obvious by looking at the code.
You didn't mention if your code is crashing, but if it is, it is not necessarily due to a memory leak. That could happen when the OS decides something has to be removed to reduce memory pressure.
UPDATE
Show the code for the class ALAssetRepresentation . You may not be releasing something in there.
As user Picciano mentioned in his post, if possible you should use the [rep fullScreenImage]; call instead of requesting the full-size image. This will save a lot of space.
However, in my case this wasn't possible because I need to send a higher-res image to an external server later.
What you can do is use the scale to resize it as much as possible:
CGFloat originalRatio = assetRepresentation.dimensions.width / assetRepresentation.dimensions.height;
CGFloat wantedRatio = maxSize.width / maxSize.height;
CGFloat scale = 1;
if (originalRatio < wantedRatio)
{
scale = maxSize.height / assetRepresentation.dimensions.height;
}
else
{
scale = maxSize.width / assetRepresentation.dimensions.width;
}
CGImageRef ref = [assetRepresentation fullResolutionImage];
UIImage *image = [UIImage imageWithCGImage:ref
scale:scale orientation:orientation];
What this basically does it determine the amount we can scale the image (defined by maxSize). This saved us enough to prevent memory leaks.