iOS Adding dynamic subview into uitableviewcell - ios

I am creating UITableViewCell like this. In that screen, I might have 1 quiz, 2 quiz, etc and 1 poll, 2 polls, etc. It will be dynamic.
As a result, when user scroll up and down, based on data received on my cell, I keep removing previous UIViews and recreating again and again. (I know it is super bad. Now my scrolling got issue.)
NSArray *quizzez = self.cellData[SERVER_QUIZZES];
NSArray *polls = self.cellData[SERVER_POLLS];
NSMutableArray *combinedQuizPoll = [NSMutableArray array];
[combinedQuizPoll addObjectsFromArray:quizzez];
[combinedQuizPoll addObjectsFromArray:polls];
for (UIView *vw in self.quizPollViewCollection) {
[vw removeFromSuperview];
}
for (NSDictionary *quizPollDict in combinedQuizPoll)
{
QuizPollSubView *vwQuizPoll = [QuizPollSubView loadFromNibWithType:QuizPollSubViewNoViewRelated andNavType:self.navType];
[vwQuizPoll setW:CGRectGetWidth(self.frame)];
[vwQuizPoll setDelegate:self];
[vwQuizPoll setData:muQuizPollDict];
[vwQuizPoll setX:0 andY:offset];
[self.contentView addSubview:vwQuizPoll];
offset = CGRectGetMaxY(vwQuizPoll.frame) + 4;
[self.quizPollViewCollection addObject:vwQuizPoll];
}
How shall I make to improve performance? I have studied other similar question in StackOverflow also.
How to make a UITableViewCell with different subviews reusable?
1) I need to have dynamic quiz, poll view (number of quiz, poll will be different for each cell)
2) How can I reference to those view that I created?

