IOS: Stop cells being incorrectly reused in UITableView - ios

I have a basic UITableView with four sections. I control the content in each section with a Switch statement.
I programmatically create a button, which should appear in the rows of the first THREE sections, but should NOT appear in the fourth. However, the button is appearing randomly in the fourth section's rows.
I presume this is because a cell is being reused, but as I create each section's rows with the Switch statement, I cannot see how this is happening. Any ideas appreciated.
I am using a custom cell configured so:`
static NSString *CustomCellIdentifier = #"DashboardCell";
DashboardCell *cell = (DashboardCell *)[tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil) { NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"DashboardCell"
owner:self options:nil];
for (id oneObject in nib) if ([oneObject isKindOfClass:[DashboardCell class]])
cell = (DashboardCell *)oneObject;
}
// Configure the cell.`
The code to create this button is: `
button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(200, 11, 50, 50);
UIImage *iConnect = [UIImage imageNamed:#"connect.png"];
[button setImage:iConnect forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonSelected:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:button];`

You need a different reuse identifier for each type of content. So here you have two types of content - cell's that have a UIButton and cells that don't.
Use the indexPath of the tableView:cellForRowAtIndexPath: method to select a reuse identifier of either #"CellWithButton" or #"CellWithoutButton".
What is actually happening in your code is that all cells are given the same reuse identifier, meaning that they all get put into the same object pool. This means that when you use [tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier]; that you are retrieving a cell from this one pool (which potentially contains cells that have no UIButton and cells that do). Therefore the dequeue method can randomly return a cell that has already had a UIButton added to it. If you use two reuse identifiers, the UITableView will setup two object pools and will correctly deposit and retrieve the appropriate cells from each.
Or you can use one reuse pool and check the cell for a UIButton each time you retrieve one using the dequeue method.

In your DashboardCell, create a property #property (nonatomic, assign) BOOL buttonEnabled. Then in your awakeFromNib, always create the button and set button.hidden = YES. Override the setter of your property
- (void)setButtonEnabled:(BOOL)enabled {
buttonEnabled = enabled;
button.hidden = !enabled;
}
And finally override prepareForReuse
- (void)prepareForReuse {
[super prepareForReuse];
self.buttonEnabled = NO;
}
And now you can enbale/disable in your configure part of the method cellForRowAtIndexPath

You can use two different cell identifiers depending on the section. Otherwise you would need to see whether the button existed in the cell that's returned from dequeueReusableCellWithIdentifier: and add one or remove an existing one if necessary.

If you're going to be reusing these cells and there's some simple logic behind showing or hiding each cell's button the first thing I would recommend is that you create the button in Interface Builder and connect it as an outlet to your UITableViewDelegate.
I would then create a setup method for the cell that you can run at any time, any number of times without it breaking it:
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CustomCellIdentifier = #"DashboardCell";
DashboardCell *cell = (DashboardCell *)[tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil) { NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"DashboardCell"
owner:self options:nil];
for (id oneObject in nib) if ([oneObject isKindOfClass:[DashboardCell class]])
cell = (DashboardCell *)oneObject;
}
// Configure the cell.
[cell setupWithSomeConfigOptions:someConfigOptions]
return cell;
}
And in your cell subclass you would have the method -(void)setupWithSomeOptions:(SomeOptions)someOptions, which would be something along the lines of:
-(void)setupWithSomeOptions:(SomeOptions)someOptions
{
// some setup code
self.myButtonOutlet.hidden = someOptions.somePropertyToCheckIfButtonShouldBeHidden;
// some more setup code
}

Related

On scroll UITableViewCell added button image issue

I have UITableViewCell and added button left side of cell for check mark cell is selected or unselected.
Number of rows in uitableview 50.
Issue:
First row is selected and added the checkmark to button, then I scroll the UITableView, I found that another check mark is added to another cell.
Is any one face same issue, your input will be appreciated.
//Cell For Row IndexPath
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:TableCell];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:UITableViewCell owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.checkButton.tag = indexPath.row;
// Image changes in Storyboard.
-(void)checkButtonAction:(id)sender
{
if ([sender isSelected]) {
[sender setSelected:NO];
}
else {
[sender setSelected:YES];
}
}
Reading your question , its the cell being re-used by iOS sir. The line below is the culprit.
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:TableCell];
What this does is look for an existing cell in the table view, and loads that cell.
You said, You clicked one cell and it had the checkmark, right?
Well, fortunately when you scroll away from it, the cell gets reused by the tableview by above call and is seen again in the list of visible cell with the checkmark.
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:TableCell];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:UITableViewCell owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.checkButton.tag = indexPath.row;
Looking at your calls in the above method, you never set the checkmark to either on or off, sir. You are reusing the cell that already had the checkmark. So, when you see it again, it also has the checkmark, because you never changed it anyway.
NOTE: IT'S NOT RANDOMLY GIVING CHECKMARKS.
AT THE BEGINNING, DON'T SCROLL. GIVE CHECKMARK TO ALL THE CELLS AND YOU WOULD GET YOUR ANSWER. NOW START SCROLLING. Every cells should have checkmarks now.
Solution:
To solve this issue, in your datamodel, add a isCheckmarkSelected:Bool property.
And, anytime user checks the cell, make isCheckmarkSelected to true.
In the method call, [ cellForRowAtIndexPath ]... read the isCheckmarkSelected value and set the checkmark of the cell.
This would solve the not so random checkmarks issue.
Kind Regards,
Suman Adhikari
Click Here
This above link is worked fine for my question.
in button action add extra code [[self.tableview] reloadData];

