iOS Delay even when using dispatch - ios

I have a wallpaper app that has a paged scrollview on the main view. each page of the scrollview displays 9 images.
When the view is scrolled I'm loading the next 10 pages of images and set the previous 10 pages uiimages that I've loaded to nil to prevent memory warnings.
The problem is when the view scrolls and so the following method of scrollview gets called, there is few seconds delay before the view can scroll even though I have put the block code that loads new images in dispatch_async.
and when I comment out the whole section of the code with dispatch stuff, there is no delay.
If anyone has any idea on why this happens. please please let me know.
Thank you all so much
- (void)scrollViewDidScroll:(UIScrollView *)scrollV
{
float fractionalPage = scrollView.contentOffset.x / self.view.frame.size.width;
if (curPageNum != lround(fractionalPage)) {
curPageNum = lround(fractionalPage);
//display the page number
pageNumLabel.text = [NSString stringWithFormat:#"%d",curPageNum+1];
pageNumLabel.alpha = 1.0;
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(dismissPageNum) userInfo:nil repeats:NO];
//loading more pages with 9 image in each
lastLoadedPageNum = MIN(lastLoadedPageNum + 10, numPages - 1);
dispatch_queue_t getImages = dispatch_queue_create("Getting Images", nil);
dispatch_async(getImages, ^{
for (int i = curPageNum + 4 ; i <= lastLoadedPageNum ; i++) {
int numPicsPerPage = 9;
if (picsNames.count%9 && i == numPages-1) {
numPicsPerPage = picsNames.count%9;
}
for (int j = 0 ; j < numPicsPerPage ; j++) {
UIImage *image = [brain imageWith:[picsNames objectAtIndex:(i*9) + j]];
dispatch_async(dispatch_get_main_queue(), ^{
//loading the image to imageview
UIImageView *imageView = (UIImageView *)[scrollView viewWithTag:IMAGE_VIEWS_TAG + (i*9) + j];
imageView.image = image;
});
}
}
});
//setting old imageview images to nil to release memory
int oldFirstLoadedPageNum = firtLoadedPageNum;
firtLoadedPageNum = MAX(curPageNum - 4, 0);
for (int i = oldFirstLoadedPageNum ; i < firtLoadedPageNum ; i++) {
int numPicsPerPage = 9;
if (picsNames.count%9 && i == numPages-1) {
numPicsPerPage = picsNames.count%9;
}
for (int j = 0 ; j < numPicsPerPage ; j++) {
UIImageView *imageView = (UIImageView *)[scrollView viewWithTag:IMAGE_VIEWS_TAG + (i*9) + j];
imageView.image = nil;
[((UIActivityIndicatorView *)[imageView viewWithTag:ACTIVITY_INDICATOR_TAG]) startAnimating];
}
}
}
}
Brain method imageWith:
-(UIImage *)imageWith:(NSString *)imageName
{
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:imageName];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
if (!image && [self connected]) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", picsURL, imageName]]]];
if (image) {
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:YES];
}
}
return image;
}

Clearly your code is looping which causes the delay. I think since the dispatch is inside the for loop it gets called only after a certain bit of iterations so that there is no real gain in using multi threading here.

Most likely your logic after the block with the nested for loops is causing the delay. Move that out into a separate method and run it in a separate thread.

As suggested by Mike Pollard I used
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0)
instead of
dispatch_queue_create("Getting Images", nil)
and it solved the issue.
Thanks everyone.

Related

Updating the text of a UIlabel

