UI got a little stuck since fetching a photo - ios

Now I'm using this code, with a little modification:
if (self._photoPath && !self._photo) {
dispatch_queue_t bg_thread = dispatch_queue_create("com.yourcompany.bg_thread", NULL);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(bg_thread,^{
NSData *data = [NSData dataWithContentsOfFile:self._photoPath];
if(data != nil) {
dispatch_async(main_queue,^{
self._photo = [UIImage imageWithData:data];
[self.photoButton setImage:[UIImage imageNamed:#"photoButton.png"] forState:UIControlStateNormal];
});
}
});
}
As you can see, in fact after I get that photo I want set it immediately to my "photoButton",
but now, UI got smoothed, but my photoButton's appearance is always black...
What should I do next?
_______________________Updated___________________
I have 2 viewControllers, A and B.
A is the root viewController, and B is A's child viewController.
In B, there is a button for calling the camera to take a photo.
After user took a photo, the photo's appearance becomes that photo.
When I push a new B (with no photo) from A,
things goes smoothly.
But when there is an old B with a photo in it,
the animation gets a little stuck, caused by the following code I guess:
- (void)viewWillAppear:(BOOL) animated {
if (self._photoPath && !self._photo) {
NSData *data = [NSData dataWithContentsOfFile:self._photoPath];
if(data != nil)
self._photo = [UIImage imageWithData:data];
}
[super viewWillApear];
}
But I do need to get that photo before the view is displayed since I need to set that photo to my photoButton's background.
So, is there a way to avoid sticking the view's animation? Because it really result in bad user experience.

Try fetching the photo in a backgroudn thread (I'm using GCD here):
- (void)viewWillAppear:(BOOL) animated {
if (self._photoPath && !self._photo) {
dispatch_queue_t bg_thread = dispatch_queue_create("com.yourcompany.bg_thread", NULL);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(bg_thread,^{
NSData *data = [NSData dataWithContentsOfFile:self._photoPath];
if(data != nil) {
dispatch_async(main_queue,^{ self._photo = [UIImage imageWithData:data]; });
}
});
}
[super viewWillApear];
}

Related

How to load images into uicollectionview cell from url asynchronous

I have a method that I'm calling from cell in the viewForRowAtIndexPath method.
The method containts only this code
NSURL *imageURL = [NSURL URLWithString:urlString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^(void){
completionBLock(image);
});
Images are loading very slowly and sometimes not at all. Everytime the cell goes back into view the method gets called again.
This is in my cellForIndexPathRow
[Method getImage:brochureModel.brochureCoverUrl Oncompletion:^(UIImage *image) {
cell.imageView.image = image;
}];
How can I download images from a url and display it in a collection view reusable cells.
Note: I'm trying to not use any third party libraries for my app
My problem is with the caching of the images. I tried this with self.imageCache being a mutableDictionary.
if (self.imageCache[urlString]) {
UIImage *imageFromCache = self.imageCache[urlString];
onImageDidLoad(imageFromCache);
} else {
[API fetchImageFromUrl:urlString onDidLoad:^(UIImage *image) {
self.imageCache[urlString] = image;
onImageDidLoad(image);
}];
}

how can I reduce time complexity from picking images from array

That is how I have my data:
(
{
image = "https://www.myurl/abc- a/uploads/1161570652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/1161570652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/1161570652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/11615720652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/11615730652.jpg";
}
)
I could retrieve it successfully. But the problem is it is very slow.
Here is my code:
seeview=[[UIImageView alloc]init];
for (int k=0; k<[dictofimage count]; k++)
{
img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[image_array objectAtIndex:k]]]];
NSLog(#"image name is 0 %#",img);
[myimageview setImage:img];
}
It takes time to pick every element and is slow in progress. Is there any faster way to do it?
You can't get away from looping through all of the contents of image_array so you're stuck with O(n) but you can use a faster construct. The for:in loop is much faster than a typical C style loop, so something like:
for (imageString in image_array) {
img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageString]]];
NSLog(#"image name is 0 %#",img);
[myimageview setImage:img];
}
For a breakdown of the benchmarks check out: http://iosdevelopertips.com/objective-c/high-performance-collection-looping-objective-c.html
EDIT: I should also note that dataWithContentsOfURL is a synchronous call that you probably shouldn't be making in this loop. You'd be better off creating an NSOperation that downloads the image, and when it finishes you can dispatch back to the main queue to set the actual image in the imageView.
You are better off handling the array of objects concurrently, in the background. This is precisely what dispatch_apply is for.
dispatch_apply(
image_array.count,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^(size_t index) {
NSURL *url = [NSURL URLWithString:[image_array objectAtIndex:index]];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
dispatch_async(dispatch_get_main_queue(), ^{
// Handle the image on the main thread for UI elements
});
});
You only need the dispatch_async if you need to handle the image on the main thread as part of a UI change.
Note, that dispatch_apply is synchronous, in that it kicks off one background task for each iteration. Those tasks run in the background, but dispatch_apply will not return until they are all done. Thus, you may also want to wrap that in an async block.
Or, you can do it yourself, and get notified when all the blocks are complete...
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
for (size_t i = 0; i < image_array.count; ++i) {
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:[image_array objectAtIndex:index]];
// The rest is the same...
});
}
dispatch_group_notify(group, queue, ^{
// This code will execute on `queue` when all the above blocks have finished
});
Note, however, that dispatch_apply has a nice algorithm to not overload the system. The above approach is not so kind.
However, you can get dispatch_apply nice-ness using an async call.
dispatch_async(queue, ^{
dispatch_apply(image_array.count, queue, ^(size_t index) {
NSURL *url = [NSURL URLWithString:[image_array objectAtIndex:index]];
// The rest is the same...
});
// This code will execute on `queue` when all the above blocks have finished
});

