Transfering code from cellForItemAtIndexPath to a CollectionViewCell (Parse Back-End) - ios

I'm using Parse as the database for my app. I want to create a CollectionViewCell and transfer my code there, instead of having it inside the View Controller's cellForItemAtIndexPath. How do I do this?
Thanks.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"productCell";
ProductCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
PFObject *product = [self.products objectAtIndex:indexPath.row];
NSString *price = [NSString stringWithFormat:#"$%#.00", product[#"price"]];
cell.price.text = price;
PFFile *userImageFile = product[#"firstThumbnailFile"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *thumbnailImage = [UIImage imageWithData:imageData];
UIImageView *thumbnailImageView = [[UIImageView alloc] initWithImage:thumbnailImage];
cell.image.image = thumbnailImageView.image;
}
}];
return cell;
}
Cell.h
#interface ProductCell : UICollectionViewCell
#property (nonatomic, weak) IBOutlet UIImageView *image;
#property (nonatomic, weak) IBOutlet UILabel *price;
#end

Remember that cellForIndexPath is called over and over as cells scroll into view. So it's bad practice to make unguarded network requests in that method.
If you want to fetch the images lazily, add logic that caches the retrieved results, and only fetch images that haven't been fetched before...
// in private interface
#property(strong,nonatomic) NSMutableDictionary *imageForProduct;
// in init
self.imageForProduct = [#{} mutableCopy];
A method to fetch an image...
- (void)imageForProduct:(PFObject *)product completion:(void (^)(UIImage *))completion {
PFFile *userImageFile = product[#"firstThumbnailFile"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
UIImage *image;
if (!error) {
image = [UIImage imageWithData:imageData];
}
completion(image);
}];
}
Now, in cellForIndexPath, we can't count on the state of the collection being the same by the time the image arrives, so rather than retaining manipulating the cell in the completion block, just reload the index path...
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"productCell";
ProductCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
PFObject *product = [self.products objectAtIndex:indexPath.row];
NSString *price = [NSString stringWithFormat:#"$%#.00", product[#"price"]];
cell.price.text = price;
if (self.imageForProduct[product.objectId]) {
cell.image = self.imageForProduct[product.objectId];
} else {
cell.image = // optionally put a placeholder image here
[self imageForProduct:product completion:^(UIImage *)image {
self.imageForProduct[product.objectId] = image;
[collectionView reloadItemsAtIndexPaths:#[indexPath]];
}];
}
return cell;
}

create a method in your custom cell which is exposed in your .h file.
This method should receive an argument of type PFObject.
Then in you cellForItemAtIndexPath, call that method and pass your object in that method.
And in the implementation of that method, extract the details from your object and assign them to respective properties.

Related

Loading images asynchronously, wrong image in cell

I have a UICollectionView in which I am loading multiple images into. From what Ive been reading, in order to match the correct image to each cell I need to subclass UIImageView and get the image there. Because every time I collectionView reloadData, some images duplicate and they are all out of order. But I am unsure how to do this and haven't found any tutorials. I am using Parse for a database.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[albumImageCell alloc]init];
}
PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
PFFile *file = [temp objectForKey:#"imageThumbnail"];
if (![cell.hasImage isEqualToString:#"YES"]) {
dispatch_async(imageQueue, ^{
NSData *data = [file getData];
if (data) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^(void){
cell.imageView.image = image;
cell.hasImage = #"YES";
});
}
});
}
return cell;
}
One way to solve this is to re-query the collection view for the cell again once you're back on the main queue. This code should work:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[albumImageCell alloc]init];
}
PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
PFFile *file = [temp objectForKey:#"imageThumbnail"];
if (![cell.hasImage isEqualToString:#"YES"]) {
dispatch_async(imageQueue, ^{
NSData *data = [file getData];
if (data) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^(void){
// cellAgain will be the actual cell at that index path, if it is visible.
// If it is not visible, cellAgain will be nil.
albumImageCell *cellAgain = [collectionView cellForItemAtIndexPath:indexPath];
cellAgain.imageView.image = image;
cellAgain.hasImage = #"YES";
});
}
});
}
return cell;
}
I made a small 'tutorial' in answer to a this question. Although the question refers to Core Data, my answer applies to any data source so you should be able to fit it around your use case.
One thing you want to watch out for is the inner block, when you get back onto the main queue. Given that you have no idea how long it takes to get to that point, the cell may no longer be relevant to that image (could have been reused), so you need to do a couple of additional checks...
(a) is the image still required?
if ([[tableView indexPathsForVisibleRows] containsObject:indexPath])
(b) is that cell is the correct cell for the image?
UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath];
Although this tutorial is still valid, I tend to abstract things further these days. As the viewController has to deal with thread-unsafe entities like UIKit and Core Data, it is a good idea to keep all viewController code on the main thread. Background queue abstractions should take place at a lower level, preferably in the model code.
What I ended up doing was subclassing UIImaveView and then passing the image file in cellForRow
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
albumImageCell *cell = (albumImageCell *) [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[albumImageCell alloc]init];
}
PFObject *temp = [_dataArray objectAtIndex:indexPath.row];
PFFile *file = [temp objectForKey:#"imageThumbnail"];
[cell.imageView setFile:file];
return cell;
}
And then in the customImageView -
- (void) setFile:(PFFile *)file {
NSString *requestURL = file.url; // Save copy of url locally (will not change in block)
[self setUrl:file.url]; // Save copy of url on the instance
self.image = nil;
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:data];
if ([requestURL isEqualToString:self.url]) {
[self setImage:image];
[self setNeedsDisplay];
}
} else {
NSLog(#"Error on fetching file");
}
}];
}
But this gets Data every time the user scrolls to a new cell. So Im still trying to figure out how to match a particular image to a cell, without getting data every time.

