Objective C model - How to download image inside a model class? - objective-c

I have the following model of a box, it is meant to download images in a background thread and create an image from this downloaded image.
The viewcontroller has a custom uicollectioncell, but its just a uiimageview; nothing too complex.
In the cellForItemAtIndexPath I want to assign the cell's imageview using the model's downloaded image.
However, its not quite working;
The image never appears
If I move the background image downloader to the cellForItemAtIndexPath and change a few items then the image loads fine.
But what I'm wanting is to seperate the ViewContoller and the model; the model should do the heavy lifting and the viewcontroller simply handles the display.
Code follows
// ViewController: View Did Load
- (void)viewDidLoad {
[super viewDidLoad];
if (!self.picturesArray) self.picturesArray = [NSMutableArray arrayWithCapacity:kNumberOfCells];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
self.collectionView.backgroundColor = [UIColor clearColor];
for (int i=0; i<kNumberOfCells; i++)
{
Box *box = [[Box alloc] init];
[self.picturesArray addObject:box];
box = nil;
}
}
// ViewController : collectionView delegage
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCollectionViewCell *cell = (MyCustomCollectionViewCell *) [collectionView dequeueReusableCellWithReuseIdentifier:cellId forIndexPath:indexPath];
Box *box = self.picturesArray[indexPath.row];
cell.backgroundColor = box.bgColor;
cell.imageView.image = box.image;
return cell;
}
The box model is as follows
// Box model with an image property
- (instancetype)init
{
self = [super init];
if (self)
{
NSURL *url = [NSURL URLWithString:kUrlString];
// Block variable to be assigned in block.
__block NSData *imageData;
dispatch_queue_t backgroundQueue = dispatch_queue_create("imagegrabber.bgqueue", NULL);
// Dispatch a background thread for download
dispatch_async(backgroundQueue, ^(void) {
imageData = [NSData dataWithContentsOfURL:url];
if (imageData.length >0)
{
// self.image is a property of the box model
self.image = [[UIImage alloc] initWithData:imageData];
// Update UI on main thread
dispatch_async(dispatch_get_main_queue(), ^(void) {
});
}
});
}
return self;
}
My question is this:
How do I get the box model to download the image and then in my cellAtIndexPath make the cell's imageView assign its image from that downloaded boxmodel image?
A further unrelated question
Isn't it best practice to seperate the model from the actual downloading of items? But if I'm not meant to put this in the view controller, and not the model where does it go and how/whem would you call it?
Thanks for now

Separating the model from its image file and keeping images in a cache with a key which is model's image URL, is better approach.
Also, putting initialization method in your MyCustomCollectionViewCell is better way to configure your cell.
I suggest you to use https://github.com/rs/SDWebImage (or similar libraries) to download and cache images.
MyCustomCollectionViewCell.h :
#interface MyCustomCollectionViewCell : UITableViewCell
-(void) initializeWithBox:(Box*)box;
MyCustomCollectionViewCell.m with SDWebImage:
#implementation MyCustomCollectionViewCell
-(void) initializeWithBox:(Box*)box
{
[self setBackgroundColor:[box bgColor]];
[self.imageView sd_setImageWithURL:[NSURL URLWithString:[box kUrlString]] placeholderImage:nil];
}
MyCustomCollectionViewCell.m without libraries:
#implementation MyCustomCollectionViewCell
-(void) initializeWithBox:(Box*)box
{
[self setBackgroundColor:[box bgColor]];
__weak typeof(self) weakSelf = self;
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:[box kUrlString]] 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(), ^{
typeof(self) strongSelf = weakself;
if(strongSelf)
[self.imageView setImage:image];
});
}
}
}];
[task resume];
}
In your view controller:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCollectionViewCell *cell = (MyCustomCollectionViewCell *) [collectionView dequeueReusableCellWithReuseIdentifier:cellId forIndexPath:indexPath];
Box *box = self.picturesArray[indexPath.row];
[cell initializeWithBox:box]
return cell;
}

Related

Best Way utilize PHPhotoLibrary for display Camera Roll image in UICollectionView with custom Cell

