selected UITableViewCell background color changes upon scroll - iphone

I've got these cells I have set a custom background colour to. The background colour works fine when I select the cell, however, when I scroll down and back up, two things can happen:
If not many cells are selected, the cells that went out of view sometimes come back with the default blue colour when selected.
If most or all of the cells are selected, the cells that went out come back with one of the colours that is on the cells that were there beforehand - ie. I select all the cells, scroll down and back up and the cells at the top have the same colour as the cells at the bottom (or at least some of them - others retain their own colour).
Here is the code I have that produces this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *row = [tableView cellForRowAtIndexPath:indexPath];
UIView *backview = [[UIView alloc] initWithFrame:row.frame];
backview.backgroundColor = [colours objectAtIndex:indexPath.row];
row.selectedBackgroundView = backview;
}
That's where the selected method for the cells changes the colour. The cells are created here:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"eventTypeID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *sEventType = [[self.eventTypes valueForKeyPath:#"name.text"] objectAtIndex:indexPath.row];
cell.textLabel.text = sEventType;
return cell;
}
And the colours for each cell are set here:
- (void)loadView {
colours = [[NSMutableArray alloc] init];
CGFloat red[] = {0.84,0.86,0.7,0.46,0.56,0.44,0.95,0.91,0.91,0.76,0.06,0.8,0.73,0.0,0.0,0.01,0.18,0.23,0.57,0.18};
CGFloat green[] = {0.12,0.01,0.07,0.17,0.32,0.18,0.49,0.49,0.78,0.61,0.48,0.85,0.85,0.28,0.38,0.53,0.23,0.36,0.32,0.24};
CGFloat blue[] = {0.34,0.5,0.2,0.53,0.55,0.31,0.18,0.18,0.12,0.27,0.14,0.1,0.49,0.1,0.37,0.49,0.4,0.41,0.55,0.40};
for (int i = 0; i < 20; i++) {
[colours addObject: [UIColor colorWithRed:red[i] green:green[i] blue:blue[i] alpha:1.0]];
}
//Get data from server and parse it
...
}
Now, I have only just started programming the iPhone but my guess (and this is a wild one btw) is that the cells are getting re-created in cellForRowAtIndexPath and although some of the properties are getting saved (like the title...) the custom background isn't.
Has anyone come across this behaviour before? If so, how did you solve it?
EDIT: Even weirder behaviour: Sometimes, if you scroll back down and up, the cell that had gone to the "default" selected background colour goes back to it's custom one. The behaviour seems to be random.

Cell background colours are set in many places, to ensure that the background displayed is the one you want you need to use:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
see this question for more details. If you require custom selection colours, then you should subclass UITableViewCell and override - (void)setSelected:(BOOL)selected and - (void)setHighlighted:(BOOL)highlighted

Related

UITableViewCell background color based upon NSFetchedResultsController - strange issues

