UITableView cell shows same images on fast scrolling - ios

I am creating the iPhone app which shows the app icons & app names in table view.
First time i download the images in user document directory & then make entry in dictionary [value - image stored document directory path & key is image json URL], for showing image in cell first i checked the image is already download or not.
If downloaded, then show the local image which stored on document directory and if not download it.
If i Scrolled normally, cell shows the rights images & if i scrolled it fast, cell shows the same images instead of different.
// code for displaying images
-(void)refreshViews
{
self.appLabelName.text = _applicationObject.name;
self.appLabelName.font = [UIFont fontWithName:#"Helvetica-Bold" size:17];
self.detailTextLabel.text = _applicationObject.artistName;
self.detailTextLabel.font = [UIFont fontWithName:#"Helvetica" size:14];
NSString *appIconStoredPath = [appDelgate.saveAppIconURLAndPathInFile valueForKey:_applicationObject.iconURL];
_appIcon.image = [UIImage imageWithContentsOfFile:appIconStoredPath];
if(!_appIcon.image && appDelgate.hasInternetConnection)
{
[self downloadAppIconsInDirectory];
}
}
// code for download image
-(void)downloadAppIconsInDirectory
{
NSURL *downloadURL = [NSURL URLWithString:_applicationObject.iconURL];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:nil delegateQueue:nil];
__weak ApplicationCell* weakSelf = self;
dispatch_async(queue, ^{
downloadTask = [session downloadTaskWithURL:downloadURL completionHandler:^(NSURL *location, NSURLResponse *respone, NSError *error)
{
NSString *iconName = [location lastPathComponent];
NSMutableString *changeIconName = [[NSMutableString alloc] init];
changeIconName = [iconName mutableCopy];
[changeIconName setString:_applicationObject.bundleId];![enter image description here][1]
NSString *appIconDirectory = [[documentsDirectoryForAppIcons absoluteString] stringByAppendingPathComponent:#"appIcons"];
destinationUrlForAppIcons = [[NSURL URLWithString:appIconDirectory] URLByAppendingPathComponent:changeIconName];
NSError *error1;
BOOL status = [appIconFileManager copyItemAtURL:location toURL:destinationUrlForAppIcons error:&error1];
if (status && !error1)
{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf refreshViews];
});
[appDelgate.saveAppIconURLAndPathInFile setValue:destinationUrlForAppIcons.path forKey:_applicationObject.iconURL];
NSString *dictSavedFilePath = [appDelgate.documentDirectoryPath stringByAppendingPathComponent:#"IconURLsAndPaths.plist"];
dispatch_async(queue, ^{
[appDelgate.saveAppIconURLAndPathInFile writeToFile:dictSavedFilePath atomically:YES];
});
}
}];
[downloadTask resume];
});
}

As it shows, there is no error in code. This means you are priority for queues is wrong. Image must be downloaded before scrolling. As you scroll your view slow, image gets enough time to be downloaded. This means you change your code to this and try ;)
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration
defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:nil
delegateQueue:nil];
__weak ApplicationCell* weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
downloadTask = [session downloadTaskWithURL:downloadURL completionHandler:^(NSURL *location,
{
NSString *iconName = [location lastPathComponent];
NSMutableString *changeIconName = [[NSMutableString alloc] init];
changeIconName = [iconName mutableCopy];
[changeIconName setString:_applicationObject.bundleId];![enter image description here]
[1]
NSString *appIconDirectory = [[documentsDirectoryForAppIcons absoluteString]
stringByAppendingPathComponent:#"appIcons"];
destinationUrlForAppIcons = [[NSURL URLWithString:appIconDirectory]
URLByAppendingPathComponent:changeIconName];
NSError *error1;
BOOL status = [appIconFileManager copyItemAtURL:location
toURL:destinationUrlForAppIcons error:&error1];
if (status && !error1)
{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf refreshViews];
});
[appDelgate.saveAppIconURLAndPathInFile
setValue:destinationUrlForAppIcons.path forKey:_applicationObject.iconURL];
NSString *dictSavedFilePath = [appDelgate.documentDirectoryPath
stringByAppendingPathComponent:#"IconURLsAndPaths.plist"];
dispatch_async(queue, ^{
[appDelgate.saveAppIconURLAndPathInFile writeToFile:dictSavedFilePath
atomically:YES];
});
}
}];
[downloadTask resume];
});
}

