CollectionView NSURL Session Speed and Reloading - ios

I'm fairly new to iOS and developing weather App in when I download hourly forecast along with icon of conditions. I have been able to implement UICollection with NSURL Connection. However, I'm having issues regarding speed/performance issues regarding NSURL Session. Here are the two issues:
1) the speed of downloading and presenting the downloaded icons is very slow (and there are extremely small images). This download process can take anywhere from 5-10 seconds.
2) when I put a button to reset the collection, all data is reset, however the existing images remain until the new images are downloaded. Again this can take anywhere from 5-10 seconds.
Here is my code:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.hours.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"ConditionsCell";
ConditionsCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.conditionsTime.text = [self.hours objectAtIndex:indexPath.row];
cell.conditionsTemp.text = [NSString stringWithFormat:#"%#°", [self.hoursTemp objectAtIndex:indexPath.row]];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:[self.hoursIcons objectAtIndex:indexPath.row]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
UIImage * serverImage = [UIImage imageWithData: data];
cell.conditionsImage.image = serverImage;
}];
[dataTask resume];
return cell;
}
And here is the IBAction for the button and to reload the CollectionView:
- (IBAction)selectDay:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == 0)
{
self.todayOrTomorrow = #"today";
}
else if (sender.selectedSegmentIndex == 1)
{
self.todayOrTomorrow = #"tomorrow";
}
self.hours = [self hours];
self.hoursIcons = [self hoursIcons];
self.hoursTemp = [self hoursTemp];
[_collectionViewHours reloadData];
}

You are downloading data in background that, but not updating UI on main thread,try below pattern it willlhelp you
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"ConditionsCell";
ConditionsCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.conditionsTime.text = [self.hours objectAtIndex:indexPath.row];
cell.conditionsTemp.text = [NSString stringWithFormat:#"%#°", [self.hoursTemp objectAtIndex:indexPath.row]];
cell.conditionsImage.image = [UIImage imageNamed:""];//reseting image
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString: [self.hoursIcons objectAtIndex:indexPath.row]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
#autoreleasepool {//autorelease pool for memory release
if (!error) {
//UIImage * serverImage = [UIImage imageWithData: data];//comment this extra variable and can increase memory overhead.
dispatch_async(dispatch_get_main_queue(), ^{
cell.conditionsImage.image = [UIImage imageWithData: data];//update UI
});
}}//autorelease pool
}];
[dataTask resume];
return cell;
}
it will definetly help you for your first part.
Thanks.

Related

How to refresh UICollectionViewCell's image while bouncing?