This is driving me absolutely insane.
I have a UITableView with cells populated via an NSFetchedResultsController that should have their background color set based upon one of the Core Data parameters.
This table view is in the master view of a UISplitViewController and the selected cell needs to remain visibly selected to indicate what is being displayed in the detail view.
Based upon guidance from several other Stack Overflow questions, I have learned that the ideal place to configure the cell is during the willDisplayCell delegate call, like this:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
WorkTask *workTask = (WorkTask*) [self.fetchedResultsController objectAtIndexPath:indexPath];
if ([workTask.strStatus isEqualToString:#"A"]) {
cell.backgroundColor = [self colorWithHexString:#"fffdcf"];
// cell.textLabel.backgroundColor = [self colorWithHexString:#"fffdcf"];
// cell.detailTextLabel.backgroundColor = [self colorWithHexString:#"fffdcf"];
} else if ([workTask.strStatus isEqualToString:#"B"]) {
cell.backgroundColor = [self colorWithHexString:#"cfffd1"];
// cell.textLabel.backgroundColor = [self colorWithHexString:#"cfffd1"];
// cell.detailTextLabel.backgroundColor = [self colorWithHexString:#"cfffd1"];
} else if ([workTask.strStatus isEqualToString:#"C"]) {
cell.backgroundColor = [self colorWithHexString:#"ffcfcf"];
// cell.textLabel.backgroundColor = [self colorWithHexString:#"ffcfcf"];
// cell.detailTextLabel.backgroundColor = [self colorWithHexString:#"ffcfcf"];
} else {
cell.backgroundColor = [self colorWithHexString:#"ffffff"];
// cell.backgroundColor = cell.contentView.backgroundColor;
}
This mostly sort of works. But...
Depending on how I play around with different variants of accomplishing this, I end up with the background color being ignored sometimes (and only sometimes?!?) behind the textLabel and detailTextLabel. Or causing the cell to display incorrectly while selected. Or having the checkmark indicator displayed without a background color. Or having new items added to the core data database showing up in the table, but with the no background color for the cell, but the text labels having a background color.
No matter what I do, I have not found a simple and intuitive way to make things behave overall as expected - particularly when cells are being programmatically selected.
In fact - the cell selection seems like it might be at the root of the problem. The selected cell is usually the one that ends up drawn incorrectly after I change the selection to another, particularly if the color of the cell changed while the cell was selected.
Is there any example out there anywhere of how this is supposed to work?!?!
Thanks!
If I were you I would create a UITableViewCell subclass with your own titleLabel/subtitleLabel UILabels in it and stop using the textLabel/detailTextLabel. In the xib file you can just change the background color of the labels and of the cell. I have never had the sort of problem you are experiencing when I have used custom cells as opposed to the default cells.
Here's an example of how to load a UITableViewCell from an xib:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
if (cell == nil) {
// Load the top-level objects from the custom cell XIB.
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = [topLevelObjects objectAtIndex:0];
}
cell.titleLabel.text = #"whatever";
cell.subtitleLabel.text = #"whatever";
return cell;
}
You could also try setting the background color of the cell's contentView.
cell.contentView.backgroundColor = ...;
If you really can't figure it out, then in a UITableViewCell subclass you could always just put a UIView of your own in the background of the cell and change the background color of that view instead.
willDisplayCell:forRowAtIndexPath: might not get called after insertRowsAtIndexPaths: is called (i.e. when you add items into core data w/ a fetchedresultscontroller). If absolutely necessary maybe you should try setting the background color of the cell and it's contentView in both cellForRowAtIndexPath: and willDisplayCell:forRowAtIndexPath:.
For some bizarre reason the background color of UITableViewCell objects can only be set just before the cell is drawn. In your UITableView's delegate implement this method:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
Set the background color of the cell there and it will draw the way you want.

Not refreshing screen in programmatically filled TableView

When I scroll down and up again my text in tableView will disappear.
And my code is:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [screenDefBuild.elementsToTableView count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
ScreenListElements *currentScreenElement = [screenDefBuild.elementsToTableView objectAtIndex:indexPath.row];
cell.textLabel.text = currentScreenElement.objectName;
currentRow++;
return cell;
}
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
[tableView setDataSource:self];
[self.view addSubview:tableView];
}
I also want to fill my table view to entire screen. (grey strap on the top).
I don't know what you're doing with this variable
currentRow++;
But whatever you use that for, i'd wager its breaking your code.
the UITableView will call cellForRowAtIndexPath every time a cell is about to appear on screen regardless of whether it has been on screen before or not. When you scroll down and then scroll back up this variable will have increased beyond the bounds of your data, hence you get empty cells.
You need to design this method in such a way that it can create any cell in the table view at any time. You can't rely on the order that the cells will be made and with scrolling you will have to make the same cell over and over again. Only use indexPath to figure out which cell you are currently supposed to be making.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html
Answer for the second part of the question- grey strap. You are adding the table view to current view, so you should use the size property of self.view.frame but not the origin. You want this to be set to 0,0.
Change
tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
to
CGRect viewFrame=self.view.frame;
viewFrame.origin=CGPointZero;
tableView = [[UITableView alloc] initWithFrame:viewFrame];
As for the first part of your question- it's strange, as you seem to do everything properly. One thing i may suggest is to add [tableView reloadData]; in the end of viewDidLoad.

UITableViewDelegate methods

Here is an xcode project that I just did to ask this question:
http://www.mediafire.com/?z26ufsfhby62br9
Open the log and run it. You will find that it outputs display: x and create: x where x is a number 0 to 49 and corresponds to the cell of the same number. It should only output to 22 before any scrolling is performed as Apple are always boasting that their tableviews are loaded as needed.
It basically shows that tableView:willDisplayCell:forRowAtIndexPath: and tableView:cellForRowAtIndexPath: are fired for each cell pretty much as soon as the tableview appears, why is this?
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"create: %i", indexPath.row);
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
cell.textLabel.text = [NSString stringWithFormat:#"%i", indexPath.row];
return cell;
}
- (void) tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"display: %i", indexPath.row);
}
Why are the above meothds called just after the tableView loads (and just before each cell appears)? Surely they should be called just before each cell appears only?
These are the default delegate methods.. This will be be called for each cell everytime.
You use willDisplayCell:forRowAtIndexPath: to configure things like font and text color. In the newer version of the iPhone, with certain table configurations, if you configure things like the label text color in the tableView:cellForRowAtIndexPath: method, your changes will be lost at some point before the cell is actually displayed. Here you can do things like change the label's color, adjust background highlighting, such things as these.
If your table view is reloading before it actually fully displays, that could cause the behavior you're seeing. Then the cells would get initialized, prepared for display, but then all of that would be lost (before even shown on screen) as the table is reloaded.

Show blank UITableViewCells in custom UITableView

I am trying to customize a UITableView. So far, it looks good. But when I use a custom UITableViewCell sub-class, I do not get the blank table cells when there's only 3 cells:
alt text http://img193.imageshack.us/img193/2450/picture1zh.png
Using the default TableView style I can get the repeating blank rows to fill the view (for example, the mail application has this). I tried to set a backgroundColor pattern on the UITableView to the same tile background:
UIColor *color = [UIColor colorWithPatternImage:[UIImage imageNamed:#"score-cell-bg.png"]];
moneyTableView.backgroundColor = color;
...but the tile starts a bit before the TableView's top, so the tile is off once the actual cell's are done displaying:
alt text http://img707.imageshack.us/img707/8445/picture2jyo.png
How can I customize my tableview but still keep the blank rows if there's less rows than fill a page?
Did you by chance remove the background color and separator style? If you did, that could be why there are no extra cells. I would think the default UITableView doesn't add more cells really, it just has the separator style to create that illusion and because it has a white background, they look like cells.
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
If that's not the case, you could always try adding extra cells that can't be selected:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return ([source count] <= 7) ? 7 : [source count];
}
- (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] autorelease];
}
// Set all labels to be blank
if([source count] <= 7 && indexPath.row > [source count]) {
cell.textLabel.text = #"";
cell.selectionStyle = UITableViewCellSelectionStyleNone;
} else {
cell.textLabel.text = [source objectAtIndex:indexPath.row];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
return cell;
}
I believe the slight misalignment of the top row is caused by the vertical scroll bounce of the tableview. If you turn that off, the top row should align properly.
Also, you can just return a cell height that will encompass the tile in tableview:cellHeightForRow:. That works on the default cells nicely.

Adding a subview larger than cellHeight to a UITableViewCell?

I'm trying to add a subview to a UITableViewCell and the design that I'm working from demands that this particular subview (an image) needs to be larger than the actual UITableViewCell and thus partly overlap its siblings.
So I've set up my table cell, generated my image and added it to the cell's contentView:
// rowHeight for the UITableView is 45.0f
UIImage *image = [self createCellThumbnail: someImage];
UIImageView *thumbView = [[UIImageView alloc] initWithFrame: CGRectMake(150, -5, 55,55)];
thumbView.transform = CGAffineTransformMakeRotation(0.1f);
thumbView.image = image;
cell.clipsToBounds = NO;
cell.contentView.clipsToBounds = NO;
[cell.contentView addSubview: thumbView];
While the image will 'overflow' into the cell below it, the top of the image is always clipped, as demonstrated here:
Does anyone know if what I'm trying to do is possible with the current approach?
Or should I just figure out a way to draw these images onto the UITableView after all the cells are drawn (it's a non-scrollable tableview, so that would work and be fairly easy).
Update:
Have also tried adding the following, to no avail:
cell.opaque = NO;
cell.contentView.opaque = NO;
cell.clearsContextBeforeDrawing = NO;
cell.contentView.clearsContextBeforeDrawing = NO;
cell.clipsToBounds = NO;
cell.contentView.clipsToBounds = NO;
I seems that the tableView renders its cell from bottom to top, so the cells above one cell overlap that one cell. To avoid this, you'd have to set the backgroundColor of all cells to +[UIColor clearColor] so that you won't see those overlap problems.
But setting the backgroundColor to clear in -tableView:cellForRowAtIndexPath: does not make any sense. UIKit does a lot of stuff with the cell before it's drawn, so does it reset the backgroundColor property of the cell.
What we need to do is setting the backgroundColor in a later state. Luckily there is this -[UITableViewDelegate tableView:willDisplayCell:forRowAtIndexPath:] which we can implement like this:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor clearColor];
}
Now we're setting the backgroundColor just before the cell is drawn an this turns out to be working.
UPDATE:
So I've done some more experimentation and the following solution still works without having to set the background of the cell to transparent, this involved moving the z order of the covered cell. This works with highlighting and selecting of the other cell (via the relevant callbacks), and if the two cell's backgrounds are different colors. Solution is as follows (you can ignore the didHighlight and didSelect methods if they don't matter to you):
(note that "covered row" is the one whose content we are trying to keep visible and In my case its content goes slightly into the row above, which was clipping it)
-(void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0 && indexPath.row == ROW_ABOVE_COVERED_ROW)
{
NSIndexPath * rowbelow = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section];
UITableViewCell* cell = [tableView cellForRowAtIndexPath:rowbelow];
[cell.superview bringSubviewToFront:cell];
}
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0 && indexPath.row == ROW_ABOVE_COVERED_ROW)
{
NSIndexPath * rowbelow = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section];
UITableViewCell* cell = [tableView cellForRowAtIndexPath:rowbelow];
[cell.superview bringSubviewToFront:cell];
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0 && indexPath.row == COVERED_ROW)
{
[cell.superview bringSubviewToFront:cell];
cell.contentView.superview.clipsToBounds = NO;
}
}
NOTE: you should also set the background color of your content to clear, or it will adopt the bgcolor of the rest of your cell, and so when you manage to bring your content to the front of the covering cell, it will take the background color with it and leave a nasty looking block in the other cell (in my case my only content was the detailTextLabel and the textLabel):
// in cellForRowAtIndexPath:
[cell setBackgroundColor:[UIColor redColor]]; //using red for debug
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.backgroundColor = [UIColor clearColor];
I hope that's helpful to anyone else trying this....
ORIGINAL:
For me the solution was to use:
self.contentView.superview.clipsToBounds = NO;
My cells were already transparent, but my content was still getting clipped. In my case I was using a custom cell which moves it's content up in layoutSubviews. So layoutSubviews for my custom cell wound up as follows:
-(void)layoutSubviews
{
[super layoutSubviews];
self.contentView.frame = CGRectOffset(self.contentView.frame, 0, -11);
self.contentView.superview.clipsToBounds = NO;
}
I don't know if this would work if the cell above was opaque, or if the cells were to highlight when pressed, whether this would cover up my content.
However, I didn't need to make the cell transparent again in the viewWillDisplayCell callback method - doing it in the normal cellForRowAtIndexPath was sufficient
I had this problem and I made sure my custom tableviewcell's main background had clip subviews checked and it solved the problem. This was with a custom tableview cell loaded from a xib though. Not exactly the same but similar situation.
I actually had the opposite just yesterday, I had created a custom table cell and for some reason I got an overflow which I didn't want to have. My solution was to add the following code to my view controller class:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 175;
}
When it matched the height of the table cell there was no overlap; when it was too small there was overlap. Mind you though that I got very quicky behavious so I'm not sure it's a very good idea to do this.