I have a slide display that gathers a series of images from a database. Each image has descriptions and other properties associated with it. Basically I want to update the text of a UILabel every time a new image is loaded. How can I do that?
Here is what I am working with:
//SLIDE MENU
- (void)loadVisiblePages {
// First, determine which page is currently visible
CGFloat pageWidth = gridView.frame.size.width;
NSInteger page = (NSInteger)floor((gridView.contentOffset.x * 2.0f + pageWidth) / (pageWidth * 2.0f));
//NSArray * descriptArray[20];
// Update the page control
pageControl.currentPage = page;
// Work out which pages you want to load
NSInteger firstPage = page - 1;
NSInteger lastPage = page + 1;
// Purge anything before the first page
for (NSInteger i=0; i<firstPage; i++) {
[self purgePage:i];
}
for (NSInteger i=firstPage; i<=lastPage; i++) {
[self loadPage:i];
// descriptArray[i] = selectedPhotoDesc;
}
for (NSInteger i=lastPage+1; i<self.pageImages.count; i++) {
[self purgePage:i];
}
}
- (void)loadPage:(NSInteger)page {
NSURL* imageURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#%#/%#.jpg", host, photoPath, collection, photoNumber]];
//get photo from server
NSData *imageData = [[NSData alloc] initWithContentsOfURL:imageURL];
UIImage *image = [[UIImage alloc] initWithData: imageData];
if (page < 0 || page >= 20) {
return;
}
// Load an individual page, first checking if you've already loaded it
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView == [NSNull null]) {
CGRect frame = gridView.bounds;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0.0f;
frame = CGRectInset(frame, 10.0f, 0.0f);
UIImageView *newPageView = [[UIImageView alloc] initWithImage:image];
newPageView.frame = frame;
[gridView addSubview:newPageView];
labelDescription.text = descriptionArray[page];
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
- (void)purgePage:(NSInteger)page {
if (page < 0 || page >= self.pageImages.count) {
// If it's outside the range of what you have to display, then do nothing
return;
}
// Remove a page from the scroll view and reset the container array
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView != [NSNull null]) {
[pageView removeFromSuperview];
[self.pageViews replaceObjectAtIndex:page withObject:[NSNull null]];
labelDescription.text = #"";
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// Load the pages that are now on screen
[self loadVisiblePages];
}
I should mention that I am able to slide different images but cannot update their text. As it stands, I can only get the text corresponding to the first image but nothing else.
THANKS

Parse.com Saving images sample. retrieve only one image

I have set up the sample from parse.com where you can upload an image from the camera and then display all the images in a grid view.
What I want is to display only the recent image taken instead of a total grid view.
The sample is located here : Parse example
The images is being downloaded and placed in some sort of grid view.
The use NSMutableArray *allImages; to get all the images.
The images is being output with button classes and a link to a new view.
The new view then only contains the image from the grid view (the one you click).
I would like to instead of downloading all of the images and placing them inside a grid view - just to display the latest image.
They code used to take the image from a small thumbnail to the full image view is like this:
// When picture is touched, open a viewcontroller with the image
PFObject *theObject = (PFObject *)[allImages objectAtIndex:[sender tag]];
PFFile *theImage = [theObject objectForKey:#"imageFile"];
NSData *imageData;
imageData = [theImage getData];
UIImage *selectedPhoto = [UIImage imageWithData:imageData];
PhotoDetailViewController *pdvc = [[PhotoDetailViewController alloc] init];
pdvc.selectedImage = selectedPhoto;
[self presentViewController:pdvc animated:YES completion:nil];
The code to get allImages is like:
// Contains a list of all the BUTTONS
allImages = [images mutableCopy];
// This method sets up the downloaded images and places them nicely in a grid
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSMutableArray *imageDataArray = [NSMutableArray array];
// Iterate over all images and get the data from the PFFile
for (int i = 0; i < images.count; i++) {
PFObject *eachObject = [images objectAtIndex:i];
PFFile *theImage = [eachObject objectForKey:#"imageFile"];
NSData *imageData = [theImage getData];
UIImage *image = [UIImage imageWithData:imageData];
[imageDataArray addObject:image];
}
// Dispatch to main thread to update the UI
dispatch_async(dispatch_get_main_queue(), ^{
// Remove old grid
for (UIView *view in [photoScrollView subviews]) {
if ([view isKindOfClass:[UIButton class]]) {
[view removeFromSuperview];
}
}
// Create the buttons necessary for each image in the grid
for (int i = 0; i < [imageDataArray count]; i++) {
PFObject *eachObject = [images objectAtIndex:i];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = [imageDataArray objectAtIndex:i];
[button setImage:image forState:UIControlStateNormal];
button.showsTouchWhenHighlighted = YES;
[button addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
button.tag = i;
button.frame = CGRectMake(THUMBNAIL_WIDTH * (i % THUMBNAIL_COLS) + PADDING * (i % THUMBNAIL_COLS) + PADDING,
THUMBNAIL_HEIGHT * (i / THUMBNAIL_COLS) + PADDING * (i / THUMBNAIL_COLS) + PADDING + PADDING_TOP,
THUMBNAIL_WIDTH,
THUMBNAIL_HEIGHT);
button.imageView.contentMode = UIViewContentModeScaleAspectFill;
[button setTitle:[eachObject objectId] forState:UIControlStateReserved];
[photoScrollView addSubview:button];
}
// Size the grid accordingly
int rows = images.count / THUMBNAIL_COLS;
if (((float)images.count / THUMBNAIL_COLS) - rows != 0) {
rows++;
}
int height = THUMBNAIL_HEIGHT * rows + PADDING * rows + PADDING + PADDING_TOP;
photoScrollView.contentSize = CGSizeMake(self.view.frame.size.width, height);
photoScrollView.clipsToBounds = YES;
});
});
I'm not an expert at this iOS-coding and I'm really just looking for a simple solution to display the recent image posted.
Looking through the posted example code, it would be a big undertaking to modify it to handle just a one image (mostly deleting a lot of yuck that synchs object ids). If you just want the quickest path to get started, you can do this:
// ...
// where you do the query for UserPhoto
[query orderByAscending:#"createdAt"];
// use the getFirst form of the query
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
// build the objects array that the demo code expects, but out of just one object
NSArray *objects = #[ object ];
// the remainder of the code as it is
A lot of UI code can get simpler, too. But the key is to skip all of the grid/button building code and do something like this:
// ...
// inside the method called setUpImages:
// this loop will only run once since we have only one object in the array
for (int i = 0; i < [imageDataArray count]; i++) {
PFObject *eachObject = [images objectAtIndex:i];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = [imageDataArray objectAtIndex:i];
// you're essentially done here. add a big image view
// that replaces photoScrollView, then:
self.myBigImageView.image = image;
Hope that helps.

How to stop preloading process running in concurrent thread?

In my game, after play button is pressed, game view controller is presented and couple of assets start to preload in background for later use in game.
My question is, how to stop preloading process when home button is pressed but my preloading process is still running?
Now I've to wait till all preloading is done for proper deallocating...
ViewController.m
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
// preload intro sequences
dispatch_sync(concurrentQueue, ^{
[weak_self.spaceSun prepareAnimationArrays];
});
dispatch_async(concurrentQueue, ^{
[weak_self.sailor prepareAnimationArrays];
});
SpaceSun, Sailor.m
- (void)prepareAnimationArrays
{
_introArray = [NSMutableArray new];
_introArray = [self.animator generateCachedImageArrayWithFilename:#"circleSun" extension:#".png" andImageCount:10];
self.isAnimationArrayCached = YES;
}
- (NSMutableArray *)generateCachedLoopedImageArrayWithFilename:(NSString *)filename
extension:
(NSString *)extension
andImageCount:
(int)count
{
_imagesArray = [[NSMutableArray alloc] init];
_fileExtension = extension;
_animationImageName = filename;
_imageCount = count;
for (int i = 0; i < _imageCount; i++)
{
NSString *tempImageName = [NSString stringWithFormat:#"%#%i", filename, i];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:tempImageName ofType:extension];
UIImage *frameImage = [self loadRetinaImageIfAvailable:imagePath];
UIGraphicsBeginImageContextWithOptions(frameImage.size, NO, [UIScreen mainScreen].scale);
CGRect rect = CGRectMake(0, 0, frameImage.size.width, frameImage.size.height);
[frameImage drawInRect:rect];
UIImage *renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_imagesArray addObject:renderedImage];
_precachedImagesCount++;
if (_didPreCachePicture)
_didPreCachePicture();
if (_isDoublingFrames)
{
[_imagesArray addObject:renderedImage];
}
else if (_isTriplingFrames)
{
[_imagesArray addObject:renderedImage];
[_imagesArray addObject:renderedImage];
}
if (i == _imageCount - 1)
{
// we have 5 images
// 12345 already in array
// let's add 432
for (int j = _imageCount - 2; j > 0; j--)
{
tempImageName = [NSString stringWithFormat:#"%#%i", filename, j];
imagePath = [[NSBundle mainBundle] pathForResource:tempImageName ofType:extension];
frameImage = [self loadRetinaImageIfAvailable:imagePath];
UIGraphicsBeginImageContextWithOptions(frameImage.size, NO, [UIScreen mainScreen].scale);
rect = CGRectMake(0, 0, frameImage.size.width, frameImage.size.height);
[frameImage drawInRect:rect];
renderedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_imagesArray addObject:renderedImage];
_precachedImagesCount++;
if (_didPreCachePicture)
_didPreCachePicture();
if (_isDoublingFrames)
{
[_imagesArray addObject:renderedImage];
}
else if (_isTriplingFrames)
{
[_imagesArray addObject:renderedImage];
[_imagesArray addObject:renderedImage];
}
}
}
}
return _imagesArray;
}
You cannot cancel a block that has been scheduled using dispatch_async. Instead of using GCD you should prefer the higher-level NSOperation API. Create multiple NSOperation subclasses for your tasks and schedule them on a NSOperationQueue.
If you have tasks that need to finish earlier than others specify dependencies between the operations with addDependency:.
To cancel the preloading process simply call:
[self.operationQueue cancelAllOperations];
In order to benefit from the cancelation feature exposed by operation queues, you should regularly check the isCancelled property for longer-running operations:
- (void)main
{
for (int i = 0; i < _imageCount; i++) {
if (self.isCancelled) break;
// do your processing
}
}

Animation For loop image does not show

I've downloaded animated bonfire from the internet. My image sequence is "*.png" and I can't get it work. It keeps looping to the end. It does not show image. How come I can't animate this code? What did I do wrong?
UIImageView* campFireView = [[UIImageView alloc] initWithFrame:screenx.frame];
int j;
int i;
for (j = 1, i = 0; i < 28; i =1)
{
NSLog( #"%i.png", i);
campFireView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:[NSString stringWithFormat:#"%i.png",i]], nil];
}
// all frames will execute in 1.75 seconds
campFireView.animationDuration = .15;
// repeat the annimation forever
campFireView.animationRepeatCount = 1;
// start animatinganimationRepeatCount:1];
[campFireView startAnimating];
// add the animation view to the main window
[self.view addSubview:campFireView];
[campFireView release];
Every time you call:
campFireView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:[NSString stringWithFormat:#"%i.png",i]], nil];
You are setting the animationImages to a one-item array. What you probably meant to do instead:
NSMutableArray *animationImages = [NSMutableArray array];
for (i = 0; i < 28; i++)
{
[animationImages addObject:[UIImage imageNamed:[NSString stringWithFormat:#"%i.png",i]]];
}
campfireView.animationImages = animationImages;
Also - I don't know what was going on with your for loop but I fixed it.
When u r looping then providing one image every time to animationImages
NSMutableArray *arrImgs = [NSMutableArray array];
for (i = 0; i < 28; i++)
{
[arrImgs addObject:[UIImage imageNamed:[NSString stringWithFormat:#"%i.png",i]]];
}
campfireView.animationImages = [NSArray arrayWithArray:arrImgs];

How do I remove an UIImageView animation from memory completely?

I'm trying to free up my apps memory and want to completely remove loadingImageView from memory when I tap a button. How do I do this?
-(IBAction)animationOneStart {
NSMutableArray *images = [[NSMutableArray alloc] initWithCapacity:88];
for(int count = 1; count <= 88; count++)
{
NSString *fileName = [NSString stringWithFormat:#"testPic1_%03d.jpg", count];
UIImage *frame = [UIImage imageNamed:fileName];
[images addObject:frame];
}
loadingImageView.animationImages = images;
loadingImageView.animationDuration = 5;
loadingImageView.animationRepeatCount = 1; //Repeats indefinitely
[loadingImageView startAnimating];
[images release];
}
-(IBAction)removeFromMemory {
//What do I add here?
}
Thanks!
Ok. If you want to remove UIImageView animation try this:
-(IBAction)removeFromMemory {
[loadingImageView stopAnimating]; //here animation stops
[loadingImageView removeFromSuperview]; // here view removes from view hierarchy
[loadingImageView release]; loadingImageView = nil; //clean memory
}
For remove CoreAnimation from UIView instance use method:
[view.layer removeAllAnimations];

Resources