My image view inside my cell is not refreshing when the collection view is bouncing. I mean the image is being fetched over the network and is sometimes received during bouncing. If the reception completes during bouncing, there is no image refresh. So this basically happens to the last row cells.
Here's my code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"myIdentifier" forIndexPath:indexPath];
//[...]
[cell.imageView setImage:nil];
if(currentProduct.imageUrl){
if(currentProduct.image){
[cell.imageView setImage:[UIImage imageWithData:currentProduct.image]];
}else{
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:currentProduct.imageUrl]];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:urlRequest uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if(responseObject){
[[MainController sharedController] updateProduct:currentProduct withImage:responseObject];
dispatch_async(dispatch_get_main_queue(), ^{
UICollectionViewCell *updateCell = [collectionView cellForItemAtIndexPath:indexPath];
if(updateCell){
[collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
}
});
}
}];
[dataTask resume];
}
}
return cell;
}
The reloadItemsAtIndexPaths seems not to be called (or the collection view doesn't care).
Is there a way to ensure refreshing while bouncing? The only solution I came with is:
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{
MyCell *myCell = (MyCell *)cell;
Product *currentProduct = [self.category.products objectAtIndex:indexPath.row];
if(!myCell.imageView.image && currentProduct.image){
[collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
}
}
Edit: By bouncing I mean that scrolling is not brutally stopped when reaching the edges:

dispatch_async(dispatch_get_main_queue() is not working on iPad, but working on iPhone

I am using SDWebImage library and its working on iPhone. But I don't know why this is not called in iPad. I tried to put break points, but it doesn't hit the break point either. I put this method in cellForItemAtIndexPath.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier = #"CustomCollectionViewCell";
CustomCollectionViewCell* cell = (CustomCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
[cell.downloadButton setTag:indexPath.row];
[cell.downloadButton addTarget:self action:#selector(deleteButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
catalogModel = [catalogArray objectAtIndex:indexPath.item];
cell.cellDescription.text = catalogModel.catalogName;
SDWebImageManager *manager = [SDWebImageManager sharedManager];
NSString *urlStr = catalogModel.imageNameThumbnail;
NSURL *url = [NSURL URLWithString:urlStr];
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.catalogCollectionView.indexPathsForVisibleItems containsObject:indexPath])
{
catalogModel = [catalogArray objectAtIndex:indexPath.item];
[manager downloadImageWithURL:[NSURL URLWithString:catalogModel.imageNameThumbnail] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {}completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL)
{
cell.cellImageView.image = image;
NSLog(#"%#",catalogModel.catalogName);
}];
};
});
return cell;
}
There is a problem in concurrent image loading implementation. When -collectionView: cellForItemAtIndexPath: is calling, the device executes code on the main queue. Assuming that -downloadImageWithURL:options:progress:completed: method performs image loading on the background thread and returns instantly we can call it without dispatch_async(dispatch_get_main_queue()... wrapping. Otherwise we cannot guarantee that the completion handler is executing on the main thread, so the code should look like this
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier = #"CustomCollectionViewCell";
CustomCollectionViewCell* cell = (CustomCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
...
SDWebImageManager *manager = [SDWebImageManager sharedManager];
// methods with completion block usually return instantly
[manager downloadImageWithURL:aURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize){} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// ensure ui is updating on main thread and for visible cell
dispatch_async(dispatch_get_main_queue(), ^{
if ([collectionView.indexPathsForVisibleItems containsObject:indexPath]) {
cell.cellImageView.image = image;
}
});
}];
return cell;
}
And the different results on iPhone and iPad can be explained by the architectural differences in technical specifications of testing devices.

Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling?

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ static NSString *CellIdentifier = #"cell";
TVcell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil)
cell = [[TVcell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.txtlabel.text = [[[arrayData objectAtIndex:indexPath.row] valueForKey:#"description"]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
cell.IMGLabel.contentMode = UIViewContentModeScaleAspectFill;
cell.IMGLabel.image = nil;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#",[enclosureUrlArray objectAtIndex:indexPath.row]]];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
cell.IMGLabel.image = image;
[cell.IMGLabel setImage:image];
[cell.IMGLabel.layer setMasksToBounds:YES];
[cell.IMGLabel.layer setCornerRadius:2.5f];
[cell setNeedsLayout];
});
}
}
}];
[task resume];
Your problem is that you have no guarantee that your NSUrlRequests will terminate in the same order than they started. This is bad because your cells are re-used for better performance and it can end with strange behavior.
You can find a fix here: Asynchronous downloading of images for UITableView with GCD
Or you can use tools listed here to help you address this issue: https://stackoverflow.com/a/32601838/3769338