Hello everyone I have a problem with my app ... Within my View Controller hp a CollectionView with a custom cell that should return all of the photos in the Camera Roll section of the app pictures of my iphone.
Now I've done all the steps to show the photos in a ImageView in the custom cell and up to now I have no problem ... My problem is that when I start to scroll through photos, uploading photos is very slow and immediately after the app crashes giving me back this error in the log ..
[GatekeeperXPC]
Connection to assetsd was interrupted or assetsd died 25/02/2017 20:
[Generic] Creating an image
format with an unknown type is an error
Can you tell me if I've taken the right way to show pictures in my collection view? Where did I go wrong? because my app crashes?
Thank you all for any help you can give me
This is the code i utilize
- (void)viewDidLoad {
[super viewDidLoad];
self.nameTextField.delegate = self;
self.emailTextField.delegate = self;
self.passwordTextField.delegate = self;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
_collectionView.backgroundColor = [UIColor clearColor];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[_nameTextField becomeFirstResponder];
[self queryImage];
}
-(void)queryImage {
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
PHFetchResult *collection = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:fetchOptions];
if (collection.firstObject != nil ) {
_photoFound = YES;
_assetCollection = collection.firstObject;
} else {
}
_photoAsset = [PHAsset fetchAssetsInAssetCollection:_assetCollection options:nil];
[_collectionView reloadData];
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(80,80);
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSUInteger count = 0;
if (_photoAsset != nil) {
count = [_photoAsset count];
}
return count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *reuseIdentifier = #"imageCell";
UPCameraRollCollectionViewCell* cell = [cv dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor redColor];
PHAsset *asset = [_photoAsset objectAtIndex:indexPath.item];
PHImageManager *imageManager = [PHImageManager defaultManager];
[imageManager requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
[cell setThumbnailImage:result];
}];
return cell;
}
Use PHCachingImageManager.
Apple has an example that shows exactly how to do the sort of thing you're after. Collection views are precisely the intended use case.

Loading url image array add to UIcollectionview

I want to load images array from Url and add to UICollectionView
Tony.pageImages =imagearray;
[Tony reloadData];
NSLog(#"imagearray%#",imagearray);
Tony is Collection view.h/m files. pageImages is NSMutableArray on the Tony.h files. The Image array display console log mean i got image array correctly
Those are Url.
but when i add code on the UICollectionView method it doesn't work
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
UIImageView *CollectionViewImage = (UIImageView *)[cell viewWithTag:120];
CollectionViewImage.image= [UIImage imageNamed:self.pageImages[indexPath.row]];
return cell;
}
Do i miss something ?
This is my imagearray
imagearray = [[NSMutableArray alloc]init];
for (int i = 0;i < comment.count; i++)
{
[imagearray addObject:comment[i][#"pic_url"]] ;
}
You have to implement and set the following dataSource delegate UICollectionViewDataSource in your viewcontroller:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
return imagearray.count;
}
If I got it right, you need to load image from URL.
To do that simply add this code:
[CollectionViewImage.image setImageWithURL:[NSURL URLWithString:self.pageImages[indexPath.row]] placeholderImage:nil];
Or if you want to add placeholder image, just set placeholderImage:[UIImage imageNamed:#"someImageInYourAssets"];
NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:self.pageImages[indexPath.row]]];
UIImage *image = [UIImage imageWithData: imageData];
CollectionViewImage.image = image;
This is solution how solve problem loading image from url,without addition frameworks such as "Heneke","AFNetworking"..etc..In Objective C Image View can't communicated with URL directly, but can using NSData as Helper Object(wrapper).

How to show an UIActivityindicator in UICollectionview customcell untill the image downloads at server side?

