I have my UITableViewCell subclass in which I use an UISwitch as accessoryView in this way:
mySwitch = [[UISwitch alloc] initWithFrame:CGRectZero];
self.accessoryView = mySwitch;
and all is ok! The app works fine.
Now I need to add some UIImageView above the switch, so I thought "Well, let's make a custom accessoryView!":
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 10.0f, 100.0f, 60.0f)];
...
mySwitch = [[UISwitch alloc] initWithFrame:CGRectMake(0.0f, 22.0f, 94.0f, 27.0f)];
[myView addSubview:mySwitch];
self.accessoryView = myView;
[myView release];
All seems to be ok, but there is a strange behavior. When I open another viewcontroller and get back to the table the switches are mysteriously changed...
It's not a problem in data management, but only in the cell redraw...
Please, help me, what can I do?
Thanks in advance
This happens because cells are reused. So if you added subview to cell's content view and then after that cell is reused in another row, that view will appear in that row. The best way to avoid this, is to hold NSArray in your table's datasource (it's usually your view controller) that contains all the custom views (with subviews). Then you can do like this:
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
NSInteger row = indexPath.row;
static NSString* cellIdentifier = #"Trololo";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifier];
if( !cell ) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier: cellIdentifier] autorelease];
}
[[cell.contentView subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
[cell.contentView addSubview: [_your_cell_views objectAtIndex: row]];
return cell;
}
Emh... I found the problem... it was not linked to table redraw...
On UIControlEventValueChanged the selector retrieves the switch value and the cell indexPath.row in this way:
UISwitch *tempSwitch = (UISwitch *)sender;
UITableViewCell *cell = (UITableViewCell *)[tempSwitch superview];
and then it updates the corresponding object (of a data logic class) saved in a NSMutableArray (the datasource of the tableview)
But now the switch is a not the accessoryView, but a subview of the accessoryView, so the object is updates in a unpredictable way. I solved with a second superview message.
Sorry for my mistake and thanks to all...
Related
I am using a simple UITableView and using separator as a image. When table is loaded initially it looks fine but when user scrolls up and leaves, the separator image disappears. Again when user scrolls down and leaves, lines appears again.
Please let me know how to resolve this.
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString *CellID=#"Cell"
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SwitchCellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIImageview *aSwitch = [[UIImageview alloc] initWithImage:[UIImage imageNamed:#"divider.png"]];
separator.frame = CGRectMake(0,50,320,1);
[cell.contentView addSubview:seperator];
}
..........
It's not clear where you declare "separator" but you'll want to make sure you initialize and allocate it as a separate instance for each cell.
UIView *separator = [[UIView alloc] initWithFrame:CGRectMake(0, 50, 320, 1)];
[cell.contentView addSubView:separator];
Are you trying to use one separator instance for all separators in the table? That won't work because a view can only be added to a single superview. You should initialize a new separator for each cell in your reuse pool.
What makes scrolling so choppy on the UITableView? In my mind following code is a culprit. I am having very hard time to replace this logic with something other.
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview]; }
This is what I am doing.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
BGMArticleAbstract *articleAbstract = [self.section.articleAbstracts objectAtIndex:indexPath.row];
[cell.contentView addSubview:[self getHedlineFromArticleAbstract:articleAbstract]];
[cell.contentView addSubview:[self getThumbnailImageFromArticleAbstract:articleAbstract]];
[cell.contentView addSubview:[self getAbstractParaFromArticleAbstract:articleAbstract]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell; }
I am doing, addSubview to contentview because I am creating a dynamic cell height. Is there any way I can make this scroll view work smooth ? Thank you for your help.
You should design your cell as you need it. Add labels and whatever you need to the cell, and then change the content of these already available subviews.
If you need to display an image, add once an UIImageView to the cell and only change the image property of it. Same for text fields and so on.
The way you do it makes the built-in cache useless, because you regenerate all subviews again and again..
To boost the performance even more, you can do the drawing of the cell by yourself.
Apple has a quite nice example project:
http://developer.apple.com/library/ios/#samplecode/AdvancedTableViewCells/Introduction/Intro.html
You are right that the problem is caused by how you return cells. The correct pattern is as follows...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// see if cell contains our image views. A reused cell will, but a new cell won't
UIImageView *imageViewA = (UIImageView *)[cell viewWithTag:32];
UIImageView *imageViewB = (UIImageView *)[cell viewWithTag:33];
UIImageView *imageViewC = (UIImageView *)[cell viewWithTag:34];
if (!imageViewA) {
// the cell must be new, so create it's image views
// you should be able to borrow most of this code from your getHeadline/thumbnail/etc methods.
// the good news is that this relatively expensive code runs only for new
// cells and there are only a few of those - only enough to fill the visible frame
imageViewA = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewA];
imageViewA.tag = 32;
imageViewB = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewB];
imageViewB.tag = 33;
imageViewC = [[UIImageView alloc] initWithFrame:CGRectMake(/* frame it here */)];
[cell.contentView addSubview:imageViewC;
imageViewC.tag = 34;
// this too, need only be done upon creation
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// now, whether it's a new cell or a reused cell, we have image views
BGMArticleAbstract *articleAbstract = [self.section.articleAbstracts objectAtIndex:indexPath.row];
// change your methods getHeadline... getThumbnail... etc to answer UIImages
// not UIImageViews, which are setup only for new cells
imageViewA.image = [self getHedlineFromArticleAbstract:articleAbstract]];
imageViewB.image = [self getThumbnailImageFromArticleAbstract:articleAbstract]];
imageViewC.image = [self getAbstractParaFromArticleAbstract:articleAbstract]];
// as a side note, once you get these methods returning images (more like model objects)
// rather than image views (view objects) they might be more appropriately placed
// in the BGMArticleAbstract class rather than the view controller
return cell;
}
Might be a stupid question.
I have a UITableview, with multiple cells. In each cell I am displaying some data. I am not using cell's text property to display data. Instead I have a custom label inside my cell, which is displaying the text.
My question is:
When I click on the cell, I need to retrieve the data from the cell. How can I do this.
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
UILabel *CellTextlabel = [[UILabel alloc] init];
CellTextlabel.tag = 222;
[CellTextlabel setFrame:CGRectMake(40, 5, 200, 20)];
[cell.contentView addSubview:CellTextlabel];
[CellTextlabel release];
}
UILabel *editCellTextlabel = (UILabel *)[cell.contentView viewWithTag:222];
editCellTextlabel.font = [UIFont boldSystemFontOfSize:18];
editCellTextlabel.text = contact.lastName;
In your didSelectRowAtIndexPath method, you may do it as follows:
UITableViewCell *cell = (UITableViewCell *)[self.tableViecellForRowAtIndexPath:indexPath];
UILabel *textLabel = (UILabel *)[cell viewWithTag:222];
Now you can retrieve the data in the cell's UILabel using textLabel.text
You could get access to that label in didSelectRowAtIndexPath: with
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UILabel *myLabel = [cell.contentView viewWithTag:222];
But probably worth asking, why are you adding a sub label instead of using the textLabel property? you can modify its frame, settings etc and then you don't have to worry about tags, since this property is exposed in UITableViewCell by default
In the -tableView:didSelectRowAtIndexPath: method, you can get the data from your tableView's array:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
id objectForCell = [self.myArray objectAtIndex:indexPath.row];
//do what you want with the above data.
}
I have been looking for a solution to this issue for a few days now... and cannot find someone with a similar problem or a solution that would work for me. At this point I am not even sure that I am doing something wrong, as I have read and analyzed many sample code and I am almost 100% sure that I am doing this the way it should...
Anyway here it comes:
I have a UITableView to which I display custom built UITableViewCell, here is where I create them:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CellIdentifier";
NSDictionary *myDictionary = [myArray objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier: CellIdentifier] autorelease];
CGRect textViewRect = CGRectMake(10.0, 0.0, 250, 25);
UILabel *textView = [[UILabel alloc] initWithFrame:textViewRect];
textView.text = [myDictionary objectForKey:#"name"];
textView.font = [UIFont systemFontOfSize:12.0];
textView.textColor = [UIColor blackColor];
textView.highlightedTextColor = [UIColor whiteColor];
textView.textAlignment = UITextAlignmentRight;
[cell.contentView addSubview:textView];
[textView release];
CGRect imageRect = CGRectMake(270.0, 15, 16, 16);
UIImageView *icon = [[UIImageView alloc] initWithFrame:imageRect];
icon.image = [myDictionary objectForKey:#"color-icon"];
icon.highlightedImage = [myDictionary objectForKey:#"gray-icon"];
[cell.contentView addSubview:icon];
[icon release];
}
So as you can see, pretty standard stuff... Then when I click on one of the cell, another view gets loaded instead of the table.
Until now, everything is fine, but then when I come back to that table view and that it has to reload the problem starts...
By the way I have added this to the delegate methods so the cells never stay selected:
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// [...] a lot more code here
}
When I clicked on my cell in the simulator with the mouse and keep it down the cell stays selected as expected (same way on the device itself) and this is why I get:
[Image 1]
The UILabel Text is totally mixed up ! The screenshot is actually of the last cell of the table and the overlapping text is the one of the first cell. And the behavior on the first is similar, if I maintain the click on it, it will show the same behavior mixing up with the UILabel text of the last cell. I am guessing that is is due to the way the dequeueReusableCellWithIdentifier works (probably a FILA queue).
Of course I tried to work around that and found some really weird stuff.
If I don't click the cells are perfect, no bugs, the correct text is displayed etc:
[Image 2]
Then I tried to mess up a little bit with the parameters of my UILabel. I added this:
textView.backgroundColor = [UIColor clearColor];
And when I do this, as soon as the table reload, then I don't even need to highlight the cell to see the screw up behavior:
[Image 3]
The only way I was about to get rid of the problem to always instantiate a new cell rather than dequeueing one...
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier: CellIdentifier] autorelease];
was replaced by:
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier: CellIdentifier] autorelease];
But of course this won't work on the long term because of the memory leak...
Also, it is to be noted that I have the exact same behavior for another UITableView in another view somewhere in this App... The other custom cell is more complex, more labels, more images, etc, but all the label exhibit the same behavior. And this more complex Table View is managed by a Navigation Controller, so no custom loading / unloading like the first one...
That's all I have, and I can't find a solution... please help !
Arghh this is really annoying... I am new so I cannot post images... :(
Here is a link where you can see the referenced images: https://skitch.com/aponsin/rne9k/fullscreen.png-100-layer-3-rgb-8
Alex
The problem is that you create new label and add it to cell each time cell is being reused. To fix that you must create your label only when your cell is created:
if (cell == nil){
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier: CellIdentifier] autorelease];
CGRect textViewRect = CGRectMake(10.0, 0.0, 250, 25);
UILabel *textView = [[UILabel alloc] initWithFrame:textViewRect];
textView.tag = kLabelTag;
textView.font = [UIFont systemFontOfSize:12.0];
textView.textColor = [UIColor blackColor];
textView.highlightedTextColor = [UIColor whiteColor];
textView.textAlignment = UITextAlignmentRight;
[cell.contentView addSubview:textView];
[textView release];
CGRect imageRect = CGRectMake(270.0, 15, 16, 16);
UIImageView *icon = [[UIImageView alloc] initWithFrame:imageRect];
icon.image = [myDictionary objectForKey:#"color-icon"];
icon.highlightedImage = [myDictionary objectForKey:#"gray-icon"];
[cell.contentView addSubview:icon];
[icon release];
}
And after that you can get your label from cell using tag property you've assigned:
UILabel *textView = (UILabel*)[cell.contentView viewWithTag:kLabelTag];
textView.text = [myDictionary objectForKey:#"name"];
same logic also applies to setting up your icon imageview if it varies in different cells
Also have a look at components standard UITableViewCell already provides (depending on its cell style) - there's already UIImageView and UILabels there and you can set your custom properties to them and use them without creating extra components
I want to display a label showing a number in each cell of the tableview but the label is only visible when I click on a row (when the cell is highlited)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UILabel *label;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
label = [[UILabel alloc] initWithFrame:CGRectMake(200,10, 15, 15)];
label.tag = 1;
[cell.contentView addSubview:label];
[label release];
}
else {
label = (UILabel *)[cell viewWithTag:1];
}
if (indexPath.row == 0) {
cell.textLabel.text = #"Photos";
label.text = [NSString stringWithFormat:#"%d",1];
}
return cell;
}
I had the same problem and it was solved by setting the text for textlabel BEFORE adding the custom label as a subview.
...
cell.textLabel.text = #"X";
...
[cell.contentView addSubview:label]
When you update the textLabel property of a UITableViewCell, it lazily creates a UILabel and adds it to the cell's subviews. Usually you wouldn't use a combination of textLabel and adding subviews to contentView, but if you do you need to make sure the textLabel view isn't placed over the top of your contentView subviews.
First, I assume this is targeting 3.0. Apple has changed how UITableViewCells are created in 3.0, and you should move over to that. -initWithFrame:reuseIdentifier: is deprecated.
That said, a likely problem is that the built-in textLabel is interfering with your added label, perhaps overlapping. You should look first at whether one of the new built-in styles meets your needs directly. If not I would recommend either just using your own views or only using the built-in views, possibly rearranging them. If you want to rearrange them, Apple suggests subclassing the cell and overloading -layoutSubviews. I also believe that -tableView:willDisplayCell:forRowAtIndexPath: is a good place to do final cell layout without subclassing.
Using a custom UITableViewCell gives you more control over the layout of a cell. Add custom views to the cell's contentView in the subclass and override the layoutSubviews to set the order of the subviews:
- (void)layoutSubviews {
[super layoutSubviews];
[self.contentView bringSubviewToFront:self.yourCustomView];
}