iOS: Customizing TableView cell to mimic music player

Is there an easy way of having a tableview cell like we see here with numbering like this and the border around. Is this created using different sections?
You need to create a custom UITableViewCell.
If you're using storyboards look here:
See this link http://mobile.tutsplus.com/tutorials/iphone/customizing-uitableview-cell/
If not here is a rundown:
Basically create a new class that inherits from UITableViewCell and a XIB. Drag a UITableViewCell to the XIB and set it to the class that you created previously.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CustomCellIdentifier = #"CustomCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil) {
//*- Load your custom XIB. objectAtIndex:0 says load the first item in the XIB. Should be your UITableViewCell that you dragged onto your XIB in Interface Builder.
cell = [[[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil] objectAtIndex:0];
}
//*- Customize the cell, i.e., cell.myLabel.text = #"Text";
return cell;
}
Using this technique you can layout your cell with three labels, one for the number and one for the name of the song and one for the song time. Add a background image view for the border and color.
A simple way to get the song number in the table is to use the indexpath.
cell.myLabel.text = [NSString stringWithFormat:#"%i", indexPath.row + 1];

Issue with UITextField values in custom cell while scrolling fast ios

I have a simple table with custom cells each containing a textfield. In cellForRowAtIndexPath: I create and initialize each cell depending on indexPath.row:
case 0:
{
CellIdentifier = #"TextEditCell";
TextEditCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
[cell configureCellWithText: [self.valueArray objectAtIndex:0]
placeholder: #"value no.0"]
[cell performAction: #selector(saveValue0:)
forControlEvent: UIControlEventEditingDidEnd
inTarget: self];
return cell;
}
configureCellWithText:placeholder: sets text and placeholder of cell's textField.
performAction:forControlEvent:inTarget refers directly to textField and saves the value of textField to local array to be accurate when used again.
Problem occurs, when I scroll the table fast. Values from different cells copy to another cells and modify local array. I can't find out why it happens. Anyone have any idea? I can provide more code if needed.
This is happening because you are reusing the cells and configureCellWithText is being run after the cell is reused. To solve this you could:
Don't reuse cells - But this would really hurt your performance.
If you are running on 6.0 you can use tableView:didEndDisplayingCell:forRowAtIndexPath: to cancel the text setting action when the cell scrolls off screen.
You can create a flag in your custom cell class that you set when you dequeue a cell.
Edit
Because I do not know how your cell works. It is hard for me to give you anything more then a sudo code concept.
Here is my sudo code:
Tableview Cell for row...
- dequeue cell
- [cell cancel_previous_action]
- set new actions.

iOS xcode, adding UILabel subview to custom table cell, label text does not update on screen

I've created a custom UITableViewCell class, and used the layoutSubviews method to add a custom label. Like this:
- (void)layoutSubviews
{
[super layoutSubviews];
if (statusLabel == nil)
{
statusLabel = [[UILabel alloc]initWithFrame:CGRectMake(430.0, 10.0, 100.0, 20.0)];
[statusLabel setTextAlignment:UITextAlignmentRight];
[statusLabel setText:#"Status, set in code"];
statusLabel.tag = 1;
[self.contentView addSubview:statusLabel];
}
}
As you can see, I have set the initial text of the label to "Status, set in code".
In the table view controller I set the text for this custom label in the cellForRowAtIndexPath method, like so:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
int index = [indexPath row];
NSString *introducerString =[introducers objectAtIndex:index];
NSArray *parts = [introducerString componentsSeparatedByString:#","];
static NSString *MyIdentifier = #"Requester";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[DanceCardCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier] autorelease];
}
UIImage *image = [UIImage imageNamed:#"1.jpg"];
[cell.imageView setImage:image];
cell.textLabel.text = [parts objectAtIndex:1];
cell.detailTextLabel.text = #"Some text";
UILabel *statusLabel = (UILabel *)[cell viewWithTag:1];
statusLabel.text = #"Did it!";
return cell;
}
I'm using one table view to display two lists, depending on which of two buttons is pressed. When a button is pressed the appropriate table view controller is attached to the table view, and the reloadData method is called to trigger display of the new data. The new data does display, but the custom label text, which should read "Did it!" reads "Status, set in code ...", until I switch lists again twice.
How can I get the new text for the custom label to update straight away? I have checked the official documentation and cannot find any reference to refreshing a cell's display after updating its custom content.
Here is a screen shot to demonstrate what happens: http://www.dsbsystems.co.uk/images/xcode1.png
You're initializing the cell and immediately attempting to find the statusLabel with tag 1 inside it. layoutSubviews hasn't had the opportunity to be called yet, and so the label hasn't been created and added. (I suggest overriding the designated initializer method on your table view cell and creating the label there.)
Because of this, when you try to pull out statusLabel, it becomes nil because there's no such view, and messaging (calling a method on) nil simply does nothing (actually, it returns nil). You will need to watch out for this going forward if you're used to things blowing up with e.g. null reference exceptions.
When the cell is requested again, a new cell isn't needed because it's available from the reuse queue, and the label will be found correctly.

Load a AQGridViewCell from XIB (not working)

I am using AQGridView class and I am trying to load a cell from an XIB. I have setup the XIB like a Custom Cell for a UITableView, but when I attempt to load the cell, it is simply blank. I was wondering if there was an easier way to get the XIB to load.
AQGridViewCell need to load the cell from an xib
- (AQGridViewCell *) gridView: (AQGridView *) gridView cellForItemAtIndex: (NSUInteger) index
{
static NSString * CellIdentifier = #"cellID";
gridCell * cell = (gridCell *)[gridView dequeueReusableCellWithIdentifier: CellIdentifier];
if ( cell == nil ){
gridCell = [[gridViewCell alloc] initWithFrame: CGRectMake(0,0,_gridView.frame.size.width/2-4,
_gridView.frame.size.height/2-8)
reuseIdentifier:CellIdentifier];
cell = gridCell;
self.gridCell = nil;
}
cell.title = #"Test Grid Item";
cell.date = #"Apr. 7, 2011";
return ( cell );
}
Here's an article that describes how to load an AQGridViewCell from nib, with example code. Check out the section called "A reusable AQGridViewCell".
(Thanks to pt2ph8 for pointing out contentView.)
From what I've understood, I think it shows as blank because what gets displayed is the cell's contentView. I ended up loading my custom view from IB and adding it as a subview of the cell's contentView when the cell is requested.
AQGridView's developers once claimed on GitHub that proper IB support will be added in the future, but that post is dated August 2010, so don't hold your breath.
This took me a while, but I figured a different way than the blog post jlstrecker mentioned.
Create a subclass of AQGridViewCell - let's call it
MyGridViewCell.
Create a nib for that cell, link it up in IB.
Pub a view ON TOP of the cell's view in IB. That's right, a view
on top of a view. Make the size the exact same.
For that view on
top of the view (let's call it view2), set the tag property (can
be done in IB) to 1.
Put everything you want to link up on top of
view2, decorate your cell, whatever you'd like.
Use the following code (of course, change it to your needs) in your subclass of AQGridViewController:
`
- (AQGridViewCell *)gridView:(AQGridView *)aGridView cellForItemAtIndex:(NSUInteger)index {
static NSString *CellIdentifier = #"MyGridViewCell";
MyGridViewCell *cell = (MyGridViewCell *)[self.gridView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = (ZZProductGridViewCell *)[[[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil] objectAtIndex:0];
}
[cell.contentView addSubview:[cell viewWithTag:1]]; //THIS IS THE IMPORTANT PART
return cell;
}
Enjoy!
I'm not familiar with AQGridView, but I believe you can leverage NSBundle's Nib loading capabilities. An excerpt from AdvancedTableViewCells sample project illustrates the idea:
RootViewController.h
#interface RootViewController : UITableViewController
{
ApplicationCell *tmpCell;
}
RootViewController.m
ApplicationCell *cell = (ApplicationCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"IndividualSubviewsBasedApplicationCell" owner:self options:nil];
cell = tmpCell;
self.tmpCell = nil;
}
Inside the IndividualSubviewsBasedApplicationCell.xib you would have to set the outlet of the UITableViewCell within to be the RootViewController's tmpCell property. Then, as a side effect of invoking NSBundle's loadNibNamed method, the tmpCell property gets set on the RootViewController via the Nib loading mechanism.
What you can do is do your xib (uiview) unpacking/loading in the subclass itself (which does have a different init method than a uitableviewcell)
you can also connect any outlets to this xib and add its entire view as a subview, or maybe replace contentview).
To make it even faster you can make uinib of this xib and reuse it to save disk i/o.
Build your cell normally using IB, then in your subclass of AQGridViewCell, add
- (void)awakeFromNib{
self.contentView.backgroundColor = [UIColor clearColor];
}

Resources