I am using a UICollectionView with a modified version of LXReorderableCollectionViewFlowLayout and data within my last two cells are being mixed up.
2 separate images are displayed in each cell, with a label beneath, and when I leave the viewController and return, I call reloadData. Every time this is called, the images in the cells swap. The labels, however, stay in the same place.
Here is the code I use to draw the cells which I have the issue with.
SettingsTile *setTileD = (SettingsTile *)[collectionView dequeueReusableCellWithReuseIdentifier:#"settingsReuse" forIndexPath:indexPath];
NSString *songTitle = [colors objectAtIndex:indexPath.row];
MPMediaQuery *query = [MPMediaQuery songsQuery];
[query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:songTitle forProperty:MPMediaItemPropertyTitle]];
if ([query items].count > 0)
{
MPMediaItemArtwork *art = [[[query items] objectAtIndex:0] valueForProperty:MPMediaItemPropertyArtwork];
UIImage *im = [art imageWithSize:CGSizeMake(145, 145)];
setTileD.titleLabel.text = songTitle;
setTileD.titleLabel.textColor = [UIColor whiteColor];
UIImageView *imView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 145, 145)];
imView.image = im;
[setTileD addSubview:imView];
[setTileD sendSubviewToBack:imView];
return setTileD;
}
else
{
return nil;
}
The first time the app is run the images are mixed up, likely because viewDidAppear is also called when the viewController is first displayed, and when the user comes back to it, the images aren't mixed up. This process repeats itself.
reloadData is called as normal (e.g [collectionView reloadData]; ).
I'm new to UICollectionView, and it's accompanying classes, so please be kind. :)
I fixed the problem by putting the UIImageViews in a backgroundView, rather than adding them directly as subviews.
I can only imagine this problem is caused either by the way UICollectionView's cellForRowAtIndexPath: method grabs images as I call them, or the way LXReorderableCollectionViewFlowLayout works.
Related
This is my first time working with UICollectionView.
I've got everything set up and laid out as it should be (as far as I know). I've had a little bit of difficulty with the way the dequeue function works for the UICollectionView, but I think I've gotten past that. It was tricky setting up my custom cell classes when I didn't know if initWithFrame would be called or prepareForReuse.
I'm pretty sure the problem lies within the prepareForReuse function, but where is the question.
What happens is, the cells will apparently randomly draw at the top-left of the collection view and some cells will not be where they belong in the grid. (see image attached)
When bouncing, scrolling, and zooming (so as to cause reuse to occur), the problem happens. Randomly a slide will appear in the top left, and other slides will randomly disappear from the grid.
( I need more rep to post an image. Ugh. :| If you can help me, I'll email you the image. bmantzey#mac.com )
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
Slide* thisSlide = [_presentation.slidesInEffect objectAtIndex:indexPath.row];
[BuilderSlide prepareWithSlide:thisSlide];
BuilderSlide* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"PlainSlide" forIndexPath:indexPath];
return cell;}
I'm using a static method to set the Slide object, which contains the data necessary to either prepare the asynchronous download or retrieve the image from disk cache.
It's simply:
+(void)prepareWithSlide:(Slide*)slide{
if(s_slide)
[s_slide release];
s_slide = [slide retain];}
I'm not sure if it's a big no-no to do this but in my custom Cell class, I'm calling prepareForReuse in the initWithFrame block because I need that setup code to be the same:
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self)
{
[self prepareForReuse];
}
return self;}
Here's the prepareForReuse function:
-(void)prepareForReuse{
CGSize size = [SpringboardLayout currentSlideSize];
[self setFrame:CGRectMake(0, 0, size.width, size.height)];
self.size = size;
// First remove any previous view, so as not to stack them.
if(_builderSlideView)
{
if(_builderSlideView.slide.slideID == s_slide.slideID)
return;
[_builderSlideView release];
}
for(UIView* aView in self.contentView.subviews)
{
if([aView isKindOfClass:[BuilderSlideView class]])
{
[aView removeFromSuperview];
break;
}
}
// Then setup the new view.
_builderSlideView = [[BuilderSlideView alloc] initWithSlide:s_slide];
self.builderCellView = _builderSlideView;
[s_slide release];
s_slide = nil;
[self.contentView addSubview:_builderSlideView];
if([SlideCache isImageCached:_builderSlideView.slide.slideID forPresentation:_builderSlideView.slide.presentationID asThumbnail:YES])
{
[_builderSlideView loadImageFromCache];
}
else
{
[_builderSlideView loadView];
}}
Finally, when the slide image has been downloaded, a Notification is posted (I plan on changing this to a delegate call). The notification simply reloads the cell that has received an update. Here's the notification code:
-(void)didLoadBuilderCellView:(NSNotification*)note{
BuilderCellView* cellView = [[note userInfo] objectForKey:#"cell"];
BuilderSlideView* slideView = (BuilderSlideView*)cellView;
NSIndexPath* indexPath = [self indexPathForSlide:slideView.slide];
if(indexPath)
[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];}
Note that the slide objects exist in the model.
Any ideas as to what may be causing this problem? Thanks in advance!
The problem that was causing the cells to draw top left and/or disappear was caused by infinite recursion on a background thread. Plain and simply, I wasn't implementing the lazy loading correctly and safely. To solve this problem, I went back to the drawing board and tried again. Proper implementation of a lazy loading algorithm did the trick.
I have the smooth scrolling issue at my UITableView with UITableViewCell which contains UIImageView. Similar issues could be found all over the StrackOverflow but none of the proposed solutions helped me to completely get rid of the lag.
My case is quite common:
images are stored at application storage (in my sample at app bundle)
images could have different size (500x500, 1000x1000, 1500x1500)
I need to display those images in UITableView where UIImageView size is 120x120 (retina)
I have followed multiple optimization tips and managed to optimize scrolling a lot.
Unfortunately it is still not perfect. This is my scenario:
first I moved all the image loading/processing/resizing logic to the background thread
UITableViewCell reuse is enabled
once UITableViewCell is in view I clear old values (settings to null) and start background thread to load the image
at this point we are in background thread and I'm adding 500 ms delay to avoid settings new image to often (in case we are scrolling fast) (see below explanation)
if UIImage exists at static image cache (regular dictionary with UIImage instances) - fetch that one and go to the step 9.
if not - load new image from bundle (imageWithName) using url to app bundle (in real world scenario images will be stored to application storage, not bundle)
once image is loaded resize it to 120x120 using graphics context
save resized image to the static image cache
at this point we have instance to UIImage and process is in the background thread. From here we move back to UI Thread with the given image
if data context was cleared (for example UITableViewCell disappeared or was reused to display another image) we skip processing of the currently available image.
if data context is the same - assign UIImage to UIImageView with an alpha animation (UIView.Animate)
once UITableViewCell is out of view - clear the data context
Originally before starting new background thread to fetch the image here (step 1) was UIImage cache check without background thread. In this case if we have the image in the cache we assign it instantly and this introduces a great lag during fast scrolling (we assign images to often as long as we fetch them instantly). Those lines are commented at my example attached below.
There are still two issues:
at some point during scrolling I still have a small lag (at the
moment when I'm assign new UIImage to UIImageView.
(this one is more noticeable) when you tap on item and go back from details there is a lag right before back navigation animation is finished.
Any suggest how to deal with those two issues or how to optimize my scenario are appreciated
Please take into account that sample written in Xamarin but I don't believe that Xamarin is the cause of the problem as long as I have the same issue for the app written in ObjectiveC as well.
Smooth Scrolling Test App
Did you every tried to populate your TableView with only one 120x120 Image which is saved in your Bundle? This way you can check, if the problem occurs of your Image rendering
Instead of resizing all your images to 120x120 and save them in cache, I would recommend creating and using a thumbnail of all your images. You are somehow already doing this, but you are doing this couple of times (everytime you are scrolling or if your cache is full).
In our last project we had a UICollectionView with book covers. Most of the covers were between 400-800kb big and the feeling while scrolling was really bad. So we created a thumbnail for each image (thumbails about 40-50kb) and used the thumbnails instead of real covers. Works like a charm! I attached the thumbnail creation function
- (BOOL) createThumbnailForImageAtFilePath:(NSString *)sourcePath withName:(NSString *)name {
UIImage* sourceImage = [UIImage imageWithContentsOfFile:sourcePath];
if (!sourceImage) {
//...
return NO;
}
CGSize thumbnailSize = CGSizeMake(128,198);
float imgAspectRatio = sourceImage.size.height / sourceImage.size.width;
float thumbnailAspectRatio = thumbnailSize.height/thumbnailSize.width;
CGSize scaledSize = thumbnailSize;
if(imgAspectRatio >= thumbnailAspectRatio){
//image is higher than thumbnail
scaledSize.width = scaledSize.height * thumbnailSize.width / thumbnailSize.height;
}
else{
//image is broader than thumbnail
scaledSize.height = scaledSize.width * imgAspectRatio;
}
UIGraphicsBeginImageContextWithOptions( scaledSize, NO, 0.0 );
CGRect scaledImageRect = CGRectMake( 0.0, 0.0, scaledSize.width, scaledSize.height );
[sourceImage drawInRect:scaledImageRect];
UIImage* destImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSString* thumbnailFilePath = [[self SOMEDIRECTORY] stringByAppendingPathComponent:name];
BOOL success = [UIImageJPEGRepresentation(destImage, 0.9) writeToFile:thumbnailFilePath atomically:NO];
return success;
}
Try facebook's Async Display library.
https://github.com/facebook/AsyncDisplayKit
Really easy to use.. from their guide: http://asyncdisplaykit.org/guide/
_imageNode = [[ASImageNode alloc] init];
_imageNode.backgroundColor = [UIColor lightGrayColor];
_imageNode.image = [UIImage imageNamed:#"hello"];
_imageNode.frame = CGRectMake(10.0f, 10.0f, 40.0f, 40.0f);
[self.view addSubview:_imageNode.view];
This decodes the image on a background thread.
I'm not sure if it's easy to use iOS libraries on Xamarin but if it's easy, give this a shot.
I sub-class Paul Hegarty's CoreDataTableViewController and employ thumbnails of my photos in the CoreDataTableView.
Look for the examples in Lecture 14 titled FlickrFetcher and Photomania. You will also need to download the CoreDataTableViewController at that same link.
Make a CoreData Entity with an appropriate title and define whatever attributes (data variables) you want. You will need to define two "Transformable" attributes, one for the photo and one for the thumbnail.
Then load your thumbnail in the CoreDataTableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *exceptions = [NSArray arrayWithObjects:#"SCR", #"DNS", #"NT", #"ND", #"NH", nil];
static NSString *CellIdentifier = #"resultsDisplayCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
MarksFromMeets *athleteMarks = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString* date = [ITrackHelperMethods dateToAbbreviatedString:athleteMarks.meetDate];
NSMutableString *title = [NSMutableString stringWithFormat:#"%#", athleteMarks.markInEvent];
NSMutableString *subTitle = [NSMutableString stringWithFormat:#"%# - %#",date, athleteMarks.meetName];
[title replaceOccurrencesOfString:#"(null)"
withString:#""
options:0
range:NSMakeRange(0, [title length])];
// cell.imageView.image = athleteMarks.photoThumbNail; // Don't like image in front of record.
[cell.textLabel setFont:[UIFont
fontWithName:#"Helvetica Neue" size:18]];
[cell.detailTextLabel setFont:[UIFont fontWithName:#"Helvetica Neue" size:16]];
[cell.detailTextLabel setTextColor:[UIColor grayColor]];
// make selected items orange
if ([athleteMarks.eventPR integerValue] != 0
&& (![exceptions containsObject:athleteMarks.markInEvent])) {
title = [NSMutableString stringWithFormat:#"%# (PR)",title];
[cell.textLabel setTextColor:[UIColor redColor]];
}
else if ([athleteMarks.eventSB integerValue] != 0
&& (![exceptions containsObject:athleteMarks.markInEvent])) {
title = [NSMutableString stringWithFormat:#"%# (SB)",title];
[cell.textLabel setTextColor:[UIColor orangeColor]];
} else {
[cell.textLabel setTextColor:[UIColor grayColor]];
}
cell.textLabel.text = title;
cell.detailTextLabel.text = subTitle;
cell.indentationLevel = indentationLevelOne;
cell.indentationWidth = indentationForCell;
return cell;
}
If you want, I can send you an example of a Category for an Entity's NSManagedObject Sub-Class. This Category loads the photo and the thumbnail into CoreData Entity. The first time will be slow. However, after that the user should be able to scroll through TableView smoothly and then all the updated results will load automatically. Let me know.
One nice thing is that CoreData handles all the memory management.
Good luck!
I don't have enough rep to comment, So here's an answer which helped my tableview scrolling performance:
Make the tableview height larger than the viewable window. Cells will load "off screen" and helps improve scroll smoothness.
Do your image processing in the following method:
-(void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
Those two tricks got my table flowing really nice. I'm getting my image data from an API service and AFNETWORKING has an awesome image loader, but not necessary for you since images are in the bundle.
Maybe you could try SDWebImage instead. It is also a xamarin component
which fashions an asynchronous image downloader and asynchronous memory and disk image caching with automatic cache expiration handling. Using it would probably mean throwing away a lot of hard written code, but it might be worth it -plus your code will become a lot simpler. In iOS you can also setup a SDWebImageManager inside the viewDidLoad of a controller:
- (void)viewDidLoad{
...
SDWebImageManager *manager = [SDWebImageManager sharedManager];
manager.delegate = self;
...
}
and set the view controller as the delegate. Then, when the following delegate method is called:
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL
you could scale your images to thumbs of the appropriate size before caching them.
Hope that helps.
Weel I had a similar problem, my scroll was not smooth. I am inserting in the table a variable UIImageView with inside labelViews.
What I did was to change the method HeightforRowAtIndexPath for estimatedHeightforRowAtIndexPath and now scroll is smooth.
I have a tableview in my view-controller and each tableview cell is associated with an audio file. I'm using custom tableview cells that have a UIView inside. The UIView for each cell has a unique UIBezierPath that is stroked in blue. When the cell is tapped, didSelectRowForIndexPath initiates playing the audio file and the blue UIBezierPath is stroked over with a red UIBezierPath in relation to the progress of the audio file.
float progress = playTime/duration;
self.redWave.strokeEnd = self.progress;
This is working just fine. What I noticed is that when I tap the first cell, the audio will start playing and the red path will begin to get stroked - which is suppose to happen, but if I scroll down while this is happening, the 5th cell down from the one I tapped has the same UIBezierPath and it is also stroking the red path over the blue path! Its clear that the cell is being reused. Also, this isn't just happening when I tap a cell - the UIBezierPath's of the first 5 cells are replicated in the next set of 5 cells by default when my tableview loads. I don't know how to get around this. Any ideas?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* identifier = #"audioTableCell";
OSAudioTableCell *cell = [self.audioTable dequeueReusableCellWithIdentifier:identifier];
if(cell == nil)
{
cell = [[OSAudioTableCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: identifier];
}
//Pull data from NSFetchResultsController
Recording * recording = [self.managedDocument.frc objectAtIndexPath:indexPath];
cell.waveView.minAmpl = [recording.minAmpl floatValue];
//UIBezierPath Data
NSData * pathData = recording.waveData;
//set path for UIView (waveView is custom UIView class)
cell.waveView.path = [NSKeyedUnarchiver unarchiveObjectWithData:pathData];
[cell setImageSize:cell.waveView.bounds.size];
cell.tag = indexPath.row;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//tableCell textfield
NSString * track = #"Track";
NSInteger row = indexPath.row;
row = [self.managedDocument.frc.fetchedObjects count] - row;
track = [track stringByAppendingFormat:#" %li",(long)row];
cell.trackLabel.text = track;
[cell.textField setFont:[UIFont fontWithName:#"Raleway-SemiBold" size:12]];
cell.textField.delegate = self;
if([recording.trackTitle isEqualToString:#""])
{
cell.textField.text = track;
}
else
{
cell.textField.text = recording.trackTitle;
}
UIColor * circleColor = [UIColor colorWithRed:107/255.0f green:212/255.0f blue:231/255.0f alpha:1.0f];
cell.trackIcon.backgroundColor = circleColor;
[cell.trackIcon.layer setCornerRadius:cell.trackIcon.bounds.size.width/2];
cell.trackIcon.layer.masksToBounds = YES;
return cell;
}
didSelectRowAtIndexPath calls the following method in my tableViewCell class:
-(void) rowSelected2: (NSURL*) url
{
self.audioPlayer = [OSTablePlayerController getInstance];
NSData *data=[NSData dataWithContentsOfURL:url];
[self.audioPlayer playAudio:data];
self.audioPlayer.isPlayerPlaying = YES;
self.timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:#selector(updateProgress:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
Table views are designed to reuse the rows. It's a feature that can improve the performance of your app, especially if your table view has a lot of elements in it.
Since table views reuse their rows, you need to manually clear your rows before they are recycled. If you implemented your cells by creating a UITableViewCell subclass, then you can override its
- (void)prepareForReuse
method. This gets called each time a cell is about to be reused, and it is the perfect location for doing any clean up that might need to happen.
Many apps that play media in tableview cells (e.g. tumblr) stop the playback as the cells are scrolled away (probably using prepareForReuse).
If you want to continue the playback, you'll need to keep all of the state associated with that playback outside of the table cell (in your model) including the playback progress. Your custom cell should be prepared to indicate playback progress starting at any point (from 0-99%). When a cell comes into view (in your datasource cellForRowAtIndexPath), you'll be required to check your model to see if that cell's media is playing and have it draw the current progress (hopefully it has a method setProgress:(float)progress that changes a progress property and tells that bezier drawing view to setNeedsDisplay).
This also means that if there's a timer set to check the media playback progress, it's target cannot be the cell, but rather the view controller that maintains the model.
I have an UITable with just 6 custom cells. Each CustomCell have a horizontal scroll view which have custom Views into it. Each CustomView has a ImageView and Text on it.
So to put it all together it may look like this
UITable --> CustomCell ---> Horizontal ScrollView --> CustomView --> ImageView and Text
Here is the code for Cell in UITable
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MySecondIdentifier = #"MySecondIdentifier";
UITableViewCell *cell2 = [tableView dequeueReusableCellWithIdentifier:MySecondIdentifier];
if(cell2 == nil){
cell2 = [(CustomCell* )[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MySecondIdentifier target:self row:indexPath.row parent:self];
}
[cell2 setSelectionStyle:UITableViewCellSelectionStyleNone];
[cell2 setValueToCellViewItem:tempTitleString setOfImage:dictCatData];
return cell2;
}
where DictCatData = NSMutableArray of data nodes
and tempTitleString = Title string for the cell (using it for some other purpose)
Here is how I set a CustomCell values
- (void) setValueToCellViewItem:(NSString *)pTitle setOfImage:(NSMutableArray *)catData{
[the_pScrolView setContentSize:CGSizeMake([catData count] * 107 , 107)];
int counter = 0;
for(NSDictionary *tempDict in catData){
NSString *url = [[NSString alloc]init];
url = [tempDict objectForKey:#"main_img_url"];
url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
UIImageView *mSdWebImage = [[UIImageView alloc] initWithFrame:CGRectMake(counter * 107, 0, 107, 107)];
[mSdWebImage setImageWithURL:[NSURL URLWithString:url] placeholderImage:nil];
[mSdWebImage setBackgroundColor:[UIColor grayColor]];
[the_pScrolView addSubview:mSdWebImage];
///Setting the title properties
UILabel *the_pLable = [[UILabel alloc] initWithFrame:CGRectMake((counter * 107) + 15, 85, 97, 22)];
the_pLable.textColor = [UIColor whiteColor];
the_pLable.font = [UIFont fontWithName:#"Helvetica" size:10.0];
the_pLable.numberOfLines = 1;
the_pLable.backgroundColor = [UIColor clearColor];
the_pLable.text = [tempDict objectForKey:#"title"];
[the_pScrolView addSubview:the_pLable];
counter++;
}
I am using SDWebImage for async downloading and caching as I think thats the best we have on net.
The ScrollView can contain images ranging from 0 to 30+ images
When I open this page on my iPhone, the images are getting downloaded an cached properly I guess, as I am able to see them with no difficulties
My Problem are
When I try to scroll up and down the table, the scrolling is not smooth. So how can I make it more smoother without effecting the background image downloading and caching
When I scroll the table up and down several times, the custom cells are redrawn I guess so the CustomCells with no images (i.e. no customViews in scrollView) show the images from other custom cells below/top.
Sometimes the app crashes, I guess this is issue of memory management.
How big are the images being downloaded? I had similar issues (without the extra horizontal scrolling) and I was able to fix it by using actual thumbnails of the images instead of the actual images (in the TableView).
You may want to try downloading, caching, and creating thumbnails of the images in a separate object and then letting the TableViewCell load those thumbnails of the images instead of the actual images.
This sped up scrolling perfectly for me.
To fix the reused cells showing wrong images, simply remove the images in cellForRowAtIndexPath before calling your code to display new images. That way if the images are delayed, at least the old ones have been removed or hidden.
I have an iOS app I'm working on that grabs a bunch of photo URLs from a MySQL database with a JSON request. Once I have these photos and related information, I use it to populate the datasource for a UITableView. I want to create a grid of UIButtons, made out of photos, 4 per row. This current code works, however it is wildly slow and my phone / simulator freezes right up as I scroll through the table. Tables with only a couple rows work fine, but once I reach 10 or more rows it slows right down and near crashes. I'm new to iOS and objective-c, so I'm assuming it's an inefficiency in my code. Any suggestions? Thanks!!
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
static NSString *CompViewCellIdentifier = #"CompViewCellIdentifier";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier: CompViewCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CompViewCellIdentifier] autorelease];
}
// The photo number in the photos array that we'll need to start off with.
NSUInteger photoNumber = (row * 4);
// Assemble the array of all 4 photos we'll need for this table row (for this cell).
NSMutableArray *rowPhotos = [[[NSMutableArray alloc] initWithObjects:[self.photos objectAtIndex:photoNumber], nil] retain];
NSInteger counter = 1;
while ([self.photos count] > photoNumber+counter && counter<4) {
[rowPhotos addObject:[self.photos objectAtIndex:photoNumber+counter]];
counter = counter+1;
}
NSLog(#"The rowPhotos array: %#", rowPhotos);
for (int i=0; i<[rowPhotos count]; i++) {
// Set which photo we're dealing with for this iteration by grabbing it from our rowPhotos array we assembled. Use i as the index.
NSDictionary *photoRow = [[NSDictionary alloc] initWithDictionary:[rowPhotos objectAtIndex:i]];
// Get the photo.
NSString *photoPath = [[NSString alloc] initWithFormat:#"http://localhost/photorious%#", [photoRow objectForKey:#"path"]];
NSURL *url = [NSURL URLWithString: photoPath];
[photoPath release];
UIImage *cellPhoto = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:url]];
// Figure out the container size and placement.
int xCoordinate = ((i*70)+8*(i+1));
CGRect containerRect = CGRectMake(xCoordinate, 0, 70, 70);
// Create the Button
UIButton *cellPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom];
[cellPhotoButton setFrame:containerRect];
[cellPhotoButton setBackgroundImage:cellPhoto forState:UIControlStateNormal];
[cellPhotoButton setTag:(NSInteger)[photoRow objectForKey:#"id"]];
// Add the button to the cell
[cell.contentView addSubview:cellPhotoButton];
// Add the action for the button.
[cellPhotoButton addTarget:self
action:#selector(viewPhoto:)
forControlEvents:UIControlEventTouchUpInside];
[cellPhoto release];
}
[rowPhotos release];
return cell;
}
This is slow because you do everything in tableView:cellForRowAtIndexPath:.
tableView:cellForRowAtIndexPath: Is called really ofter, especially each time a cell need to be displayed in your tableview, which includes when your are scrolling your tableView. Thus this method needs to be fast, and non-blocking (especially don't do synchronous downloads!)
Moreover your don't use the reusability of your tableview cells correctly. This drastically decrease performance as you recreate the content (subviews) for each cell each time.
When your cell is reused from a previous one (see it as being "recycled"), you must NOT redo everything, especially you must not re-add every subviews as there already are in the cell itself, as it has been reused and is not a clean new one!
Instead, when dequeueReusableCellWithIdentifier: returns a cell (= an old cell previously created but not used anymore so you can "recycle"/reuse it), you should only change what differs from cell to cell. In your example, typically you will only change the 4 images displayed, but don't recreate the UIImageView, neither add them to as a subview (as these subviews already exists) nor reaffect the target/action.
You only need to create the UIImageView, add them a target/action, set their frame and add them as a subview when your are creating a brand new cell, with alloc/initWithReuseIdentifier:/autorelease.
Moreover, you are fetching your images from the network directly in your tableView:cellForRowAtIndexPath:, and synchronously in addition (which means it blocks your application until it finished downloading the image from the net!!).
Do an asynchronous download instead, way before your tableView:cellForRowAtIndexPath: (when your app is loaded for example) and store them locally (in an NSArray, or sthg similar for example), and only fetch the local, already downloaded image in your tableView:cellForRowAtIndexPath:.
The thing you are trying to do is not the greatest idea to begin with if you are new to iOS programming. What you wanna do may seem easy, but it implies concepts like asynchronous downloads, MVC design of your app and prefetching the images from the net in your model before displaying them in your view, provide a way to update the tableview when the download is done, and the basic concepts of cell reuse in tableviews.
DO read the TableView Programming Guide before going further. It explains it in details and it really worth reading.
Also consult Apple's LazyTableImages sample code which explains how to load images in a tableview lazyly (meaning loading images asynchronously when they are needed), and the URL Loading Programming Guide which explains how to do asynchronous downloads of data.
These guides and samples are really worth reading if you want to do what you explain. There are also a lot of classes to do Grid Views on the net, one of them being my work (OHGridView), but you need to understand basics explained above and in the mentioned guides first before going further.