UICollectionViewController reloads images on scroll

I am using the ParseFramework to get some images and display them in a collectionViewController. The thing is, if I scroll before all images are loaded, I experience duplication of images, so I find the same image in different cells. Here’s the code snippets that could be useful for you to be able to help me.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ExampleCell *cell = (ExampleCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"imageCell" forIndexPath:indexPath];
PFObject * imageObject = [self.imageFilesArray objectAtIndex:indexPath.row];
PFFile * imageFile=[imageObject objectForKey:#"image"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if(!error){
cell.parseImage.image=[UIImage imageWithData:data];
}
}];
return cell;
}
And the method that’s performing the query:
-(void) queryParseMethod{
PFQuery *query = [PFQuery queryWithClassName:#"Allimages"];
[query findObjectsInBackgroundWithBlock:^(NSArray * objects, NSError *error) {
if(!error){
self.imageFilesArray = [[NSArray alloc] initWithArray:objects];
[self.collectionView reloadData];
}
}];
}
This is in ExampleCell.h
#property (weak, nonatomic) IBOutlet UIImageView *parseImage;
This is in ExampleCell.m
#synthesize parseImage;
In the storyboard I am setting the identifier of the cell to imageCell.
I considered the answer below so I modified my method:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ExampleCell *cell = (ExampleCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"imageCell" forIndexPath:indexPath];
if(cell==nil) // no queued cell to dequeue
{
cell = (ExampleCell *)[[UICollectionViewCell alloc] initWithFrame:CGRectMake(0,0,50,50)];
}
// clear any previous image if necessary
cell.parseImage.image = nil;
PFObject * imageObject = [self.imageFilesArray objectAtIndex:indexPath.row];
PFFile * imageFile=[imageObject objectForKey:#"image"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error)
{
if(!error)
cell.parseImage.image=[UIImage imageWithData:data];
}];
return cell;
}
But I still have the same issue. What did I do wrong?
I think you have duplication of images because some cells are reused.
When a cell is no more visible, it is put in a queue where it can be reused for new cell to be displayed. The cell still keep a reference to its image.
So when you dequeue a cell, you should start by clearing its previous image:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ExampleCell *cell = (ExampleCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"imageCell" forIndexPath:indexPath];
if( !cell ) // no queued cell to dequeue
{
// create a new cell to use
}
// clear any previous image if necessary
cell.parseImage.image = nil;
PFObject * imageObject = [self.imageFilesArray objectAtIndex:indexPath.row];
PFFile * imageFile=[imageObject objectForKey:#"image"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error)
{
if(!error)
cell.parseImage.image=[UIImage imageWithData:data];
}];
return cell;
}
So after a lot of investigation I found out the the problem resides in the getDataInBackground..So since getData was working fine for me in terms of not confusing the images with their indexes, I decided to use both getData and getDataInBackground to reach my purpose. I used getDataInBackground to bring the data from PFFile but without displaying it in the cell. When the data is available I will retrieve it using getData.
Here’s the code inside cellForItemAtIndexPath:
//PFCell is the same as ExampleCell previously but of type PFCollectionViewCell
PFCell * cell = (PFCell*) [collectionView dequeueReusableCellWithReuseIdentifier:#"imageCell" forIndexPath:indexPath];
PFObject * imageObject = [self.imageFilesArray objectAtIndex:indexPath.row];
PFFile * imageFile=[imageObject objectForKey:#"image"];
cell.parseImage.image=nil;
//Getting the data from the PFFile placing it in memory
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error)
{}];
dispatch_async(dispatch_get_main_queue(), ^{
//If the data is now available in memory
if(imageFile.isDataAvailable){
cell.parseImage.image=[UIImage imageWithData:[imageFile getData]];
cell.activityIndicator.hidden=YES;
}
//I added an activity Indicator in case there’s no image yet
else{
[cell.activityIndicator startAnimating];
cell.activityIndicator.hidden=NO;
}
});
return cell;
Now sure there’s a drawback for this. The images will not load unless the user is scrolling so the method will be called again and so the getData.
so I had to add a timer in ViewDidLoad:
[NSTimer scheduledTimerWithTimeInterval:5.0f
target:self selector:#selector(reloadData) userInfo:nil repeats:YES];
And the function:
-(void) reloadData{
NSArray * cellsToReload=[[NSArray alloc] init];
NSMutableArray * temp=[[NSMutableArray alloc] init];
for(int i=0;i<[[self.collectionView visibleCells] count];i++){
PFCell * cell=(PFCell*)[self.collectionView visibleCells][i];
if(cell.parseImage.image==nil){
[temp addObject:[self.collectionView indexPathForCell:cell]];
}
}
cellsToReload=[temp copy];
[self.collectionView reloadItemsAtIndexPaths:cellsToReload];
}
I had to do all this because a simple [self.collectionView reloadData] would flicker the view each 5 seconds.

Passing data from collection view to detail view

I need some help figuring out how to pass data from my collection view to the detail view controller. I am starting by simply passing the image through. My code is below:
ViewController:
-(MyCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CELL" forIndexPath:indexPath];
PFObject *imageObject = [imageFilesArray objectAtIndex:indexPath.row];
__block UIImage *MyPicture = [[UIImage alloc]init];
PFFile *imageFile = [imageObject objectForKey:#"test"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
MyPicture = [UIImage imageWithData:data];
cell.CollectionImg.image = [UIImage imageWithData:data];
cell.cellLabel.text = [object objectForKey:#"phone"];
}
}];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
TopDetailViewController *detailVC = [[TopDetailViewController alloc] initWithNibName:#"TopDetailViewController" bundle:[NSBundle mainBundle]];
detailVC.img= [imageFilesArray objectAtIndex: indexPath.row];
[self.navigationController pushViewController:detailVC animated:YES];
}
And the Detail view:
.h
#property (nonatomic, strong) IBOutlet UIImageView *imageView;
#property (nonatomic, strong) IBOutlet NSString *img;
.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageView.image = [UIImage imageNamed:self.img];
// Do any additional setup after loading the view from its nib.
}
If someone could help me out I would greatly appreciate.
You can only use [UIImage imageNamed:] to retrieve images that are in your app bundle. It looks like you create these images at run-time based on the PDF file, so just passing in the string to the detail view and loading it with [UIImage imageNamed:] won't work. You should pass the reference to the image or the PFFile object so that the detail view can generate the image itself.