Can Any one help me how to show the activity indicator until the image for UICollection cell downloads at back end.
In my code the activity indicator is show only for last cell..Don't know where I m making the mistake
Here is my code:
- (collectionCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath{
[self.menuCollectionView registerNib:[UINib nibWithNibName:#"collectionCell"
bundle:nil] forCellWithReuseIdentifier:#"CELL"];
collectionCell= [menuCollectionView dequeueReusableCellWithReuseIdentifier:#"CELL"
forIndexPath:indexPath];
MenuItems *item=[itemsfinal objectAtIndex:indexPath.row];
NSMutableString *str = [NSMutableString stringWithFormat:#"%#", item.itemImage];
NSLog(#" url %#",str);
UIImage *image = [UIImage imageWithContentsOfFile:[self loadImage:str]];
if(image !=nil){
collectionCell.menuRecipeImage.image = image;
collectionCell.activityIndicator.hidden = YES;
[collectionCell.activityIndicator stopAnimating];
}else{
collectionCell.activityIndicator.hidden = NO;
[collectionCell.activityIndicator startAnimating];
collectionCell.menuRecipeImage.image = [UIImage imageNamed:#"menudefualt.png"];
}
return collectionCell;
}
In cellForItemAtIndexPath you should set up your activity indicator. Then start loading your image for the cell in the background. When the image have loaded apply it to the cell on the main thread.
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
__block UICollectionViewCell* cell = [cv dequeueReusableCellWithReuseIdentifier:#"cell"
forIndexPath: indexPath];
// Placeholder text --
UILabel* label = [[UILabel alloc] initWithFrame:cell.bounds];
label.text = #"Downloading...";
[cell.contentView addSubview:label];
// Load image in background --
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSURL* url = [NSURL URLWithString: [NSString stringWithFormat:#"http://example.com/img/img%02lu.png", (long unsigned)indexPath.row]];
// Load and decode image --
NSData * imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
// Apply image on the main thread --
dispatch_sync(dispatch_get_main_queue(), ^{
UIImageView* iv = [[UIImageView alloc] initWithImage:image];
[cell.contentView addSubview:iv];
});
});
return cell;
}
The example in action...
One thing about Niels example is that this may cause an incorrect image to be set for a cell, if the cell is re-used before the image is completely loaded (eg. if you're scrolling quickly). So you need to keep a map of what URL should be set for each cell, so roughly modifying Niels' example above:
#property NSDictionary *cellMap;
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
__block UICollectionViewCell* cell = [cv dequeueReusableCellWithReuseIdentifier:#"cell"
forIndexPath: indexPath];
// Placeholder text --
UILabel* label = [[UILabel alloc] initWithFrame:cell.bounds];
label.text = #"Downloading...";
[cell.contentView addSubview:label];
NSURL* url = [NSURL URLWithString: [NSString stringWithFormat:#"http://example.com/img/img%02lu.png", (long unsigned)indexPath.row]];
[cellMap setObject:url forKey:cell];
// Load image in background --
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Load and decode image --
NSData * imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
// Apply image on the main thread --
dispatch_sync(dispatch_get_main_queue(), ^{
NSURL *cellUrl = [cellMap objectForKey:cell];
if (cellUrl == url) {
// Only set the image if the url is still the same for this cell
UIImageView* iv = [[UIImageView alloc] initWithImage:image];
[cell.contentView addSubview:iv];
}
});
});
return cell;
}
You should use async image load. It can be done with GCD
//First start your activityIndicator
collectionCell.activityIndicator.hidden = NO;
[collectionCell.activityIndicator startAnimating];
//Then using GCD load your image on secondary thread
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//Here your image loading from url
UIImage *image = [UIImage imageWithContentsOfFile:[self loadImage:str]];
dispatch_async(dispatch_get_main_queue(), ^{
//This block (Main thread) waits until your image will be downloaded
//(Note that all operation with views must be executed on a main thread)
//Then loading is done just set your image and stop activityIndicator
collectionCell.menuRecipeImage.image = image;
collectionCell.activityIndicator.hidden = YES;
[collectionCell.activityIndicator stopAnimating];
});
});
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section{
return _items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ImageFilterCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"ImageFilterCell" forIndexPath:indexPath];
cell.lblEffectName.text = [_itemsName objectAtIndex:indexPath.row];
cell.imgLoader.hidden = NO;
cell.imgFilter.image = nil;
NSString *effectName = _items[indexPath.row];
if([effectName isEqualToString:#"Original"]){
cell.imgLoader.hidden = YES;
cell.imgFilter.image = _imgThumb;
}
else {
UIImage *filteredImage = [_filteredImages objectForKey:effectName];
if(filteredImage){
cell.imgLoader.hidden = YES;
cell.imgFilter.image = filteredImage;
} else {
__weak ImageFilterCell *weakCell = cell;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getFilteredImageForEffect:effectName forImage:_imgThumb completionBlock:^(UIImage *image) {
dispatch_async(dispatch_get_main_queue(), ^{
weakCell.imgLoader.hidden = YES;
weakCell.imgFilter.image = image;
[_filteredImages setObject:image forKey:effectName];
});
}];
});
}
}
if(_checkedIndexPath==indexPath.row)
cell.highlighted = YES;
else
cell.highlighted = NO;
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if(_checkedIndexPath != indexPath.row && indexPath.row!=0){
_checkedIndexPath = indexPath.row;
NSString *effectName = _items[indexPath.row];
if([_delegate respondsToSelector:#selector(showIndicator)]){
[_delegate showIndicator];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getFilteredImageForEffect:effectName forImage:_orgImage completionBlock:^(UIImage *image) {
dispatch_async(dispatch_get_main_queue(), ^{
if([self.delegate respondsToSelector:#selector(filterImageView:filteredImage:)]){
[self.delegate filterImageView:self filteredImage:image];
}
});
}];
});
} else if (indexPath.row == 0) {
_checkedIndexPath = 0;
if([self.delegate respondsToSelector:#selector(filterImageView:filteredImage:)]){
[self.delegate filterImageView:self filteredImage:_orgImage];
}
}
[_collectionView reloadData];
}

I have some remote images I'm loading into an NSMutableArray but the array isn't populated by the time I need it

Well after troubles with swipe gesture and my own image gallery slider I decided to go with a premade one.
I'm using iCarousel and it works fine. I've tested using an array that is populated with non remote images.
Inside init with coder I done this:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_additionalGarmentImagesArray = [[NSMutableArray alloc] init];
UIImage *image1 = [UIImage imageNamed:#"asos.png"];
UIImage *image2 = [UIImage imageNamed:#"neck.png"];
UIImage *image3 = [UIImage imageNamed:#"diamind.png"];
[_additionalGarmentImagesArray addObject:image1];
[_additionalGarmentImagesArray addObject:image2];
[_additionalGarmentImagesArray addObject:image3];
}
return self;
}
This works fine. However it's not useful to me as my images are remote images. So I've taken the code I used with my own image gallery but the images don't get downloaded on time.
In my viewDidLoad:
PFQuery *query = [PFQuery queryWithClassName:#"Garments"];
[query whereKey:#"title" equalTo:[self garmentTitle]];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
PFFile *additionalImage1 = [object objectForKey:#"image2"];
PFFile *additionalImage2 = [object objectForKey:#"image3"];
PFFile *additionalImage3 = [object objectForKey:#"image4"];
PFFile *additionalImage4 = [object objectForKey:#"image5"];
PFFile *additionalImage5 = [object objectForKey:#"image6"];
// Add main image to array first, the one passed over from previous controller
[_additionalGarmentImagesArray addObject:[self garmentImage]];
if (additionalImage1) {
PFImageView *pfImageView1 = [[PFImageView alloc] init];
[pfImageView1 setFile:additionalImage1];
[pfImageView1 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
if (additionalImage2) {
PFImageView *pfImageView2 = [[PFImageView alloc] init];
[pfImageView2 setFile:additionalImage2];
[pfImageView2 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
if (additionalImage3) {
PFImageView *pfImageView3 = [[PFImageView alloc] init];
[pfImageView3 setFile:additionalImage3];
[pfImageView3 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
} else {
NSLog(#"empty array");
}
}];
This is where the array of images is needed:
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
return [_additionalGarmentImagesArray count];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 380)];
view = imageView;
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
return view;
}
The viewDidAppear method shows array is populated:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"Images Count: %i", [_additionalGarmentImagesArray count]);
}
Please help me solve this issue with solutions. My images are stored remotely and I need a way to have them ready by the time the controller is loaded so they can be used by iCarousel or maybe have iCarousel wait then refresh itself when the array is populated.
Maybe even some how do something in the previous controller which is a UICollectionView. I could tap the cell then get the images I need from the cells object and populate an array then. The only allow the push/segue to happen once the images are in the array. However I don't like that. It means a user would have to wait after they tap to go to the detail view controller where the iCarousel is.
Would appreciate some solutions if possible. It's been a few hours and I've made little progress.
Thanks for your time
Solution was to reloadData at a time where I did have the array. So the best place to do this was inside the viewDidAppear method.
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 400)];
view = imageView;
//create new view if no view is available for recycling
if (view == nil)
{
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
} else {
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
}
return view;
}
Reload Data:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"Images Count: %i", [_additionalGarmentImagesArray count]);
[[self carousel] reloadData];
}

Why are my UITableViewCell images only being loaded when I leave then return to a controller and not during viewDidLoad?

Having a slight problem with my UITableViewCell images. I'm loading my data straight from parse.com. My objects array that returns PFObject's is stored inside an NSMutable array named "people".
This is how I display the data in my table:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [[self tableView] dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
Person *current;
if (tableView == [[self searchDisplayController] searchResultsTableView]) {
current = [searchResults objectAtIndex:indexPath.row];
} else {
current = [people objectAtIndex:[indexPath row]];
}
[[cell textLabel] setText: [current valueForKey:#"name"]];
PFFile *userImageFile = [current valueForKey:#"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
UIImage *image = [UIImage imageWithData:imageData];
[[cell imageView] setImage: image];
}];
// [[cell imageView] setImage: [current image]];
[[cell detailTextLabel] setText: [current valueForKey:#"notes"]];
return cell;
}
The problem is when I load the app up and this view which is my main loads it doesn't load any images. However when I tap on a row just before the next controller is popped on screen I see the image for that row load and then when I tap the back button and go back to the main view again the rest of the tableViews images load.
Is this something to do with the images not being thumbnail versions?
I've tried wrapping the code in dispatch_async(dispatch_get_main_queue(), ^ { )}; with no luck. Can someone help me solve this issue?
Kind regards
Update to show where I call reload data:
-(void)viewDidAppear:(BOOL)animated {
dispatch_async(dispatch_get_main_queue(), ^{
[[self tableView] reloadData];
});
}
- (void)viewDidLoad
{
[super viewDidLoad];
people = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:#"People"];
[query whereKey:#"active" equalTo:#1];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
people = objects.mutableCopy;
dispatch_async(dispatch_get_main_queue(), ^ {
[[self tableView] reloadData];
});
I don't think there is anything wrong with your loading in your viewDidLoad.
My suspicion is that the UIImageView's frame is actually zero as you did not have a placeholder image while loading the actual images. The cell will not be redrawn again until the next time layoutSubviews is called again, even if your fetched image has loaded. So either set a placeholder image, or call:
[cell setNeedsLayout];
once your image is fully loaded.
Another alternative is to use PFImageView, a subclass of UIImageView, which takes care of everything for you.
PFFile *userImageFile = [current valueForKey:#"image"];
[cell imageView].image = [UIImage imageNamed:#"placeholder.jpg"]; // placeholder image
[cell imageView].file = userImageFile;
[[cell imageView] loadInBackground];
Instead of loading my data directly from parse.com into my tableView I loaded it into an object first. So each object was no longer an PFObject and now a Person object and I stored these in a mutable array which I accessed in my tableView.
Try it:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear]; //this is necessary for most time
//viewDidAppear be called in main thread, so just call reloadData directly
[self.tableView reloadData];
}
As mentioned in Apple document about - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath:
You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method.
If you registered a class for the specified identifier and a new cell must be created, this method initializes the cell by calling its initWithStyle:reuseIdentifier: method.
For nib-based cells, this method loads the cell object from the provided nib file. If an existing cell was available for reuse, this method calls the cell’s prepareForReuse method instead.
So, do you forget to use the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling cellForRowAtIndexPath method?
Here is a discussion about this.
How I am doing this
In my UIViewController.m
#property (nonatomic, strong) NSMutableDictionary *imageDownloadsInProgress;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.imageDownloadsInProgress = [NSMutableDictionary dictionary];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
SRKProduct *productRecord = [stockArray objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if (!productRecord.image || [productRecord.image isEqualToData:NULL] || productRecord.image.length == 0) {
if (_itemTableView.dragging == NO && _itemTableView.decelerating == NO)
{
[self startIconDownload:productRecord forIndexPath:indexPath];
}
cell.imageView.image = [[UIImage imageNamed:#"Placeholder.png"] makeThumbnailOfSize:CGSizeMake(50,50)];//This is just a placeholder and will be removed when original image is downloaded.
}
return cell;
}
#pragma mark -
- (void)startIconDownload:(SRKProduct *)srkproduct forIndexPath:(NSIndexPath *)indexPath
{
SRKIconDownloader *iconDownloader = [self.imageDownloadsInProgress objectForKey:indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[SRKIconDownloader alloc] init];
iconDownloader.srkproduct = srkproduct;
[iconDownloader setCompletionHandler:^{
UITableViewCell *cell = [_itemTableView cellForRowAtIndexPath:indexPath];
// Display the newly loaded image
cell.imageView.image = [UIImage imageWithData:srkproduct.image];
NSLog(#"Image %d",[productAdapter updateproductImage:srkproduct]);
// Remove the IconDownloader from the in progress list.
// This will result in it being deallocated.
[self.imageDownloadsInProgress removeObjectForKey:indexPath];
}];
[self.imageDownloadsInProgress setObject:iconDownloader forKey:indexPath];
[iconDownloader startDownload];
}
}
Then in SRKIconDownloader.h
#interface SRKIconDownloader : NSObject
#property (nonatomic, strong) SRKProduct *srkproduct;
#property (nonatomic, copy) void (^completionHandler)(void);
And in SRKIconDownloader.m
#implementation SRKIconDownloader
#pragma mark
- (void)startDownload
{
PFQuery *queryCouple = [PFQuery queryWithClassName:#"Product"];
[queryCouple whereKey:#"Name" equalTo:_srkproduct.productName];
[queryCouple findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
if ([objects count] > 0) {
for (PFObject *object in objects) {
PFFile *image = (PFFile *)[object objectForKey:#"Image"];
[image getDataInBackgroundWithBlock:^(NSData *data, NSError *error){
_srkproduct.image = data;
// call our delegate and tell it that our icon is ready for display
if (self.completionHandler)
self.completionHandler();
}];
break;
}
}
else{
}
}
}];
}
#end