tableview load image async but sometimes some cell will never load successfully? - objective-c

I want tableview to load images async,
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
StickerListItemView *rowView = [tableView makeViewWithIdentifier:StickerTableViewIdentifier
owner:self];
if (!rowView) {
rowView = [[StickerListItemView alloc] initWithFrame:NSMakeRect(0, 0, 460, 300)];
rowView.identifier = StickerTableViewIdentifier;
id obj = [self.stickerListArray objectAtIndex:row];
if ([obj isKindOfClass:[StickerListModel class]]) {
StickerListModel *listModel = (StickerListModel *)obj;
rowView.productId = listModel.productId;
rowView.title = listModel.name;
rowView.intro = listModel.intro;
rowView.status = [self hasStickersDownloaded:listModel.productId];
[rowView setDelegate:self];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSImage *image = [self getPreviewImageWithMd5:listModel.md5OfPrieviewImage];
//****************************************
NSLog(#"get image at index %ld",(long)row);
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
rowView.image = image;
[rowView setNeedsDisplay:YES];
//**************************************
NSLog(#"set image at row %ld",(long)row);
});
}
});
}
}
return rowView;
}
Sometimes, it will load successfully, but sometimes one cell will never load the image, just like the picture below: the
I wait for long time, but the third image never loaded.