Using Grand Central Dispatch to convert URL string from JSON into image for ImageView

Among other data (specifically strings), I've got a URL being pulled from JSON and saved in the array "jsonArray". I need the the URL (which is for an image dependent on the user signed in) to be converted into an actual image ready to be displayed in my imageview "imageProfPic". I'm not that familiar with GCD, so I'd greatly appreciate any and all help with my code and getting my image successfully displayed in imageProfPic.
(EDIT: Forgot to mention that I'm getting the error "_NSCFString isFileURL")
TableViewController.m file
NSURL *myURL = [[NSURL alloc]initWithString:#"http://domain.com/json2.php"];
NSData *myData = [[NSData alloc]initWithContentsOfURL:myURL];
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:&error];
[tableView reloadData]; // if tableView is unidentified make the tableView IBOutlet
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return jsonArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NeedCardTableViewCell *cell = (NeedCardTableViewCell *) [tableView dequeueReusableCellWithIdentifier:#"needCard" forIndexPath:indexPath];
NSDictionary *needs = jsonArray[indexPath.row]; // get the data dict for the row
cell.textNeedTitle.text = [needs objectForKey: #"needTitle"];
cell.textNeedPoster.text = [needs objectForKey: #"needPoster"];
cell.textNeedDescrip.text = [needs objectForKey: #"needDescrip"];
dispatch_async(dispatch_queue_create("imageQueue", NULL), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:needs[#"userImage"]]];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imageProfPic setImage:image];
});
});
return cell;
TableViewController.h file
#interface NeedCardTableViewCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *textNeedTitle;
#property (weak, nonatomic) IBOutlet UILabel *textNeedPoster;
#property (weak, nonatomic) IBOutlet UILabel *textNeedDescrip;
#property (weak, nonatomic) IBOutlet UIImageView *imageProfPic;
#property (strong, nonatomic) IBOutlet UITableView *tableView;
I think you just need to create a NSURL with your string and you should be good. Give this a go:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSURL *imageURL = [NSURL URLWithString:needs[#"userImage"]];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imageProfPic setImage:image];
});
});
If what you mean is displaying the image from a JSON, I used SDWebImage and it easily works with me.
Using UIImageView+WebCache category with UITableView
Just #import the UIImageView+WebCache.h header, and call the setImageWithURL:placeholderImage: method from the tableView:cellForRowAtIndexPath: UITableViewDataSource method. Everything will be handled for you, from async downloads to caching management.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
Using blocks
With blocks, you can be notified about the image download progress and whenever the image retrival has completed with success or not:
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {... completion code here ...}];