replace your refreshViews method with this
-(void)refreshViews
{
self.appLabelName.text = _applicationObject.name;
self.appLabelName.font = [UIFont fontWithName:#"Helvetica-Bold" size:17];
self.detailTextLabel.text = _applicationObject.artistName;
self.detailTextLabel.font = [UIFont fontWithName:#"Helvetica" size:14];
_appIcon.image = nil;
NSString *appIconStoredPath = [appDelgate.saveAppIconURLAndPathInFile valueForKey:_applicationObject.iconURL];
_appIcon.image = [UIImage imageWithContentsOfFile:appIconStoredPath];
if(!_appIcon.image && appDelgate.hasInternetConnection)
{
[self downloadAppIconsInDirectory];
}
}
Its load previous image because your tableview reuse the cell so the imageview also reusing which hold the previous image. so you have to do nil this image

Related

Progress Bar updates out of synch with image downloads

I have an iPad app where I download a list of images and a progress bar updates as the images are downloaded. I can't seem to get the progress bar and image downloads to match up. The progress bar always finishes before the image downloads are completed. I have a method UpdateProgressBar that should increment the progress bar every time an image is downloaded.
-(void)DownloadPhoto{
NSMutableArray *failedDownloads = [[NSMutableArray alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
listPhoto = [CoreDataRead GetPhotoList:[self selectedAccountGuid]];
dispatch_group_t downloadGroup = dispatch_group_create();
for (Photo *item in listPhoto) {
NSString *imageName = item.photoName;
NSString *myURL = [NSString stringWithFormat:#"%#%#", #"http://acimagedownload.com/photos/", imageName];
NSURL *url = [NSURL URLWithString:myURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_group_enter(downloadGroup);
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError == nil && data != nil)
{
if (data != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:#"%#%#",item.guid, #".png"]];
[data writeToFile:path atomically:YES];
NSLog(#"Photo Downloaded %#!", #"");
}
else
{
NSLog(#"image is not downloaded");
}
}
else if (connectionError != nil)
{
[failedDownloads addObject:myURL];
NSLog(#"Error %#",[connectionError description]);
}
else
{
[failedDownloads addObject:myURL];
NSLog(#"Image Download Failed %#!", #"");
}
dispatch_group_leave(downloadGroup);
dispatch_async(dispatch_get_main_queue(), ^{
[self UpdateProgressBar];
});
}];
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self DownloadVideo];
});
});
}
-(void)UpdateProgressBar{
currentTask = currentTask + 1;
NSLog(#"Current Task %#!", [#(currentTask) stringValue]);
float progressPercentage = (float)currentTask/(float)taskCount;
[self.progressBar setProgress:progressPercentage animated:YES];
if(currentTask == taskCount){
[self ShowDoneAlert];
}
}
It's hard to tell without seeing the updateProgressBar method why you get that behaviour but one thing that I noticed is that your dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); is inside your loop.
So what happens is, you go into the first iteration of the loop and at the end of it, the thread execution stops and waits for the first dispatch_group_leave. After that is continues to the next iteration and so on.
If this is the desired behaviour (but I doubt it, I think you actually want your downloads to run in parallel) you should probably use a dispatch_semaphore_t or even a serial queue.

How to show GET request in Label

My get request works only in command line NSLog.
I need to show a data in Label, but it doesn't works.
-(void)getRequest{
NSURLSessionConfiguration *getConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *getSession = [NSURLSession sessionWithConfiguration: getConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
NSURL * getUrl = [NSURL URLWithString:#"http://localhost:3000/get"];
NSURLSessionDataTask * getDataTask = [getSession dataTaskWithURL:getUrl completionHandler:^(NSData *getData, NSURLResponse *getResponse, NSError *getError) {
if(getError == nil){
NSString * getString = [[NSString alloc] initWithData: getData encoding: NSUTF8StringEncoding];
[self.label setText:getString];// doesn't work!
NSLog(#"Data = %#",getString);}// it works!!
MainViewController*l=[[MainViewController alloc]init];
[l getRequest];
}
];
[getDataTask resume];
}
dataTaskWithURL is not working on the main-thread and that's necessary to update your UI.
if (getError == nil) {
NSString * getString = [[NSString alloc] initWithData: getData encoding: NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
[self.label setText: getString];
NSLog(#"Data = %#", getString);
});
}
This code will work fine for you.
You can also use:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.label setText:getString];
}];
Real more here Why should I choose GCD over NSOperation and blocks for high-level applications?
While I'm not quite sure what the usage would be here...you are using #getString, which I think is the issue. You probably want to do something like:
[self.label setText:[NSString stringWithFormat:"Data = %#", getString];
That should have the same behavior as NSLog.
dispatch_async(dispatch_get_main_queue(), ^{
[self.label setText:someString];
});

How can I get my asynchronous url images loaded into NSMutableArray in order?

I am trying to load images by their URL and store them in NSMutableArray in order. My current code works properly if I were not to care about storing the images in order, however it stores them not in order. It currently stores the images in the articleImage array based on the speed at which the asynchronous requests are completed. I have tried playing around with insertObject:AtIndex but could not get anything to work. To clarify, the NSMutableArray that I am trying to store the images in (in orderly fashion) is articleImage.
Here is some code from my viewDidLoad:
dispatch_async(dispatch_get_main_queue(), ^{
if(articleInfoJSONArray.count > 0)
{
for(int i=0; i<articleInfoJSONArray.count; i++)
{
[issueID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"issueID"]];
[articleID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleID"]];
NSString *imageLink = [[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleImage"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageLink]];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[articleImage addObject:image];
if(articleImage.count == articleInfoJSONArray.count)
[self imagesLoaded];
});
});
}
}
});
Here is my imagesLoaded:
- (void)imagesLoaded
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
ViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"MainView"];
[self presentViewController:vc animated:NO completion:nil];
}
Try to use dispatch_group. A dispatch group monitors work that has been added to it, and it will know when that work is done. :) http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/
One way i did an image download is with NSOperationQueue and NSOperation. You could define a NSOperationQueue in your header file:
#property (strong, nonatomic) NSOperationQueue *sequentialOperationQueue;
in your implementation do:
self.sequentialOperationQueue = [[NSOperationQueue alloc] init];
self.sequentialOperationQueue.maxConcurrentOperationCount = 1;
then you can add:
for (NSDictionary *imageDict in imagesToFetch) {
ImageDownloadOperation *imgDownloadOperation = [[ImageDownloadOperation alloc] initWithImageLocationDict:imageDict];
[self.sequentialOperationQueue addOperation:imgDownloadOperation];
}
LogoDownloadOperation is a subclass of NSOperation. this way you always have only one active download and process them in the order you want. For details on NSOperation check the apple doc.
in extract i did in ImageDownloadOperation:
- (void)start {
NSURL *imageUrl = [NSURL URLWithString:self.imageDict[#"imageUrl"]];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
NSURLSessionDownloadTask *downloadPhotoTask = [session
downloadTaskWithURL:imageUrl
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
self.sessionTask = nil;
[self done];
return;
}
NSData *imageData = [NSData dataWithContentsOfURL:location];
NSBlockOperation *saveImageBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
[SharedAppDelegate.entityManager saveImage:imageData
imageDict:self.imageDict
inManagedObjectContext:SharedAppDelegate.managedObjectContext];
}];
saveImageBlockOperation.qualityOfService = NSQualityOfServiceBackground;
[[NSOperationQueue mainQueue] addOperation:saveImageBlockOperation];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.sessionTask = nil;
[self done];
}];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
As you can see, i store the imageData via my AppDelegate in CoreData. Instead of my way, you could give the ImageDownloadOperation a pointer to your NSMutableArray, then you can store the data direct in your array.
You could make an array of [UIImage new] then once the task is complete
replace the empty image images[i] = newImage
EDIT
NSMutableArray *imageArray = [NSMutableArray new];
for (int i=0; i<articleInfoJSONArray.count; i++) {
[imageArray addObject:[UIImage new]];
}
for (int i=0; i<articleInfoJSONArray.count; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
//download image
imageArray[i] = downloadedImage;
});
}