First of all I have to say that your approach to use the same cell to put things in a vertical is not the best one. For this kind of situation you should use more than one cell. Something like:
...
DecriptionCell
QuizCell
QuizCell
PollCell
PollCell
PollCell
...
Anyway I'm going to propose you a solution that could help you without change the structure of your UITableView.
Actually I had the same problem a few weeks ago, and I found a very good solution for that.
Basically the main concept is, to Reuse UITableViewCell you shouldn't add or remove views in the configure of the cell because the performance will be affected.
So, the solution that I have used was, use different reuse identifier for each kind of configuration that the cell can have.
The unique requirement is not to have a Nib file for the cell.
If I understood properly your cell can have dynamics Quizs and Polls. Let's go to say that a maximum of 10 Quizs and a Maximum of 10 Polls. Although I'm watching that both have the same View, QuizPollSubView. So let's put a maximum of 20 subviews per cell.
So in the method where you are registering the cells I would do the next:
Class myClass = [CustomTableViewCell class];
NSString *classID = NSStringFromClass(myClass);
for (NSUInteger index = 0; index < 20; index++) {
NSString *identifier = [classID stringByAppendingString:[#(index) stringValue]];
[self.tableView registerClass:myClass forCellReuseIdentifier:identifier];
}
Then in the CellForRow you must dequeue the cell with the properIdentifier, for instance:
NSString *cellID = NSStringFromClass([CustomTableViewCell class]);
NSUInteger numberOfQuizsAndPolls = 3 + 2; //This is 3 quizs and 2 polls, I gess that you can read from the DataModel
NSString *identifier = [cellID stringByAppendingString:[#(numberOfQuizsAndPolls) stringValue]];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
//then configure the cell
Next, in the initWithStyle:reuseIdentifier: you should create the subviews with empty value, extracting the info from the identifier
NSString *stringNumber = [reuseIdentifier stringByReplacingOccurrencesOfString:NSStringFromClass([self class])
withString:#""];
NSUInteger numberOfSubviews = [stringNumber integerValue];
//here you should add all of your QuizPollSubView with emtpy content.
for (NSUInteger index = 0; index < numberOfSubviews; index++) {
QuizPollSubView *vwQuizPoll = [QuizPollSubView loadFromNibWithType:QuizPollSubViewNoViewRelated andNavType:self.navType];
[vwQuizPoll setW:CGRectGetWidth(self.frame)];
[vwQuizPoll setDelegate:self];
//[vwQuizPoll setData:muQuizPollDict]; YOU CAN NOT SET THE DATA HERE BECAUSE YOU DONT HAVE IT
[vwQuizPoll setX:0 andY:offset];
[self.contentView addSubview:vwQuizPoll];
offset = CGRectGetMaxY(vwQuizPoll.frame) + 4;
[self.quizPollViewCollection addObject:vwQuizPoll];
}
Finally you must to set the proper information in the configure of the cell. Something like:
- (void)configureWithQuizPollDict:(NSDictionary *)combinedQuizPoll
{
for (NSDictionary *quizPollDict in combinedQuizPoll)
{
//get the proper index in the quizPollViewCollection.
QuizPollSubView *vwQuizPoll = self.quizPollViewCollection[index];
[vwQuizPoll setData:muQuizPollDict];
}
}
I hope that it helps you!!
Thanks
PD: If you want to use a Cell with Nib probably we need to subclass the UITableView to add custom dequeue

Related

Add subviews in runtime to UITableviewCell configured with xib using autolayout

There is a UITableview subclass, that has *.xib file, with configured autolayout in it (like the image below, in xib all dependencies are configured for 2 label-style). This cell can calculate it's height dynamically, as usual autolayout cell.
But there is a case, when according to received data,the number of labels can vary - dependent of data in model.
Is there a way to add several labels (3,5,n) as subviews to cell, with existing autolayout system? It's important because cell should not lose ability to self-calculate its size
Since you are using .xibs (great for several reasons) the safest, least-cumbersome and most performant way is to register your .xib with different resue identifiers. Best to scan your model to find out and register which ones you'll need (err on the side of registering too many; make a mistake the other way and instant crash). In a subclass, override -initWithStyle:reuseIdentifier: and do setup there.
There are a couple ways to do setup.
IB heavy: Create a cell with the maximum number of labels. Attach them to each other, and attach each one to the bottom of the cell content view with a separate constraint, and rank the constraints in priority with 1000 being the label attached to the bottom with none removed and priorities going down from there. Then remove the labels you don't want in -initWithStyle:reuseIdentifier:; their constraints will be removed, allowing the next-lowest priority to go into effect.
Looping: For an arbitrary number, you can add the labels and their constraints in a -initWithStyle:reuseIdentifier: -- resuse identifier could be +stringWithFormat for the loop number, which would let you get the number with -intValue. This may be too cutsy though, so you might want to try creating a method -formatWithNumberOfLabels and checks a -didSetup flag.
EDIT: Option 2 was far more cumbersome than I expected. This example is programmatic, but you should be able to see how to adapt it for .xibs. In the cell's impementation:
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self.contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSMutableDictionary *layoutDictionary = [[NSMutableDictionary alloc] init];
NSMutableString *visualFormatLanguageString = [[NSMutableString alloc] init];
NSInteger numberOfLabels = reuseIdentifier.integerValue; //This is not that safe, using below class method is the only way to make it safe to do this.
for (int i = 0; i <= numberOfLabels; i++) {
if (i == 0) {
[visualFormatLanguageString appendString:#"V:|-20-"];
} else {
[visualFormatLanguageString appendString:#"-8-"];
}
UILabel *labelX = [[UILabel alloc] init];
[labelX setTranslatesAutoresizingMaskIntoConstraints:NO];
int tag = 1000 + i;
labelX.tag = tag;
NSString *labelXString = [NSString stringWithFormat:#"Label%i", tag];
[layoutDictionary setObject:labelX forKey:labelXString];
[visualFormatLanguageString appendString:[NSString stringWithFormat:#"[%#]", labelXString]];
[self.contentView addSubview:labelX];
}
[visualFormatLanguageString appendString:#"-20-|"];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualFormatLanguageString
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:layoutDictionary]];
}
return self;
}
+ (NSString*)reuseStringForNumber:(NSInteger)reuseNumber {
return [NSString stringWithFormat:#"%li",(long) reuseNumber];
}
In the TableView Data Source you must -- for the maximum number of labels you think you need - register:
- (void)viewDidLoad {
[super viewDidLoad];
for (NSInteger i = 0; i < 4; i++) {
[self.tableView registerClass:[MultiLabelTableViewCell class] forCellReuseIdentifier:[MultiLabelTableViewCell reuseStringForNumber:i]];
}
self.tableView.estimatedRowHeight = 44;
}
For an example that creates number of labels (row % 4) + 1:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger cellNumber = indexPath.row % 4;
MultiLabelTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MultiLabelTableViewCell reuseStringForNumber:cellNumber] forIndexPath:indexPath];
for (int i = 0; i <= cellNumber; i++) {
int tag = 1000 + i;
[(UILabel *)[cell.contentView viewWithTag:tag] setText:[NSString stringWithFormat:#"Cell %i, label %i", indexPath.row, i]];
}
return cell;
}
Then just call the reuseIdentifier you need in cell for row at index path, and both you and the system will have to do the minimum work to get the correct height and layout for your cell.

Border of each UIView

I have to create something similar to above mentioned image. I have a view and i need border after eat view like {breakfast, brunch,etc..}
for giving border I was have following code.
CALayer *bottomBorder = [CALayer layer];
bottomBorder.frame = CGRectMake(0.0f, 43.0f, menuHeadView.frame.size.width, 1.0f);
bottomBorder.backgroundColor = [UIColor colorWithWhite:0.8f
alpha:1.0f].CGColor;
[menuHeadView.layer addSublayer:bottomBorder];
it works fine for single view but if I have an array for those views and I want to have border after each view using indexPath it doesn't work since it doesn't allow
[array objectAtIndex:i].layer;
help please! Thanks in advance.
If you have array of views you probably should cast:
Change :
[array objectAtIndex:i].layer;
To:
((UIView*)[array objectAtIndex:i]).layer;
EDIT:
Based on #Mischa comment:
Casting is telling the array what object is in it. If you have different kind of objects in array (you really shouldn't!) or to be super safe you can change casting to assign:
id myUnknownObject = [array objectAtIndex:i];
if([myUnknownObject isKindOfClass:[UIView class]]) {
((UIView*)myUnknownObject).layer...
} else {
NSLog(#"%#",[myUnknownObject class]);
}
But base on your comment you contain NSStrings in your array so you can't add layer to this objects. Please check your console, it's probably NSString.
Of course since you mentioned indexPath, and if you're using TableView with rows. You should use the good stuff from reusability and it method:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary *cellDic = [self.heartbeats objectAtIndex:indexPath.row];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
//here goes reusability part like styling and stuff, this will calling only once for every reusable cell. So for example for cells height 44 this will called only 13 times for iPhone screen.
//HERE you should manipulate with layer!
}
//here goes your custom text of every cell - like "Breakfast", "Brunch" and so on. This will calls for every row which is about to show up.
//HERE you should manipulate with text
return cell;
}

Collection View,with custom layouts, cells misbehave on scrolling

I am trying to create custom tiled layout using UICollectionView.
It renders perfectly as desired in simulator once I run my app.
But the moment I scroll the view and bring it back all the cell's frame changes and the cells get overlapped, leaving spaces, randomly.
I am not able to solve this issue past 2 days.
Here goes the code from my custom layout class.
-(void)prepareLayout{
[self createCellSizeArray];//cellSizeArray holds cell sizes for all the cells(calculated statically)
[self createAttributeArrayOfAll];//attributeArrayOfAll holds attributes for all the cells and also calculates their frames using cellSizeArray
}
-(CGSize)collectionViewContentSize{
return CGSizeMake(768, 1500);//The size is static to check for scrolling, is this creating problems?
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes * layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
return layoutAttributes;
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *attArray =[NSMutableArray array];
for (NSInteger i =0; i< attributeArrayOfAll.count; i++) {
UICollectionViewLayoutAttributes * attribute = (UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:i];
if(CGRectIntersectsRect(rect, attribute.frame)){
[attArray addObject:attribute];
}
}
return attArray;
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
Please help, Thanks in advance.
Edit:
In my [self createAttributeArrayOfAll]; I have these lines of code
CGRect frame = CGRectMake(_startNewRowPoint.x, _startNewRowPoint.y, cellSize.width, cellSize.height);
UICollectionViewLayoutAttributes * attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
attribute.alpha = 1.0;
attribute.frame = frame;
[attributeArrayOfAll addObject:attribute];
While I modified layoutAttributesForItemAtIndexPath:, to look something like this
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes * layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
layoutAttributes.frame = ((UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:indexPath.item]).frame;
return layoutAttributes;
}
Moreover, the method layoutAttributesForItemAtIndexPath: never gets called implicitly. I even tried this:
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *attArray =[NSMutableArray arrayWithCapacity:attributeArrayOfAll.count];
for (NSInteger i =0; i< attributeArrayOfAll.count; i++) {
UICollectionViewLayoutAttributes * attribute = (UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:i];
if(CGRectIntersectsRect(rect, attribute.frame)){
[attArray insertObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]] atIndex:i];
}
}
return attArray;
}
But still the result is the same distorted set of cells on scrolling.
I worked with 5 cells, first time it renders correctly, on scrolling away and then bringing it back in visible rect it gets distorted, if i scroll away again and bring it back in visible rect it renders correctly. However, when I do this with around 400 cells, once i scroll it never renders correctly. Even on reloading collection view, The cells gets distort. Please help.
Your layoutAttributesForItemAtIndexPath: method is not setting any properties of the layoutAttributes object before returning it. It needs to set frame (or center and size).
So finally managed a workaround!!! dequeue each cell with an unique Cell Identifier in cellForRow:
[self.summaryView registerClass:[BFSSummaryViewCell class] forCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d",CellIdentifier,indexPath.row]];
UICollectionViewCell *collectionCell = [collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d",CellIdentifier,indexPath.row] forIndexPath:indexPath];
These two lines inside cellForRow worked for me, however with my collection view having around 1000 cells it increases the size of my application considerably. Lets hope apple fixes this bug asap.

UICollectionView Performance Problems on performBatchUpdates

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.

What's the most efficient way to handle a UIButton Photo Grid in a UITableView?

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.

Resources