ios: load new data into UICollectionView

I want to make a application which will display images into UICollectionView.
Images will be downloaded from server and then shows into collectionView.
I am using custom collectionView layout into xib file.
At a time, 20 images is receiving from server.
Problem: I can't show newly downloaded images into collectionView.
Here is my code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
BOOL reloaded = NO;
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
dispatch_queue_t queue = dispatch_queue_create("com.justTest.anotherSingleApplication", NULL);
dispatch_async(queue, ^{
//code to be executed in the background
NSString *imageName1 = [[NSString alloc]initWithFormat:#"http://www.abc.com/images/thumb/%#", cellData];
NSString *url_Img1 = imageName1;
UIImage *aImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url_Img1]]];
dispatch_async(dispatch_get_main_queue(), ^{
//code to be executed on the main thread when background task is finished
[cell.cellImage setImage:aImage];
});
});
if (indexPath.row == self.imageArray.count - 1 && !reloaded) {
getOnScrollImages *getImage = [[getOnScrollImages alloc] init]; // class to get image name from server
NSMutableArray *astring = (NSMutableArray *)[getImage getImageNameFromServer:#"list" board:#"111" pin:#"122345"]; // method to get image name from server
[self setNewTestArray:astring]; //adding newly downloaded image name into array
reloaded = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
return cell;
}
Any suggestion please?
NOTE: I am just starting developing iOS application, this may be a very silly question.
Use asynchronously fetch to get data from server and display it in collectionView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
YourDataModel *model = self.dataArray[indexPath.row];
YourCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
if ([self checkWhetherImageAlreadyExist]) {
[cell.imageView setImage:model.image];
} else {
//show placeholder to avoid nothing in your UI, or your user gets confused
[cell.imageView setImage:placeholderImage];
[self startDownloadImageForIndexPath:indexPath];
}
}
- (void)startDownloadImageForIndexPath:(NSIndexPath *)indexPath
{
//YourImageDownloader is a class to fetch data from server
//imageDownloadsInProgress is a NSMutableDictionary to record the download process, which can avoid repeat download
YourImageDownloader *downloader = [self.imageDownloadsInProgress objectForKey:indexPath];
if (downloader == nil) {
YourDataModel *model = self.dataArray[indexPath.row];
//configure downloader
downloader = [[YourImageDownloader alloc] init];
[downloader setURL:model.url];
[downloader setCompletionHandler:^{
//download the image to local, or you can pass the image to the block
model.image = [UIImage imageWithContentsOfFile:model.localPath];
YourCell *cell = [self.mCollectionView cellForItemAtIndexPath:indexPath];
[cell.imageView setImage:model.image];
//remove downloader from dictionary
[self.imageDownloadsInProgress removeObjectForKey:indexPath];
}];
//add downloader to dictionary
[self.imageDownloadsInProgress setObject:downloader forKey:indexPath];
//start download
[downloader startDownload];
}
}
Use a class to download the image. If you have many images in one collection view, you may consider to save these images to local in case of memory warning. if now many, just leave the image in memory and display it in your collection view.
the code followed is save the image to local and read image data from local when displaying.
in .h:
#import <Foundation/Foundation.h>
#interface PortraitDownloader : NSObject
#property (nonatomic, copy) NSString *portraitName;
#property (nonatomic, copy) void (^completionHandler)(void);
- (void)startDownload;
- (void)cancelDownload;
#end
in .m
#import "PortraitDownloader.h"
#import <CFNetwork/CFNetwork.h>
#import "NSString+ImagePath.h" // it's a category to get the image local path
#interface PortraitDownloader ()
#property (nonatomic, strong) NSMutableData *activeDownload;
#property (nonatomic, strong) NSURLConnection *portraitConnection;
#end
#implementation PortraitDownloader
- (void)startDownload
{
self.activeDownload = [NSMutableData data];
NSString *urlstr = [NSString serverPortraitPathWithPortrait:self.portraitName];
NSURL *url = [NSURL URLWithString:urlstr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.portraitConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)cancelDownload
{
[self.portraitConnection cancel];
self.portraitConnection = nil;
self.activeDownload = nil;
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.activeDownload appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// Clear the activeDownload property to allow later attempts
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// save to local path
NSString *localSavePath = [NSString localPortraitPathWithPortrait:self.portraitName];
[self.activeDownload writeToFile:localSavePath atomically:YES];
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
// call our delegate and tell it that our icon is ready for display
if (self.completionHandler) {
self.completionHandler();
}
}
#end
if you want to leave your image in-memory, just modify the completion block as:
in .h
typedef void (^Completion_handle) (UIImage *image);
#interface PortraitDownloader : NSObject
#property (nonatomic, copy) Completion_handle myCompletionBlock;
and in .m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// get image from data
UIImage *image = [UIImage imageWithData:self.activeDownload];
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
// call our delegate and tell it that our icon is ready for display
if (self.myCompletionBlock) {
self.myCompletionBlock(image);
}
}
and also modify methods startDownloadImageForIndexPath, save the image to your model to retain it
This method expects to have answers immediately:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
when your code doesn't respond fast enough to it, the app will usually display nothing, or sometimes just crash (depending on what you've setup)
A common design pattern is to store the info that will be supplied to the collectionView in a class variable (it doesn't have to be a property, but it often times is). You always store SOMETHING in that variable, even if it is old or stale data.
Then you have the methods defined in the UICollectionViewDataSource protocol pull what they need directly from the class variables, with no delay.
Other methods can fetch and retrieve and sling updated data around, and once they finish you call reloadData: on the collectionView to update the interface.
assuming the asynchronous calls you are using are successfully retrieving data eventually, they are probably too slow for what the UICollectionViewDataSource protocol methods are expecting.
A suggestion for how to get started would be to move the code fetching your data to separate methods, and then stage the data in a class variable or two which the collectionView can reliably draw from.
You can try it with static data loaded into the bundle at first if you need, and then move into asynchronous pulls from the web too.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
UIImageView *imgView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"profile_pic.png"]];
NSMutableDictionary *contactData=[NSMutableDictionary new];
contactData = [self.collectionData objectAtIndex:indexPath.row];
imgView.image=[contactData objectForKey:#"image"];
[cell addSubview:imgView];
return cell;
}

Resources