In UITableViewCell, UIImageView content repeats after some cells using AFNetworking - objective-c

The content is distinct on all the cells on the first page but as we scroll down, only the UIImageView is repeated though the UILabels are distinct.
I am using [UIImageView setImageWithURL:(NSURL *)url] from AFNetworking which was included in the RestKit. Heres the implementation
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RClubTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"RClubTableViewCell"];
RClubObject * club = _clubs[indexPath.row];
[cell.clubImageView setImageWithURL:[NSURL URLWithString:club.clubImageURL]];
cell.nameLabel.text = club.clubName;
return cell;
}
Seems like iOS is somehow using the previously created cells. Would like to have completely fresh cells while scrolling.

You need to call setImageWithURL:placeholderImage: with a non-nil placeholder image to have the old image removed from the image view.

I guess you missed reallocation in your cellForRowAtIndexPath:
Your code should be something like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RClubTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"RClubTableViewCell"];
if(cell == nil)
{
cell = [[RClubTableViewCell alloc] init];
}
RClubObject * club = _clubs[indexPath.row];
[cell.clubImageView setImageWithURL:[NSURL URLWithString:club.clubImageURL]];
cell.nameLabel.text = club.clubName;
return cell;
}
Update :
Okay. I just had the same issue. Here what my search over google tells me : Since you are downloading the image in async and also scrolling, it gets downloaded and attached to other cells which are currently visible
Solution:
add tag to each imageView using indexpath.row
Use function setImageWithURL: placeholderImage:success:
In success block reload the cell using the tag value.
Though I haven't tested this I feel this should work. PS : Let me know if this works I also would have to do the same then :)

Yes, iOS is reusing the previously created cells. That is what this line of code does, so that the app does not have to spend the time creating a new cell from scratch:
RClubTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"RClubTableViewCell"];
Clear the image from the recycled cell before adding the new image for this cell:
cell.clubImageView.image = nil;
[cell.clubImageView setImageWithURL:[NSURL URLWithString:club.clubImageURL]];
cell.nameLabel.text = club.clubName;
return cell;
Alternatively, you could create a new cell from scratch instead of dequeuing one.

Related

Updating subviews in cells on a UITableView