First image in my array is not displayed in collectionview cell

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
cell =[self.imgCllvw dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
if(!cell)
{
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
}
NSDictionary *tmpDict = [images objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:[tmpDict objectForKey:#"img"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *imge= [[UIImage alloc]initWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = imge;
});
});
cell.layer.borderWidth=1.0f;
cell.layer.borderColor=[UIColor colorWithRed:215.0/255.0 green:214.0/255.0 blue:214.0/255.0 alpha:1.0].CGColor;
return cell;
}
First image is not loaded and if I scroll the collectionview images are displayed and I have used autolayout for collectionview
I think you should try async downloading of image rather than sync, something like this
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
cell =[self.imgCllvw dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
if(!cell)
{
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
}
NSDictionary *tmpDict = [images objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:[tmpDict objectForKey:#"img"]];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
MyCell *cell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (cell)
cell.imageView.image = image;
});
}
}
}];
[task resume];
cell.layer.borderWidth=1.0f;
cell.layer.borderColor=[UIColor colorWithRed:215.0/255.0 green:214.0/255.0 blue:214.0/255.0 alpha:1.0].CGColor;
return cell;
}
Instead of dispatch_async, mainqueue you can use dispatch_sync, like this,
dispatch_sync(dispatch_get_main_queue(), ^{
cell.imageView.image = imge;
});

UICollectionView freezes iOS app

Good morning,
I'm using UICollectionView for the first time to show images from a user (like a Facebook profile) and at the moment I can show the images fine but I have some problems:
1- When I visit my profile the app freezes for like 2-3 minutes due to the load of 5 images.
2- When I'm moving through the UICollectionView it freezes when the app load again the images outside the screen.
What I have to do in order to not to freeze the app when loading the user pictures? And what I have to do to navigate through the CollectionView without freezing? Maybe a cache system is what I need?
That's my code:
ProfileViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self.view setBackgroundColor: [self colorWithHexString:#"FFFFFF"]];
self.profileimage.layer.cornerRadius = self.profileimage.frame.size.width / 2;
self.profileimage.clipsToBounds = YES;
self.profileimage.layer.borderWidth = 1.0f;
self.profileimage.layer.borderColor = [UIColor whiteColor].CGColor;
[self fetchJson];
[self fetchImages];
self.oneCollectionView.dataSource = self;
self.oneCollectionView.delegate = self;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 1;
}
-(NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _carImages.count;
}
// COLLECTION VIEW
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCollectionViewCell *myCell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"MyCell"
forIndexPath:indexPath];
NSString *data = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"imagen"];
NSURL * imageURL = [NSURL URLWithString:data];
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage * images = [UIImage imageWithData:imageData];
myCell.imageview.image = images;
return myCell;
}
-(void)fetchImages {
self.carImages = [[NSMutableArray alloc] init];
NSString *usersPassword = [SSKeychain passwordForService:#"login" account:#"account"];
NSString * urlString = [NSString stringWithFormat:#"http://mywebsite.com/posts.php?usersPassword=%#",usersPassword];
NSURL * url = [NSURL URLWithString:urlString];
NSData * data = [NSData dataWithContentsOfURL:url];
NSError *error;
[_jsonArray removeAllObjects];
_jsonArray = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
for(int i=0;i<_jsonArray.count;i++)
{
NSDictionary * jsonObject = [_jsonArray objectAtIndex:i];
NSString* imagen = [jsonObject objectForKey:#"imagen"];
[_carImages addObject:imagen];
}
}
Thanks in advance.
Import UIImageView+AFNetworking.h
and load your image via this method in cellForItemAtIndexPath method
[imageView setImageWithURL:[NSURL URLWithString:#"https://lh6.googleusercontent.com/-B8kSXtoaQDo/VGTVlXyIXpI/AAAAAAAAJ_M/USh6SgvMemw/w1024-h1024/IMG_20141112_103152.jpg"] placeholderImage:[UIImage imageNamed:#"placeholder-avatar"]];
it will surely speed up to load and scrolling collectionView
Download the images asynchronously, dataWithContentsOfURL is synchronous method and it will block your current thread until the download completes. You can use libraries like SDWebImage to automatically handle downloading for you or You can use NSURLSessionDownloadTask to download Images.
- (void)fetchImages {
self.carImages = [[NSMutableArray alloc] init];
NSString *usersPassword = [SSKeychain passwordForService:#"login" account:#"account"];
NSString * urlString = [NSString stringWithFormat:#"http://mywebsite.com/posts.php?usersPassword=%#",usersPassword];
NSURL * url = [NSURL URLWithString:urlString];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
[self.jsonArray removeAllObjects];
self.jsonArray = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
for(int i=0;i<_jsonArray.count;i++)
{
NSDictionary * jsonObject = self.jsonArray[i];
NSString* imagen = jsonObject[#"imagen"];
[self.carImages addObject:imagen];
}
}
}];
[dataTask resume];
}
// COLLECTION VIEW
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCollectionViewCell *myCell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"MyCell"
forIndexPath:indexPath];
NSString *data = [[self.jsonArray objectAtIndex:indexPath.row] valueForKey:#"imagen"];
NSURL * imageURL = [NSURL URLWithString:data];
NSURLSessionDownloadTask *imageDownloadTask = [[NSURLSession sharedSession]
downloadTaskWithURL:imageURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
UIImage *image = [UIImage imageWithData:
[NSData dataWithContentsOfURL:location]];
myCell.imageview.image = image;
}];
[imageDownloadTask resume];
return myCell;
}
You can use the dispatcher to create an async operation for the download of the images. This will resolve the 2 problems you have:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imgData = [NSData dataWithContentsOfURL:YOUR_IMAGE_URL];
UIImage *img = [UIImage imageWithData:imgData];
[YOUR_IMAGE_VIEW_OUTLET performSelectorOnMainThread:#selector(setImage:) withObject:img waitUntilDone:YES];
});
These are the snippet you have to change:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCollectionViewCell *myCell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"MyCell"
forIndexPath:indexPath];
NSString *data = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"imagen"];
NSURL * imageURL = [NSURL URLWithString:data];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *img = [UIImage imageWithData:imageData];
[myCell.imageview performSelectorOnMainThread:#selector(setImage:) withObject:img waitUntilDone:YES];
});
return myCell;
}
Try to Register Nib For Collection View
Write following code in your viewController's viewDidLoad()method :
UINib *nib = [UINib nibWithNibName:#"MyCollectionCell" bundle: nil];
[self.collectionView registerNib:nib forCellWithReuseIdentifier:#"Cell"];
And I think you have to use https://github.com/nicklockwood/AsyncImageView for the image loading in collection view.
For Storyboards you have to see this tutorial : http://www.appcoda.com/ios-programming-uicollectionview-tutorial/ This will help you more.
Thanks!
For the first question the answer is in this line of code:
NSData * data = [NSData dataWithContentsOfURL:url];
From Apple Reference:
Do not use this synchronous method to request network-based URLs. For
network-based URLs, this method can block the current thread for tens
of seconds on a slow network, resulting in a poor user experience, and
in iOS, may cause your app to be terminated.
As alternative you can use NSURLSessionDataTask to download data (see Apple Reference)
-Edit
In ProfileViewController.h add these two properties:
#property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig;
#property (nonatomic, strong) NSURLSession *session;
then, in - viewDidLoad initialise them:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view
self.sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfig];
//Other stuff...
}
Finally, in ProfileViewController.m
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCollectionViewCell *myCell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"MyCell"
forIndexPath:indexPath];
NSString *data = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"imagen"];
NSURL * imageURL = [NSURL URLWithString:data];
NSURLSessionDownloadTask *imageDownloadTask = [self.session dataTaskWithURL:imageURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"ERROR: %#", error);
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
UIImage *image = [UIImage imageWithData:data];
myCell.imageview.alpha = 0.0f;
myCell.imageview.image = image;
[UIView animateWithDuration:0.45 animations:^{
myCell.imageview.alpha = 1.0f;
});
} else {
NSLog(#"Couldn't load image at URL: %#", imageURL);
NSLog(#"HTTP %d", (int)httpResponse.statusCode);
}
}
}];
[imageDownloadTask resume];
return myCell;
}
I hope this can help you.
- Edit 2
For future readers, I slightly refactored my code based on #suhit's answer (+1 for him)

Resources