UIImageView loading image very slowly

I have some code that gets an image from a web page and displays it in an ImageView. But the image loads very slowly for some reason I don't really understand! Through my logging I can see that all the data for the image (base64 string) arrives pretty instantly, yet it takes about 12 - 15 seconds for the image to appear in the ImageView.
I find this very strange because I used an NSStream to get the data for the image in a different method and the image loaded as soon as all the data arrived. But with this URLSession method its taking longer for the image to load. This doesn't really make sense! This method shouldn't affect how the ImageView loads that data.
Has anybody any ideas why this might be happening?
heres the code:
- (void)postMethod:(NSDictionary *)numDict
{
NSURL *url = [NSURL URLWithString:#"http://theWebAddress.com/aPage.php"]; // add url to page
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = #"POST";
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:numDict options:kNilOptions error:&error];
NSLog(#"%#", numDict);
if (!error)
{
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *diction = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (id key in diction)
{
if ([key isEqualToString:#"text"])
{
NSLog(#"data is text");
self.messageLabel.text = diction[#"text"];
break;
}
else if ([key isEqualToString:#"image"])
{
NSLog(#"data is an image");
// gets the base64 string pretty instantly but takes 12 - 15 seconds to pop up in the imageView
NSData *ImgData = [[NSData alloc] init];
ImgData = [[NSData alloc] initWithBase64EncodedString:diction[#"image"] options:1];
self.ImageView.image = [UIImage imageWithData:ImgData];
break;
}
}
}];
[uploadTask resume];
}
}
many thanks!
Your completion handler might be operating on a background thread. UI updates should always work on the main thread. Put a break point at
self.ImageView.image = [UIImage imageWithData:ImgData];
and see if it is on the main thread. If not, dispatch it to the main thread before you set the ImageView.image:
dispatch_async(dispatch_get_main_queue(), ^{
self.ImageView.image = [UIImage imageWithData:ImgData];
});
You can try to use SDWebImage https://github.com/rs/SDWebImage and all you need is to set the image in imageView like this:
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
You are firstly downloading image and then showing image.You can download image by using lazy loading.
For this you can use EgoImageView not uiimageview.
self.ImageView.imageURL=[NSURL URLWithString:
here self.ImageView is of egoimageview type.
you can get this class from github.
https://github.com/enormego/EGOImageLoading

objective c - Efficient way to download an image from web and use it to show in image view

in my app lets say there is 2 views ViewA and ViewB
in ViewA there are buttons for user to select option. And if he push one of them i will pull some images from web via web service and download them to the user's machine also i will put their paths to an array.
Then in ViewB i want to get images from that array and show them in image views
this is how i download images
-(void)startDownload
{
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:#"http://xxxx.com/Tulips.jpg"];
[arr addObject:#"http://xxxx.com/Koala.jpg"];
[arr addObject:#"http://xxxx.com/Penguins.jpg"];
for (int i=0; i<[arr count]; i++) //download array have url links
{
NSURL *URL = [NSURL URLWithString:[arr objectAtIndex:i]];
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc]initWithURL:URL];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if([data length] > 0 && [[NSString stringWithFormat:#"%#",error] isEqualToString:#"(null)"])
{
//make your image here from data.
UIImage *imag = [[UIImage alloc] initWithData:[NSData dataWithData:data]];
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [array objectAtIndex:0];
NSString *imgstr=[NSString stringWithFormat:#"%d",i];
NSString *pngfilepath = [NSString stringWithFormat:#"%#sample%#.jpg",docDir,imgstr];
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(imag)];
[data1 writeToFile:pngfilepath atomically:YES];
img = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:pngfilepath]];
NSLog(#"file is written");
}
else if ([data length] == 0 && [[NSString stringWithFormat:#"%#",error] isEqualToString:#"(null)"])
{
NSLog(#"No Data!");
}
else if (![[NSString stringWithFormat:#"%#",error] isEqualToString:#"(null)"]){
NSLog(#"Error = %#", error);
}
}];
}
}
when i run the app i see that file is written log is working so i think downloading the images is successful but i can't show image in imageview
you may think quiz up app on the store for understanding my problem clearly. quiz up first downloading questions' images then use them in another view. that's what i want exactly.
if my download code is correct how can i show them?
This code will allow you to download an image from the web, and does not require that the image be saved in the document directory:
NSMutableArray *arry = [[NSMutableArray alloc] init];
[arry addObject:#"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRr0WK-Q2t4Xxr1b6Kl7-lXdVEIh_Hj3HiDXk--Qg_0UAY0Y96P6w"];
[arry addObject:#"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRr0WK-Q2t4Xxr1b6Kl7-lXdVEIh_Hj3HiDXk--Qg_0UAY0Y96P6w"];
[arry addObject:#"https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRr0WK-Q2t4Xxr1b6Kl7-lXdVEIh_Hj3HiDXk--Qg_0UAY0Y96P6w"];
for (int i=0; i<[arry count]; i++) //download array have url links
{
NSString *string=[arry objectAtIndex:i];
NSURL *url=[NSURL URLWithString:string];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
UIImage *imagemain=[UIImage imageWithData:data];
// CGSize size=imagemain.size;
// UIImage *compimage=[appdel resizeImage:imagemain resizeSize:CGSizeMake(45,45)];
//
// Cell.imgProfile.image=compimage;
// // CGSize size1=compimage.size;
imageView.image=imagemain;
}];
}
Are you updating your UIImageView on the main thread, you can't update UI elements from a background thread. Try
dispatch_sync(dispatch_get_main_queue(),
^{
imageView.image = yourImage;
});
You have to use SDWebImage to cache the image. means the url will not be hit again and again.
#import "UIImageView+WebCache.h"
-(void)startDownload
{
NSMutableArray *arr = [[NSMutableArray alloc] init];
[arr addObject:#"http://xxxx.com/Tulips.jpg"];
[arr addObject:#"http://xxxx.com/Koala.jpg"];
[arr addObject:#"http://xxxx.com/Penguins.jpg"];
for (int i=0; i<[arry count]; i++) //download array have url links
{
NSString *string=[arry objectAtIndex:i];
NSURL *url=[NSURL URLWithString:string];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:url progress:^(NSUInteger receivedSize, long long expectedSize)
{
// progression tracking code
}completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// here you can setup imageView frames and set the image on imageView
imageView.image=image;
}
}];
}
}
}

Resources