It looks like this line is returning nil.
NSImage *image = [self getPreviewImageWithMd5:listModel.md5OfPrieviewImage];
You should check the value of image to make sure it's not nil and possibly add some error handling. Maybe something like this.
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
rowView.image = image;
[rowView setNeedsDisplay:YES];
NSLog(#"set image at row %ld",(long)row);
});
} else {
NSLog(#"Received nil image for row %ld", (long)row);
//possibly set a placeholder image for this row instead
}

Related

Set TV Menu Button To Go Back One Screen

My Apple TV app lists a collection of PDFs, and clicking one draws it on the screen. For some reason, though, it exits the app completely when someone hits the < / Menu button on the Apple TV Remote. What is going on to cause it to do that?
Up first is the code where it pulls up the list of all the PDFs, followed by the code for displaying the PDF.
-(void)viewDidLoad {
[super viewDidLoad];
self.definesPresentationContext = YES; // know where you want UISearchController to be displayed
}
- (void)viewWillAppear:(BOOL)animated {
if (self.searchControllerWasActive) {
self.searchController.active = self.searchControllerWasActive;
_searchControllerWasActive = NO;
if (self.searchControllerSearchFieldWasFirstResponder) {
[self.searchController.searchBar becomeFirstResponder];
_searchControllerSearchFieldWasFirstResponder = NO;
}
}
NSBundle *bundle = [NSBundle mainBundle];
self.files = [bundle pathsForResourcesOfType:#"pdf" inDirectory:#"AIMPDF"];
NSString *documentsDirectoryPath = [self.files objectAtIndex:thepath.row];
self.title = #"Devo Songs";
self.filenames = [[documentsDirectoryPath lastPathComponent] stringByDeletingPathExtension];
NSLog(#"%#", filenames);
NSMutableArray *names = [NSMutableArray arrayWithCapacity:[self.files count]];
for (NSString *path in self.files) {
[names addObject:[[path lastPathComponent] stringByDeletingPathExtension]];
}
self.files = names;
self.files = [names sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
self.tableView.backgroundColor = [UIColor whiteColor];
self.parentViewController.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"iphonebackground.png"]];
[super viewDidLoad];
[super viewWillAppear:animated];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[searchBar resignFirstResponder];
}
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
// update the filtered array based on the search text
NSString *searchText = searchController.searchBar.text;
NSMutableArray *searchResults2 = [self.files mutableCopy];
// strip out all the leading and trailing spaces
NSString *strippedString = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
// break up the search terms (separated by spaces)
NSArray *searchItems = nil;
if (strippedString.length > 0) {
searchItems = [strippedString componentsSeparatedByString:#" "];
}
// build all the "AND" expressions for each value in the searchString
//
NSMutableArray *andMatchPredicates = [NSMutableArray array];
for (NSString *searchString in searchItems) {
NSPredicate *sPredicate =
[NSPredicate predicateWithFormat:#"SELF contains[c] %#", searchString];
[searchResults2 filterUsingPredicate:sPredicate];
// at this OR predicate to our master AND predicate
// NSCompoundPredicate *orMatchPredicates = [NSCompoundPredicate orPredicateWithSubpredicates:searchItemsPredicate];
//[andMatchPredicates addObject:orMatchPredicates];
}
// match up the fields of the Product object
// NSCompoundPredicate *finalCompoundPredicate =
//[NSCompoundPredicate andPredicateWithSubpredicates:andMatchPredicates];
//searchResults2 = [[searchResults filteredArrayUsingPredicate:finalCompoundPredicate] mutableCopy];
// hand over the filtered results to our search results table
APLResultsTableController *tableController = (APLResultsTableController *)self.searchController.searchResultsController;
tableController.filteredProducts = searchResults2;
[tableController.tableView reloadData];
}
- (void)handleSearchForTerm:(NSString *)searchTerm
{
[self setSavedSearchTerm:searchTerm];
if ([self searchResults] == nil)
{
NSMutableArray *array = [[NSMutableArray alloc] init];
[self setSearchResults:array];
array = nil;
}
[[self searchResults] removeAllObjects];
if ([[self savedSearchTerm] length] != 0)
{
for (NSString *currentString in [self files])
{
if ([currentString rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[[self searchResults] addObject:currentString];
}
}
}
}- (void)viewDidUnload
{
[super viewDidUnload];
// Save the state of the search UI so that it can be restored if the view is re-created.
[self setSavedSearchTerm:[[[self searchDisplayController] searchBar] text]];
[self setSearchResults:nil];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
if( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad )
{
// The device is an iPad running iPhone 3.2 or later.
return YES;
}
else
{
// The device is an iPhone or iPod touch.
return YES;
}
#else
// iPhone simulator
return YES;
#endif
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
NSInteger rows;
rows = [[self files] count];
return rows;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *filename = [[[self.files objectAtIndex:indexPath.row] lastPathComponent] stringByDeletingPathExtension];
NSInteger row = [indexPath row];
NSString *contentForThisRow = nil;
contentForThisRow = filename;
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[[cell textLabel] setText:contentForThisRow];
cell.textLabel.font = [UIFont fontWithName:#"Helvetica Neue" size:90];
cell.textLabel.textColor = [UIColor blackColor];
cell.backgroundColor = [UIColor lightGrayColor];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) {
return 80;
}
else {
return 120;
}
}
- (void)tableView:(UITableView *)tableView didUpdateFocusInContext:(UITableViewFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
//this gives you the indexpath of the focused cell
NSIndexPath *nextIndexPath = [context nextFocusedIndexPath];
NSLog(#"Do Something");
}
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedCountry = (tableView == self.tableView) ?
self.files[indexPath.row] : self.resultsTableController.filteredProducts[indexPath.row];
[self performSegueWithIdentifier:#"ShowSong" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"ShowSong"])
{
NSLog(#"Selecting %#", self.selectedCountry);
FirstViewController* userViewController = [segue destinationViewController];
userViewController.selectedCountry = self.selectedCountry;
//if you need to pass data to the next controller do it here
}
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc. that aren't in use.
}
- (void)dealloc {
}
#end
And now the code for opening:
#implementation FirstViewController
- (CGPDFDocumentRef)openPDFLocal:(NSString *)pdfURL {
NSURL* NSUrl = [NSURL fileURLWithPath:pdfURL];
return [self openPDF:NSUrl];
}
- (CGPDFDocumentRef)openPDFURL:(NSString *)pdfURL {
NSURL* NSUrl= [NSURL URLWithString:pdfURL];
return [self openPDF:NSUrl];
}
- (CGPDFDocumentRef)openPDF:(NSURL*)NSUrl {
CFURLRef url = (CFURLRef)CFBridgingRetain(NSUrl);
CGPDFDocumentRef myDocument;
myDocument = CGPDFDocumentCreateWithURL(url);
if (myDocument == NULL) {
NSLog(#"can't open %#", NSUrl);
CFRelease (url);
return nil;
}
CFRelease (url);
if (CGPDFDocumentGetNumberOfPages(myDocument) == 0) {
CGPDFDocumentRelease(myDocument);
return nil;
}
return myDocument;
}
- (void)drawDocument:(CGPDFDocumentRef)pdfDocument
{
// Get the total number of pages for the whole PDF document
int totalPages= (int)CGPDFDocumentGetNumberOfPages(pdfDocument);
self.pages = totalPages;
NSMutableArray *pageImages = [[NSMutableArray alloc] init];
// Iterate through the pages and add each page image to an array
for (int i = 1; i <= totalPages; i++) {
// Get the first page of the PDF document
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, i);
CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
// Begin the image context with the page size
// Also get the grapgics context that we will draw to
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate the page, so it displays correctly
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, pageRect, 0, true));
// Draw to the graphics context
CGContextDrawPDFPage(context, page);
// Get an image of the graphics context
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[pageImages addObject:image];
}
// Set the image of the PDF to the current view
[self addImagesToScrollView:pageImages];
}
-(void)addImagesToScrollView:(NSMutableArray*)imageArray {
int heigth = 0;
for (UIImage *image in imageArray) {
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
imgView.frame=CGRectMake(200, heigth, 1520, 1080);
[_scrollView addSubview:imgView];
heigth += imgView.frame.size.height;
}
}
-(void)viewDidLayoutSubviews {
NSLog(#"%ld", (long)self.pages);
_scrollView.contentSize = CGSizeMake(1920, 1080*self.pages);
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"DOCUMENTS%#", self.selectedCountry);
NSString *testing = [self.selectedCountry stringByAppendingString:#".pdf"];
//This is passed in from a tableview after selecting the PDF needed.
_scrollView.panGestureRecognizer.allowedTouchTypes = #[ #(UITouchTypeIndirect) ];
NSString *Documents = [[NSBundle mainBundle] pathForResource:self.selectedCountry ofType:#"pdf" inDirectory:#"AIMPDF"];
NSLog(#"OKOKOK%#", Documents);
NSURL *url = [NSURL fileURLWithPath:Documents];
self.view.backgroundColor = [UIColor blackColor];
CGPDFDocumentRef pdfDocument = [self openPDF:url];
[self drawDocument:pdfDocument];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
In your case the searching view controller stays on the top level of your app that's why the app is closed by pressing Menu button instead of returning to your start screen. On tvOS you should not use UISearchController directly and call
[self.searchController.searchBar becomeFirstResponder]; or self.searchController.active = YES; to present searching because it breaks a stacked view hierarchy that allows you to navigate upper by presented view controllers.
On Apple TV, people typically navigate by moving through stacked screens of content. Each screen may present entry points to other screens, and provides a way — through the remote — to return to the previous screen or main menu.
https://developer.apple.com/design/human-interface-guidelines/tvos/app-architecture/navigation/
There is UISearchContainerViewController view controller that manages the presentation of search results in your interface and you can use it for searching:
In tvOS, rather than push a UISearchController onto a navigation controller’s stack or use one as a child of another container view controller, embed an instance of this class and let it manage the presentation of the search controller’s content. https://developer.apple.com/documentation/uikit/uisearchcontainerviewcontroller
For instance:
UITableViewController* searchTableViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SearchTableViewController"];
UISearchController* searchController = [[UISearchController alloc] initWithSearchResultsController:searchTableViewController];
searchController.delegate = self;
searchController.searchResultsUpdater = self;
searchController.searchBar.delegate = self;
self.searchContainerViewController = [[UISearchContainerViewController alloc] initWithSearchController:searchController];
// Show searching
[self presentViewController:self.searchContainerViewController animated:YES completion:nil];

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

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;
}

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];
}

UITableViewCell using reuseidentifier giving unwanted result with callback block

When the callback block for loadImage is run below, the table cell may have since been reused. So the image is applied to "imageView" is not relevant to this reused cell, it's the image for the old cell.
If I make the identifier unique for each cell that has an image, the problem goes away. But this gives poor performance with many results.
Can I somehow use the same reuse identifier with a callback block and have the images turn up in the correct cells?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *place;
PlaceTableViewCell *cell; // UITableViewCell subclass
NSString *identifier = #"PlaceTableViewCell";
if (cell == nil) {
NSArray *objects;
objects = [[NSBundle mainBundle] loadNibNamed:#"PlaceTableViewCell" owner:self options:nil];
for(id object in objects) {
if([object isKindOfClass:[PlaceTableViewCell class]]) {
cell = (PlaceTableViewCell *)object;
break;
}
}
}
UIImageView *imageView;
if((imageView = (UIImageView*)[cell viewWithTag:1])) {
NSString *filename;
int placeImageId = 0;
place = [places objectAtIndex:indexPath.row];
if(place) {
placeImageId = [[d objectForKey:#"placeImageId"] intValue];
if(placeImageId) {
[[RestAPIConnector sharedInstance] loadImage :placeImageId :#"thumb" :^(NSString *response){
NSDictionary *image = [response JSONValue];
if ([image objectForKey:#"img"]) {
NSString *b64Img = [image objectForKey:#"img"];
UIImage *ui = [UIImage imageWithData:[Base64 decode:b64Img]];
imageView.image = ui;
}
}];
}
}
}
return cell;
}
here is what I'm doing.
instead of using the cell directly, I'm passing in the index path
if(user.profileImage == nil)
{
if (self.tableView.dragging == NO && self.tableView.decelerating == NO) {
NSLog(#"file for user %d doesn't exist", [user.userId intValue]);
[self startUserProfileImageDownload:user forIndexPath:indexPath];
}
}
else
{
cell.profileImageView.image = user.profileImage;
}
once the download is complete, use the index path to retrieve the cell, and update the image
MessageCell *cell = (MessageCell *)[self.tableView cellForRowAtIndexPath:path];
// Display the newly loaded image
cell.profileImageView.image = user.profileImage;
CALayer *roundedLayer = [cell.profileImageView layer];
MessageCell is my custom cell. if you don't have use customer cell, you can use Tag to retrieve the imageView back.
I'd create a dictionary to hold the images, then attempt to read from the dictionary in cellForRowAtIndexPath:
#property(retain)NSMutableDictionary *imageData;
//...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//...
UIImageView *imageView;
if((imageView = (UIImageView*)[cell viewWithTag:1])) {
int placeImageId = 0;
place = [places objectAtIndex:indexPath.row];
if(place) {
placeImageId = [[d objectForKey:#"placeImageId"] intValue];
if(placeImageId) {
NSObject *image = [imageData objectForKey:[NSNumber numberWithInt:placeImageId]];
if ([image isKindOfClass:[UIImage class]) {
imageView.image = (UIImage *)image;
} else if (![image isEqual:#"downloading"]) {
[imageData addObject:#"downloading" forKey:[NSNumber numberWithInt:placeImageId]];
[[RestAPIConnector sharedInstance] loadImage:placeImageId onSuccess:^(NSString *response){
NSDictionary *image = [response JSONValue];
if ([image objectForKey:#"img"]) {
NSString *b64Img = [image objectForKey:#"img"];
[imageData addObject:[UIImage imageWithData:[Base64 decode:b64Img]] forKey:[NSNumber numberWithInt:placeImageId]];
}
}];
}
}
}
}
return cell;
}
Some potential optimizations:
As in #Jun1st's sample, don't load images for cells while scrolling
Add download operations to an NSOperationQueue and prioritize the most recently requested ones first (deprioritizing the ones that have scrolled by)
Save downloaded images to the filesystem and check there first