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
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 know this question has been asked many times on stackoverflow. But my problem is different.
I am iterating on the albumns of photos library to get all videos and their thumbnails.
Now, the problem is, my code is very slow to get the thumbnail of each video. For example, there is 14 videos in my camera roll and the total time taken to generate the thumbnail is around 3-4 seconds. I am using this code.
+(UIImage*)imageFromVideoAtURL:(NSURL*)contentURL {
UIImage* theImage = nil;
AVURLAsset* asset = [[AVURLAsset alloc] initWithURL:contentURL options:nil];
AVAssetImageGenerator* generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform = YES;
NSError* err = NULL;
CMTime time = CMTimeMake(1, 60);
CGImageRef imgRef = [generator copyCGImageAtTime:time actualTime:NULL error:&err];
theImage = [[[UIImage alloc] initWithCGImage:imgRef] autorelease];
CGImageRelease(imgRef);
[asset release];
[generator release];
return theImage;
}
I am finding a way to get the thumbnail of all videos very fast so that user has not to wait. I have seen apps on Apple store that are doing the same thing in just micro seconds. Please help.
I have also tried this code to generate the thumbnail, it is also very slow.
MPMoviePlayerController *moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:mediaUrl];
moviePlayer.shouldAutoplay = NO;
UIImage *thumbnail = [[moviePlayer thumbnailImageAtTime:0.0 timeOption:MPMovieTimeOptionNearestKeyFrame] retain];
[imageView setImage:thumbnail]; //imageView is a UIImageView
If you are loading videos from image library, it should already have the embedded thumbnail of the video. This can be accessed using thumbnail or aspectRatioThumnail methods of ALAsset class.
So in your case the thumbnails could be loaded like:
ALAssetLibrary* lib = [ALAssetLibrary new];
[lib assetForURL:contentURL resultBlock:^(ALAsset* asset) {
CGImageRef thumb = [asset thumbnail];
dispatch_async(dispatch_get_main_queue(), ^{
//do any UI operation here with thumb
});
}];
Please make sure to make any UIKit call in the main queue as assetForURL:: method may invoke the resultBlock in some background thread.
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.
I am trying to load data from web with few simple steps:
NSData *JSONData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:#"******"]];
NSObject *json = [JSONData objectFromJSONData];
NSArray *arrayOfStreams = [json valueForKeyPath:#"programs"];
NSDictionary *stream = [arrayOfStreams objectAtIndex:0];
NSString *str = [[NSString alloc]initWithString:[stream valueForKey:#"image"]];
NSURL *urlForImage1 = [NSURL URLWithString:str];
NSData *imageData1 = [NSData dataWithContentsOfURL:urlForImage1];
_screenForVideo1.image = [UIImage imageWithData:imageData1];
But the problem is I am doing 30 of this right after my application launches...
I want to load about 5 of them, and than load others. Because when I try to load all of them at the same time, my app is not launching all of them loaded...
Is there any way that I can load first few of them, and wait, and than load others?
As for loading them, you should probably display a spinner, start loading the image in background and then replace the spinner with the image once it’s ready.
- (void) viewDidLoad {
UIActivityIndicator *spinner = …;
[self.view addSubview:spinner];
[self performSelectorInBackground:#selector(startLoadingImage)
withObject:nil];
}
- (void) startLoadingImage {
// You need an autorelease pool since you are running
// in a different thread now.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [UIImage imageNamed:#"foo"];
// All GUI updates have to be done from the main thread.
// We wait for the call to finish so that the pool won’t
// claim the image before imageDidFinishLoading: finishes.
[self performSelectorOnMainThread:#selector(imageDidFinishLoading:)
withObject:image waitUntilDone:YES];
[pool drain];
}
- (void) imageDidFinishLoading: (UIImage*) image {
// fade spinner out
// create UIImageView and fade in
}
This code generates Low memory warning because background 5 thread is almostly loading images in same time.
I want to give a priority each thread and make lock & unlock.
I would make step by step thread. image 1 loading -> image2 loading -> image3 loading -> image4 loading.
How can I do this?
viewcontroller
-(void)viewDidLoad
{
for(int i=0; i<screenshotcount ; i++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString * url=[detailItem.mScreenshot objectAtIndex:i];
NSDictionary *args=[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:i], #"screenNum",
[NSString stringWithString:url],#"url",
nil];
[self performSelectorInBackground:#selector(loadImageScreenshot:) withObject:args];
[pool release];
}
}
loading image
-(void) loadImageScreenshot:(NSDictionary *) args
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage * screenshotImage=[UIImage imageWithStringURL:url];
NSDictionary *args2=[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:num], #"screenNum",
screenshotImage,#"image",
nil];
[self performSelectorOnMainThread:#selector(assignImageToScreenshotImageView:) withObject:args2 waitUntilDone:YES];
[pool release];
}
image add
- (void) assignImageToScreenshotImageView:(NSDictionary *)arg
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage * image= [arg objectForKey:#"image"];
UIImageView *imageview=[UIImageView alloc]init];
.
.
imageview.image=image;
[self.mScreenshotSpace addSubview:imageview];
[imageview release];
[pool release];
}
image from url
+(UIImage *)imageWithStringURL:(NSString *)strURL
{
NSURL *url =[NSURL URLWithString:strURL];
NSData * data=[[NSData alloc]initWithContentsOfURL:url options:NSDataReadingUncached error:&error];
UIImage * image=[UIImage imageWithData:data ];
[data release];
return image;
}
maybe I misunderstood your question, but from what you said, what you really want is to "serialise" the threads, that is, ensure they are executed one after the other. If this is the case I do not see a big advantage in having five (or more) threads if they spend most of the time waiting in a sort of "thread queue" :)
My 2 cents: Instead of playing with the threads' priority, maybe you should think about re-design the code to have a queue of files/images to load and a thread that dequeue and load the images one after the other. (the classic consumer/producer scenario)
If you need to speed up things you might think to have another thread doing some pre-fetching (if that make sense in your sw design/architecture)
CiaoCiao
Sergio
Sergio is right - if you can look at GCD and serialized queues, it shouldn't be too hard to migrate to it. Take a look at the videos from Apple for good how-to steps.