I'm developing an application in iPad 6.0 using Storyboards.
Let me first explain my goal. I'm trying to achieve a Master-Detail (SplitViewController-like) View Controller using 2 UITableViewControllers.
The first UITableView("Master"), let's call this HeaderTableView, as the name implies, lists down the Headers for the...
...Second UITableView("Detail"), let's call this the EncodingTableView, which contains a programmatically changing CustomTableViewCell (subviews contained within each cell may be a UITextField, UIButton or UISwitch).
See EncodingTableView.m
- (void)updateEncodingFields:(NSArray *)uiViewList
{
// Add logic for determining the kind of UIView to display in self.tableView
// Finally, notify that a change in data has been made (not working)
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *encodingFieldsTableId = #"encodingFieldsTableId";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:encodingFieldsTableId];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
}
// Change text in textView property of CustomTableViewCell
cell.encodingFieldTitle.text = uiViewList.title;
// added methods for determining what are to be added to [cell.contentView addSubView:]
// data used here is from the array in updateEncodingFields:
}
My HeaderTableView.m, contains the didSelectRowAtIndexPath to update the EncodingTableView
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (![selectedIndexPath isEqual:indexPath]) {
selectedIndexPath = indexPath;
[self updateDataFieldTableViewForIndexPath:indexPath];
}
}
- (void)updateDataFieldTableViewForIndexPath:(NSIndexPath *)indexPath {
[self.encodingTableView updateEncodingFields:self.uiViewList];
}
Question
- Data is all ok but why doesn't EncodingTableView "redraw"ing the fields? My
suspicion is that reusing cells has something to do with this but I just can't figure out why.
Screenshots on the result:
Initial Selection in HeaderTableView
Second Selection in HeaderTableView
What I've tried :
I kept seeing suggestions such as [UITableView setNeedsDisplay],
[UITableView reloadData] and [UITableView setNeedsLayout] but none of
them worked.
Removing the reuse of tableViewCells works fine but this causes parts of my
CustomTableView.encodingFieldTitle to disappear. Not to mention that this might cause performance issues if I were to drop reusing cells.
Restrictions:
I know that a good idea is to use a SplitViewController but this is just a subpart of my app (hence not the RootViewController).
Finally, thanks for reading such a long post. ;)
It looks like you are most likely adding subviews inside tableView:cellForRowAtIndexPath:.
The issue is that if you use cell reuse then are not always starting from a blank slate inside tableView:cellForRowAtIndexPath: instead you can possibly be given a cell back that has already been configured once. This is what you are seeing, a cell that has previously had labels added to it is handed back to you and then you add some more labels over the top.
There are a few way to deal with this:
(My preferred option) Create a subview of UITableViewCell with these extra sub views available as properties.
Ensure the cell setup is only done once
A great place to do this is when you actually create a cell when one does not already exist e.g. inside the if (cell) check
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
// add subview's here and give them some way to be referenced later
// one way of doing it is with the tag property (YUK)
UILabel *subView = [[UILabel alloc] initWithframe:someFrame];
subView.tag = 1;
[cell.contentView addSubview:subView];
}
UILabel *label = (id)[cell.contentView viewWithTag:1];
label.text = #"some value";
One problem i can see in your code is that the cell identifiers used are different in tableView cellForRowAtIndxPath function.
While dequeueing you are using this identifier - > "encodingFieldsTableId"
&
while creating a cell you are using this identifier - > "dataFieldUiGroupTableId".
Ideally these two identifiers should be same !!!
Try adding,
cell.encodingFieldTitle.text = nil;
Before if(cell == nil)
So that whenever your cellForRowAtIndexPath method is called, the string already present in the cell you are going to reuse will get deleted and the new text in uiViewList.title will be displayed.

UITableViewDataSource method

Here is the CellForRowAtIndexPath method of UITableViewDataSource protocol. I saw that code on a website.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *TableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TableIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TableIdentifier];
}
cell.textLabel.text = [playersReady objectAtIndex:indexPath.row];
return cell;
}
My questions are:
Why when here defined cell wrote = [tableView dequeueReusableCellWithIdentifier:TableIdentifier]; ? What that means? If I commented that code everything going OK. For what that code? Hmmm...
How cell in if statement can be equals to nil if cell equals to TableIdentifier (SimpleTableItem)? For what wrote that code?
Why TableIdentifier equals to SimpleTableItem? For What?
An iPhone doesn't have a lot of memory. But even on modern computers you wouldn't want to initialize a new cell for every cell in your table. That's just wasting memory. So instead Apple came up with this idea of reusable cells. You only have to initialize a few cells that fill your screen (table view). Then, when the user scrolls down some new cells will appear at the bottom of the screen, but at the same time other cells will disappear at the top of the screen. So you can simply take those cells and reuse them.
Luckily UITableView manages this for you. All you have to do when you need to setup a new cell in that method is ask the table view, if it has any cells available that can be reused. If there are reusable cells, dequeueReusableCellWithIdentifier: will return one of them. But if there are non available yet (typically when you first fill up your table view with the initial cells) it will return nil. So you have to test if cell is nil and create a new cell from scratch if that's the case.
On iOS 6.0 there is a new method dequeueReusableCellWithIdentifier:forIndexPath: that always returns a valid cell (it creates the cell for you if there is no reusable cell yet).
Table view Create only those cell which can Display at one time on screen.After this system reuse cell for save memory .
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 20;
}
-(UITableViewCell *)tableView:(UITableView *)tableViewL cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableViewL dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSLog(#"Cell == nil so create a new cell....");
}else {
NSLog(#"Reuse Cell ");
}
return cell;
}
CellIdentifier use for identify cell for example if you add label on first ten table on 12 cell you add a button it give you a problem when you reuse cell.so that we need create a different cell For add button on cell and give it a Identifier string.