Multiple webservice Making UI unresponsive

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

fetching image from URL taking too much time in watch app

I am fetching an image from a URL to set it as a background image of WKInterfaceGroup in a watch app but it is taking too much time (around 20 seconds) to load the background image, I seem to have used the most preferred method but I am not able to figure out why it is taking so long to load an image... The following is my code:
-(void) watchData:(WatchJSONParser *)data
{
int type = (int)data.sourceType;
watchUserData = data.watchDataDict;
NSLog(#"watchUserData:%#", watchUserData);
switch (type)
{
case DataConnectionSourceTypeUserData:
if([watchUserData objectForKey:#"userData"])
{
skpImgServerUrl = [[watchUserData objectForKey:#"userData"] objectForKey:#"imageServerURL"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#",skpImgServerUrl,[[watchUserData objectForKey:#"userData"] objectForKey:#"tenantLogo"]]];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(#"ASY thread:%#", url);
dispatch_async(dispatch_get_main_queue(), ^{
//[self.homeBgGroup setBackgroundImage:placeholder];
[self.homeBgGroup setBackgroundImageData:data];
[statusLabel setHidden:YES];
});
});
}
break;
default:
break;
}
}

Stop asynchronous UITableViewCell image loading when cell is not visible?

I'm developing an iOS 5.0+ application with latest SDK.
This is the code I use to load images asynchronously for UITableViewCell.
- (UITableViewCell*)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ((groups != nil) && (groups.count > 0))
{
GroupCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[GroupCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
// Configure the cell...
Group* group = [groups objectAtIndex:indexPath.row];
cell.GroupNameLabel.text = group.Name;
// TODO: Poner el estado.
if (group.Photo)
cell.GroupImageView.image = group.Photo;
else
{
// download the photo asynchronously
NSString *urlString =
[NSString stringWithFormat:kGetGroupImageURL, [group.GroupId intValue]];
NSURL *url = [NSURL URLWithString:urlString];
[ImageTool downloadImageWithURL:url completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded)
{
// change the image in the cell
cell.GroupImageView.image = image;
// cache the image for use later (when scrolling up)
group.Photo = image;
}
}];
}
return cell;
}
else
return nil;
}
And the loader:
#import "ImageTool.h"
#implementation ImageTool
+ (void)downloadImageWithURL:(NSURL *)url
completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
But it seems it's not going to work because I don't handle if the cell for which I'm loading the image it is still visible.
How can I handle if the cell is still visible?
I have found this article, but I don't know how to implement it.
You generally handle this by checking to see if the cell is still visible using the UITableView method cellForRowAtIndexPath:, which returns nil if the cell for that index path is no longer visible. Note, this method is not to be confused with the similarly named UITableViewDataSource method tableView:cellForRowAtIndexPath:.
if (group.Photo)
cell.GroupImageView.image = group.Photo;
else
{
// don't forget to `nil` or use placeholder for `GroupImageView` because if
// the cell is reused, you might see the old image for the other row momentarily
// while the new image is being retrieved
cell.GroupImageView.image = [UIImage imageNamed:#"placeholder.png"];
// download the photo asynchronously
NSString *urlString =
[NSString stringWithFormat:kGetGroupImageURL, [group.GroupId intValue]];
NSURL *url = [NSURL URLWithString:urlString];
[ImageTool downloadImageWithURL:url completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded)
{
dispatch_async(dispatch_get_main_queue(), ^ {
GroupCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
{
// change the image in the cell
updateCell.GroupImageView.image = image;
}
// cache the image for use later (when scrolling up)
group.Photo = image;
});
}
}];
}
Note, if the completion block of sendAsynchronousRequest is not submitted to the main queue (e.g. you may do something computationally expensive that you don't want to tie up the main queue with), you might further dispatch the UI update to the main queue as shown above. You can do it either here, like shown above, or in the downloadImageWithURL. But make sure to update the UI on the main queue (and to avoid synchronization issues, update group, there, too). Since you've specified the main queue for your operation queue, though, this is not a critical issue.
And as I point out in my code comments, if you're retrieving an image for a cell that has been reused, don't forget to reset the GroupImageView.image property to nil, too. If you don't, you may see the previous image for the reused cell show up momentarily while the request for the new row is in progress.
Alternatively, you should use a UIImageView category, such as provided by SDWebImage which takes care of all of this for you (including cache management).
As an aside, it also handles a subtle issue, which your code doesn't, namely where you scroll really quickly through a long tableview on a slow internet connection. You implementation wouldn't load the images for the currently visible cells until all of the other cells' images have loaded (since there can be only five concurrent NSURLConnection objects at a time). These UIImageView categories generally cancel the old requests for cells that have been reused, which ensures that the UI is presenting the images that the user is actually looking at more quickly.

Resources