I am using the new prefetchDataSource of UITableView (also can be applied to UICollectionView. The problem that I am having is the following:
I am implementing func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) which works pretty fine.
I am using an array which starts with 15 elements. So in numberOfRowsInSection will initially return array.count which is 15.
What I want is for the prefetchRowsAt function to tell me when we're runing out of the rows, so that I fetch new data from my own source, append these data to the array and then reloadData(), which will in turn increase the numberOfRowsInSection.
Problem is that prefetchRowsAt won't go beyond row 15 because it is limited by the numberOfRowsInSection that I specified. So basically, I get stuck. Basically, when I am at row 5, I want it to tell me to start prefetching row 16.
One solution that I tried and worked would be to increase the count of numberOfRowsInSection to say 100, and then put placeholder cells while I am fetching the new data. But the scrolling bar will look too small as it is expecting that there is 100 items, so the UX won't be that nice.
Maybe the pattern that I use in step 3 is wrong (append to array and reloadData)? Or Can you think of another cleaner solution to the problem?
Thanks in advance!
I think I has understood your meaning: Your class have an mutableArray to store your Cell's data model to shown. Everytime, the count of resource request back is 15.
at first time: the count of data got is 15, you want to pre-making the request of next resources from server when user scroll to 5th indexpath cell .
After get reqeust, your mutableArray's count is 30
at second time: you want to pre-get next resources from server when user scroll to 25th indexpath cell .
After get reqeust, your mutableArray's count is 45
forever making request for next time resouces ,as long as user slides the screen
OK, It's not necessary that using prefetchDataSource(it is only usable iOS 10), rather than you can using dataSource. .
Of course,if you only run your app in iOS 10 , you can making net-request for meta data(like pre-caching images) in Cell by implement prefetch method
. If not, you can use cellForRowAtIndexPath(tableView) or cellForItemAtIndexPath(collectionView)
For UITableView
tableView:cellForRowAtIndexPath:
For UICollectionView
collectionView:cellForItemAtIndexPath:
Futher, I'm not suggest you using scrollViewDidScroll,
Becuase the height of the cell usually varies with the change of data
,if it will be more expensive.
your code can like this code below:(UICollectionView)
#interface YourCollectionView()
#property (nonatomic, strong) SDWebImagePrefetcher *imagePrefetcher;
#end
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// Check current indexPath
NSInteger distance = self.array.count - indexPath.row-1;
if (distance <= 10) {
[self loadingMoreData];
}
// Preload the ten image into the cache, if you need your app to be compatible with iOS10 below.
NSMutableArray <NSURL*> *URLsArray = [[NSMutableArray alloc] init];
for (NSInteger i = 1; i <= 10; i++) {
NSInteger y = currentLastIndexRow + i;
if (y > self.array.count-1) {
break;
}
YourModel *model = self.array[indexPath.item];
ImageModel *image = model.image;
[URLsArray addObject:[NSURL URLWithString:image.httpsURL]];
}
[self.imagePrefetcher prefetchURLs:URLsArray];
YourCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
// setting your Cell
[cell setNeedsLayout];
return cell;
}
If you do not need your app to be compatible with iOS10 below,you can put prelaod code to prefetch method.
BTW: If you think my answer working, please do not hesitate to adopt it : )
#Updates: About how to reload: You can monitor array by using KVO,
like:
[self addObserver:self forKeyPath:#"array" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
and implement this method
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"array"])
{
if(!change) return;
id oldC = [change objectForKey:NSKeyValueChangeOldKey];
id newC = [change objectForKey:NSKeyValueChangeNewKey];
UICollectionView *cv = selfWeak.collectionView;
// changes is non-nil, so we just need to update
[cv performBatchUpdates:^{
// delete reload insert..
} completion:^(BOOL finished) {
// do somethind , like scrollToItemAtIndexPath
}];
}
}
I think you should start loading your new data asynchronously and just insert the new rows: func insertRows(at:with:) instead of reloadData (you are inserting data, not reloading it).
You can start loading the new data as soon as the prefetch method hits the last row (or before). Once it's loaded then you can call beginUpdates(), update your array, insert the new rows and finally endUpdates().
See more in the docs: Batch Insertion, Deletion, and Reloading of Rows and Sections
Well, working of PrefetchDelegate is bit different.
You need to tell the tableview that total how many rows are there in your system.
so basically you need to pass the total count of object table view is going to display in cellForRowAtIndexPath.
Once this is done, prefetchRowsAt will start calling and let you know which row it's going to display and there you should apply your logic of API call.
Let me know if you need more clarification.
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.
Our app has a tableview that allows users to minimize/maximize its sections. There are also animations tied to hiding/showing rows depending on the user's input, as well as being connected to different devices (via Bluetooth 4.0). Because of the different sources of animations, we set up a scheduler to handle one animation at a time so that we didn't get any crashes tied to the number of rows/sections being out of sync after a [tableview endUpdates]. It's been working really well for our purposes.
However, a new section in our table has been giving me a bit of grief. Depending on the device that is connected to the iPad, the section either has 1 row or it has 8-9 rows. The rest of the rows have a height of 0. The section minimizes/maximizes just fine when it has 8-9 rows. When the section only has one row though, the insertRowsAtIndexPaths animation for the table does not complete until the section's row is off the screen. So unless the user changes tabs or scrolls down far enough for the table to push it off the screen, no other section can be minimized/maximized due to our scheduler checking first if it's already busy. Here is the code that gets called for the animations:
-(void)animateTableUsingAnimationType:(NSNumber*)aniType
{
static int animationID = -1;
if(tableAnimationBusy)
{
[self performSelector:#selector(animateTableUsingAnimationType:) withObject:aniType afterDelay:.05f];
}
else
{
tableAnimationBusy = true;
tat = [aniType intValue];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
NSLog(#"Animation %d is complete!", tat);
tableAnimationBusy = false;
}];
//if-else if blocks checking for the animationID
//if open section 2
[self animateOpenTableSection:2]
else //undefined ID
tableAnimationBusy = false;
[CATransaction commit];
[self setUpLabelText];
}
}
and the animateOpenTableSection is:
-(void)animateOpenTableSection:(NSInteger)section
{
NSLog(#"Calling open on section: %d", section);
if (section == 2)
{
[self.tableView beginUpdates];
openFlagSettingsRowCount = fsr_COUNT; //max row count for section
[[MCUISectionHandler sharedInstance] sectionTouched:YES forSection:MCUISH_SECTION_NAME__FLAG];
NSMutableArray *indexPathsToInsert = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < fsr_COUNT; i++) {
[indexPathsToInsert addObject:[NSIndexPath indexPathForRow:i inSection:section]];
}
[self.tableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
}
As you can see, I have debug statements in the animateOpenTableSection function, as well as the completion block in animateTableUsingAnimationType. The latter never gets called until the single row is off the screen. Any ideas as to why the tableview wouldn't let go of the CATransaction?
We were able to solve this problem and I forgot to post what I found out about our app. The reason the tableview wouldn't let go of the CATransaction was because of UIActivityIndicator subviews in the rows. They were being animated and so the CATransaction never ended because it was waiting for one or more UIActivityIndicators to stop animating.
So for those who may run into this problem, check to see if any subviews are being animated.
We are trying to set up a UICollectionView with a custom layout. The content of each CollectionViewCell will be an image. Over all there will be several thousand images and about 140-150 being visible at one certain time. On an action event potentially all cells will be reorganized in position and size. The goal is to animate all the moving events currently using the performBatchUpdates method. This causes a huge delay time before everything gets animated.
This far we found out that internally the method layoutAttributesForItemAtIndexPath is called for every single cell (several thousand in total). Additionally, the method cellForItemAtIndexPath is called for more cells than can actually be displayed on the screen.
Are there any possibilities to enhance the performance of the animation?
The default UICollectionViewFlowLayout can't really offer the kind of design we want to realize in the app. Here's some of our code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
RPDataModel *dm = [RPDataModel sharedInstance]; //Singleton holding some global information
NSArray *plistArray = dm.plistArray; //Array containing the contents of the cells
NSDictionary *dic = plistArray[[indexPath item]];
RPCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CELL" forIndexPath:indexPath];
cell.label.text = [NSString stringWithFormat:#"%#",dic[#"name"]];
cell.layer.borderColor = nil;
cell.layer.borderWidth = 0.0f;
[cell loadAndSetImageInBackgroundWithLocalFilePath:dic[#"path"]]; //custom method realizing asynchronous loading of the image inside of each cell
return cell;
}
The layoutAttributesForElementsInRect iterates over all elements setting layoutAttributes for allthe elements within the rect. The for-statement breaks on the first cell being past the borders defined by the bottom-right corner of the rect:
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray* attributes = [NSMutableArray array];
RPDataModel *dm = [RPDataModel sharedInstance];
for (int i = 0; i < dm.cellCount; i++) {
CGRect cellRect = [self.rp getCell:i]; //self.rp = custom object offering methods to get information about cells; the getCell method returns the rect of a single cell
if (CGRectIntersectsRect(rect, cellRect)) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[dm.relevanceArray[i][#"product"] intValue] inSection:0];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attribute.size = cellRect.size;
attribute.center = CGPointMake(cellRect.origin.x + attribute.size.width / 2, cellRect.origin.y + attribute.size.height / 2);
[attributes addObject:attribute];
} else if (cellRect.origin.x > rect.origin.x + rect.size.width && cellRect.origin.y > rect.origin.y + rect.size.height) {
break;
}
}
return attributes;
}
On Layout changes the results are pretty much the same no matter if the number of cells being defined in the layoutAttributesForElementsInRect is limited or not .. Either the system gets the layout attributes for all cells in there if it isn't limited or it calls the layoutAttributesForElementAtIndexPath method for all the missing cells if it is limited. Overall the attributes for every single cell is being used somehow.
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
RPDataModel *dm = [RPDataModel sharedInstance];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect cellRect = [self.rp getCell:[dm.indexDictionary[#(indexPath.item)] intValue]];
attribute.size = cellRect.size;
attribute.center = CGPointMake(cellRect.origin.x + attribute.size.width / 2, cellRect.origin.y + attribute.size.height / 2);
return attribute;
}
Without seeing code, my guess is that your layoutAttributesForElementsInRect method is iterating through all of the items in your collection, and that is, in turn, what is causing the other methods to get over-called. layoutAttributesForElementsInRect is giving you a hint - that is, the CGRect it is passing to you - about which items you need to call layoutAttributesForItemAtIndexPath for, in terms of what is on the screen.
So that may be part of the performance issue - that is, tuning your custom layout so it is being smart about which items it is updating.
The other issues pertain to animation performance in general. One thing to watch out for is if there is any sort of compositing going on - make sure your images are opaque. Another thing is if you're using shadows on your image, those can be expensive to animate. One way to improve the animation performance of shadows is set the shadowPath value when the image is resized - if you do have shadows, let me know and post some code for doing that.
This appears to be caused by trying to add cells to sections which have header views but no cells already in them.
The error message is monumentally unhelpful, and it took several hours of effort to trace it down.
I'm pretty sure there is an easier way to do this that I'm missing in all my reading...but this is driving me insane. I am very tired - and do apologize if this gets verbose.
I'm using an NSFetchedResultsController in my app - very standard issue - and it works. Two sections, ordered properly, etc. When a user taps a cell in the first section (the BOOL value of the property creating the two sections is reversed) - it moves to the second section (BOOL value flipped again) - and vice versa...again, works famously.
Here's where my implementation gets a little funky. Eventually I want to support iAds - according to the HIG these should be placed at the bottom of the screen...the easiest way I found to do this is to put the UITableView inside a UIView, to avoid some logical reasons that escape me now - but that's what I have - UIView with a UITableView.
I'm using Storyboards with prototype cells - two to be specific. One is used for adding content and acts as the delimiter between the two sections. The other is the default cell to display. When I register IBOutlets for this cell - it throws an exception (which is an issue, but I'm able to work around it). So, instead, what I've been doing is using cell.contentView.subviews to get to the elements I need to mess with:
NSArray *cellSubViews = cell.contentView.subviews;
if ([cellSubViews count] == 3) {
// editing accessory view
cell.editingAccessoryType = UITableViewCellAccessoryDisclosureIndicator;
// default background image view
cell.backgroundView = self.defaultClockCellBackgroundImageView;
// clock name label
UILabel *clockNameLabel = [cellSubViews objectAtIndex:2];
clockNameLabel.text = clock.name;
clockNameLabel.textColor = (indexPath.section == 0 && ![clock.isAddClockCell boolValue])
? [UIColor whiteColor]
: [UIColor lightTextColor];
// view child button
UIButton *viewChildButton = [cellSubViews objectAtIndex:0];
UIImage *viewChildClocksButtonBackgroundImage;
if (self.clockForFetch != nil) {
//NSLog(#"child clocks list - N/A");
viewChildClocksButtonBackgroundImage = [UIImage imageNamed:#"childcell_blank"];
viewChildButton.enabled = NO;
} else if ([clock.childClocks count] < 2) {
//NSLog(#"add child button");
viewChildClocksButtonBackgroundImage = [UIImage imageNamed:#"parentcell_addchild"];
} else if (![clock.isRunning boolValue]
|| [self totalChildClocksRunning:clock] < 1) {
//NSLog(#"view child button");
// children clocks cannot be running while parent is stopped;
// therefore, no need to actually check
// this solves the "wait for cell animation to end" issue
// without the need for tableview reload data
viewChildClocksButtonBackgroundImage = [UIImage imageNamed:#"parentcell_viewchild"];
} else {
//NSLog(#"view child running button");
viewChildClocksButtonBackgroundImage = [UIImage imageNamed:#"parentcell_viewchild_running"];
}
[viewChildButton setImage:viewChildClocksButtonBackgroundImage forState:UIControlStateNormal];
viewChildButton.hidden = NO;
// view time entries list for clock
UIButton *detailDisclosureButton = [cellSubViews objectAtIndex:1];
UIImage *detailDisclosureButtonBackgroundImage = (self.clockForFetch == nil)
? [UIImage imageNamed:#"parentcell_detaildisclosure"]
: [UIImage imageNamed:#"childcell_detaildisclosure"];
[detailDisclosureButton setImage:detailDisclosureButtonBackgroundImage
forState:UIControlStateNormal];
detailDisclosureButton.hidden = NO;
if (self.editing) {
viewChildButton.hidden = YES;
detailDisclosureButton.hidden = YES;
}
}
Please note: prior to this method I was using a separate NIB with IBOutlets and could replicate the issue - just not as consistently.
Here is the table with three cells that move when tapped (Alpha, Beta, and Gamma)...ah, no images to new sorry - wish they could have told me that before I uploaded the images - enter ASCII art:
---------------
| alpha |
---------------
| beta |
---------------
| gamma |
---------------
| *delimiter* |
---------------
Here is the table after tapping the Alpha and Beta cells:
---------------
| gamma |
---------------
| *delimiter* |
---------------
| beta |
---------------
| beta |
---------------
However, if I tap on the custom detail disclosure button in the first cell labeled "Beta" (which should read Alpha), I get the correct object passed to the detail view:
--------------------------------------------
| < back detail view for alpha edit |
--------------------------------------------
So, the fetched results controller is being updated - it just doesn't seem to be configuring things consistently when it talks to the table view. In the configure cell method above I've checked to see the names of the objects found in the controller - and they are also correct; i.e., the first beta cell (cell for row at index path 1,0) is Alpha - not Beta - even though the table view displays Beta.
I keep seeing mention of custom cells needing setNeedsDisplay and/or setNeedsLayout, but I don't think I'm quite understanding that one - though I have tried putting it almost everywhere I could think of in the code. "NSFetchedResultsChangeMove" is what gets called when the user taps a cell - and I've tried a lot of the variations I've seen on the web (including here) to fix glitches that I thought were similar - to no avail.
I'm sure this construct will mature over time (as this is a first go) but, as I said, I encountered the same issue with a completely different construct (storyboards didn't exist at the time) - and could still repeat the issue.
Any assistance would be greatly appreciated. And, if there is an answer on here for this exact same issue, I do apologize for the repeat.
So, here's what I did to solve the problem, it appears there are two possible contributors I could think of to the problem; hopefully this helps others if they find themselves in the same situation.
I have two different prototype cells: the default one and the delimiter one. So, calling configure cell at index path directly may cause unexpected results. Therefore, anywhere I might have called configureCell atIndexPath, I just called cellForRowAtIndexPath instead. For example, in my fetched results controller update table methods, I altered everything to call cellForRowAtIndexPath instead (specifically the change update case - which is normally calling configure cell for row at index path:
case NSFetchedResultsChangeUpdate:
//NSLog(#"NSFetchedResultsChangeUpdate");
[self.TJTableView cellForRowAtIndexPath:indexPath];
break;
Marching through the cell content view using subviews doesn't seem to maintain itself, and it's just not a very elegant way of doing things; so, I subclassed UITableViewCell with a custom cell per the instructional article here - http://bit.ly/oI2GGW - this way I could wire up the IBOutlets and reference the label and buttons directly in configure row at index path.
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.