UITableViewCell with Checkmark not visible

I've a problem with my UITableView inside PopoverController.
When I touch cell, the didSelectRowAtIndexPath function is called, and the cell accessoryType is changed. Example simplified :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.listItems objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.tableView reloadData];
[self.popoverController dismissPopoverAnimated:YES];
}
It's working, the cell are checked, but it's not visible on my tableview : I can't see the blue checkmark. However, in touch state on the cell, the checkmark is visible in white (and the cell background is gray). But not visible in default state.
Do you have any idea why my checkmark are not visible in default state ?
Thanks,
Edit: Add screenshot, for a cell with accessoryType = UITableViewCellAccessoryCheckmark
This happened to me when I changed the global tint color to white. Once I realized, I went into the UITableView and change the tint color locally for just this table. Fixed.
I've tried the answer Jacky Boy - didn't help. But something was there in the deselection...
So I've tried to deselect the cell in the didSelectRowAtIndexPath: before adding the checkmark accessory:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UITableViewCell* selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if (row != _selectedRow) {
if (selectedCell.accessoryType == UITableViewCellAccessoryNone) {
selectedCell.accessoryType = UITableViewCellAccessoryCheckmark;
_selectedRow = row;
} else if (selectedCell.accessoryType == UITableViewCellAccessoryCheckmark) {
selectedCell.accessoryType = UITableViewCellAccessoryNone;
}
[tableView reloadData];
}
}
And for me it worked at last - the nice dark checkmark now is clearly visible on the cell!
Of course there is a part in cellForRowAtIndexPath: similar to described in arexx's answer.
I had a similar problem where, after reloading the row with a checkmark set as the accessory, the checkmark wouldn't be visible (but would be visible in white when the row was selected). In testing around the problem I discovered that the checkmark is always present in the cell, it's just white-on-white.
My understanding of the problem is that when I ask for the cell to be reloaded (so that I can show it with a checkmark), the existing cell is put on the reuse queue, but is at that time in a selected state (because the user just selected it). It's still in a selected state when the cell comes back off the reuse queue and you re-configure it in tableView:cellForRowAtIndexPath, and because it's selected, the accessory is set in white instead of in a visible colour.
To fix this, I added a line in tableView:cellForRowAtIndexPath to force the cell not to be selected. The accessory is now always visible when the new cell is displayed.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Get a reusable cell - this only works with Storyboard prototype cells
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
// THE MAGIC BIT
// Force the cell to not be in a selected state.
cell.selected = NO;
// END MAGIC BIT
if (indexPathIsPathOfSelectedRow) {
// This is the selected cell, so show the checkmark accessory.
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
// In case we're reusing a cell that previously showed a checkmark, clear it.
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Mine was the most stupidest reason. I had created a tableview in storyboard, with a View Controller of size 5.5 inch and forgot to apply the layout constraints.
Then I launched in a 4 inch phone, Everything looked fine except the accessory view was not visible because of tableviews width was greater than that of the phone screen. It took me 3 hours to find out my mistake.
You are reloading the UITableView so in theory the cells are recreated and this time without the checkmark. Do the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.listItems objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.popoverController dismissPopoverAnimated:YES];
}
Running under iOS 6.1, the behavior I see is a cell with a white check mark on an almost white background. This appears to be happening because the code that draws the check mark accessory believes the cell is in a highlighted state, so rather than drawing the check mark in the normal blue color, it is drawn in white.
Setting the selected state of the cell did not work for me but setting the highlighted state immediately before setting the accessory type did. With the following in place, I always get a dark blue check mark.
cell.highlighted = NO;
if (checked)
self.accessoryType = UITableViewCellAccessoryCheckmark;
else
self.accessoryType = UITableViewCellAccessoryNone;
If you want to reload Data then you should store selected Id in some variable for single selection like rowIndex and then in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//check index
if (rowIndex==indexPath.row)
{cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
Thanks.
-(UIImageView *)checkmarkImg{
UIImage *image = [[UIImage imageNamed:#"ic_check_black_24dp.png"] changeColor:CLR_BUY];
UIImageView *checkmark = [[UIImageView alloc] initWithImage:image];
return checkmark;
}
cell.accessoryView = [self checkmarkImg];

Confusion with storyboard and UITableView data source: How to display text in a cell

So I've been given an assignment in my Mobile apps class: make a color game app for the iphone.(The description of how to game works is at the top of the pasted viewcontroller.h file below.)
I'm very new to Objective-C and cocoa, but have managed to troubleshoot and fix a lot of things in this app. The problem I have right now is that I don't know how to properly initialize and send UITableViewCells to the view. I'm confused because all of the tutorials I've found online use datasource methods to change different attributes of the UITableView and the cells as well. I'm not sure how these methods will interact with the controls I've already placed. I'm confused because I added them by the storyboard file, not by defining tableview attributes with datasource code.
My immediate issue is that my program won't display the proper text to the cells textlabel and detailtextlabel.
I've looked everywhere online for UITableView and UITableViewCell tutorials, but they are all from years ago and I'm not sure if the advent of the storyboard has changed the way I would treat these controls.
All of the code I've written is either in the viewcontroller.m or viewcontroller.h files.
The method within ViewController.m file, that should call the cell and display text and detail text:
-(IBAction)enterClicked
{
//On enter- send instance colors to the colorTable row[i], perform comparisons and append the resulting symbols to the instanceResults String. Send instanceResults string to the resultTable row[i]. When game counter reaches 6, gameOver. If on comparisons check, the instanceColors are the same as the gameColors, then the player wins.
[self checkForLoss];
if(!self.gameOver)
{
resultOfGuess = [self comparePlayerInputToGameColors:guessColors];
[listOfGuesses addObject:guessColors];
[listOfOutcomes addObject:resultOfGuess];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_numberOfTurnsPlayed inSection:0];
UITableViewCell *thisCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
thisCell.textLabel.text = [self.listOfGuesses lastObject];
thisCell.detailTextLabel.text = [self.listOfOutcomes lastObject];
[guessColors setString:#""];
if([self checkForWin:resultOfGuess])
[UpdateLabel setText:#"You have won!"];
else
[UpdateLabel setText:#""];
self.colorCounter = 0;
self.isStepOne = YES;
_numberOfTurnsPlayed++;
}
else
{
if([self checkForLoss])
[UpdateLabel setText:#"You have lost!"];
}
}
The UITableView DataSource Methods I've called at the bottom of the viewcontroller.m file:
#pragma mark - UITableViewDataSource protocol
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if(section == 0)
return #"Guesses: Results:";
return 0;
}
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 6;
}
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
return cell;
}
So my questions are: Can I change a control's properties with datasource methods, if I created the controls through the storyboard? How do I properly display the text in a uitableview's cells?
Edit/update: Thank you, I've used your advice jrturton, but now I've found something peculiar that may be the source of my problems. in my viewController.h file I've changed my header from
ViewController: UIViewController to ViewController: UITableViewController
Thinking that the datasource methods I call within the viewcontroller files have to be able to call the same methods and properties of the class that I call in the header-- Also, I see this done in other UITableView tutorial files.
The problem is that when I change the header to read-- ViewController: UITableViewController -- and I try to compile, I get this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UITableViewController loadView] loaded the "2-view-3" nib but didn't get a UITableView.'
It compiles fine if I use just :UIViewController in the header file though.
Any ideas?
Further update: I''ve noticed within my storyboard that the only available ViewController object is a UIViewController object, while in the other tutorial files I've seen, this ViewController object is a UITableViewController object. I imagine this is my problem, but I can't seem to switch my UIViewController object to a UITableViewController. All I can do is create a new one, which isn't what I want, I imagine.
Your action method should update the data model (which I think it does, since it changes your listOfGuesses array). You then need to let your table view know that you have added or updated rows so that it can re-load them for you - check the UITableView documentation for reloading data or specific rows.
Creating a cell outside of the datasource methods isn't going to let that cell appear in your table.
At the moment I'm guessing you have 6 empty cells in your table view? You need to populate the text and detail labels in your cellForRowAtIndexPath method. The difference now there are storyboards is that you don't need to do the if (cell == nil) bit, as long as you have set the re-use identifier in your storyboard prototype cell then it will do all that for you. So your cellForRowAtIndexPath method can be reduced to:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
// This will dequeue or create a new cell based on the prototype in your storyboard
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
// Put your actual configuration here based on your model array
cell.textLabel.text = #"Hello";
return cell;
}
Further hints (this is homework so I'm not giving full samples)
'indexPath.row` in the above method will give you the index from your model array that the cell refers to
You have defined the table as having 6 rows, but you are adding items to your model arrays as you go - so when the table asks for row 5, and your model only has 3 entries, you need to deal with this. Consider changing the number of rows in the table dynamically and using table view methods to indicate that new rows have been added. Again, see the UITableView documentation for this.
Typically the text is set in each cell by accessing the setText property:
[[cell textLabel] setText:#"static string"];
or
[[cell textLabel] setText:someNSString];
or with .dot notation
cell.textLabel.text = someNSString;
return cell;
BTW this is done in the method:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:

Add editing elements to UITableViewCell

I have a couple of rows in a table view which I like to edit.
I thought that setEditing method would give me a Edit and Delete button, but it only shows a Delete button. Because I don't have a detail view controller that's going to be pushed in didSelectRowAtIndexPath I thought I could show a couple of editing elements in the selected cell.
I need to add three buttons to specify priority on assignments: Low, High and Medium priority. This means that I have to change the height of the selected cell to make room for these buttons, I think that's rather easy to do.
What I'm wondering is if this is the correct path to choose?
I have done quite a lot research today without finding examples of how other have solved editing in a UITableViewCell. If you edit a contact in the Contacts app in the iPhone the UITableViewCells changes to enable quick and easy editing, that's what I'm looking for.
So what do you have for tips for me regarding this question?
Edit #1
My code in didSelectRowAtIndexPath is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Cell *selectedCell = (Cell *)[tableView cellForRowAtIndexPath: indexPath];
UIButton *btn = [UIButton buttonWithType: UIButtonTypeRoundedRect];
btn.frame = CGRectMake(0, 0, 50, 100);
btn.titleLabel.text = #"Set this item to High Priority";
btn.backgroundColor = [UIColor greenColor];
[selectedCell.contentView addSubview: btn];
self.editing = YES;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject: indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
Code for heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
Cell *cell = nil;
if (self.editing)
cell = (Cell *)[tableView cellForRowAtIndexPath: indexPath];
else
cell = nil;
BOOL expandCell = NO;
NSInteger expandationHeight = 0;
if (cell != nil)
{
for (UIButton *btn in cell.contentView.subviews)
{
NSLog(#"A button was found.");
expandationHeight = 70;
expandCell = YES;
}
}
return expandationHeight + heightOfOtherElements;
}
When I click at a cell nothing happends but everything becomes disabled, I can't click any elements on the hole view. This has something to do with [tableView cellForRowAtIndexPath: indexPath], because if I uncomment that line the UI does not become disabled.
What am I doing wrong?
That's not too complicated though it requires some stuff.
You want to change the content of one cell or of all cells?
To specify a specific height for one cell, use the - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath delegate method.
It is called for each cell each time the table view is displayed. If you want to change a single row, call reloadRowsAtIndexPaths:withRowAnimation: method from UITableView. If you want to update all cells, simply use - (void)reloadData method.
You can also access a specific cell using - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath method from UITableView. Then you can reconfigure it to add various elements on it, as you want.
Thus, when a cell is selected, check whether you have to edit it or not, then :
update your cell get from cellForRowAtIndexPath
be sure your method tableView:heightForRowAtIndexPath: will return the good height
tell the table view to update the view using reloadRowsAtIndexPaths:withRowAnimation: