iOS - UiTableviewCell - Background Image only showing on one cell at a time - iphone

I've used UIAppearance to set the background image of my table cells across my app.
[[UITableViewCell appearance] setBackgroundView:[ [UIImageView alloc] initWithImage:[ [UIImage imageNamed:#"list_bg.png"] stretchableImageWithLeftCapWidth:0.0 topCapHeight:5.0] ]];
However, when I view a list the background image is only set for one of the cells on screen. If I scroll the screen the background image is set on the cell that appears into view but it doesnt set this bg image on any other visible cell.
Anyone got any idea whats going on? I thought that by setting the UIAppearance of UITableviewCell that all instances of this type would automatically get the background image.
Thanks
Brian
Here is my cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"plantCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"PlantListCell" owner:self options:nil];
cell = _plantCell;
self.plantCell = nil;
}
Plant *plant = [self.fetchedResultsController objectAtIndexPath:indexPath];
UIImageView *thumbnail = (UIImageView *)[cell viewWithTag:1];
thumbnail.image = [UIImage imageNamed:[plant.name stringByAppendingString:#"_S"]];
UILabel *label = (UILabel *)[cell viewWithTag:2];
label.text = plant.name;
UIImageView *set = (UIImageView *)[cell viewWithTag:3];
set.image = [UIImage imageNamed:#"set"];
UIImageView *favourite = (UIImageView *)[cell viewWithTag:4];
favourite.image = [UIImage imageNamed:#"fav"];
return cell;
}

Yep. The issue with this is you're telling the UITableViewCell class to set the one UIImageView for each table view cell's background view. Once its set as one, its removed from its previous superview, and added to the new UITableViewCell. You're not getting duplicate UIImageViews set on each tableViewCell; you're getting the one view set on every table view cell.
So then its set and unset from each tableViewCell till the last one it was set on.
Using the appearance proxy for this method sets it on every object of the UITableViewCell.
I wouldn't use the appearance proxy to set the background view method, as whatever view you pass to it will be one view, which can only apply to one cell at a time.
My recommendation is to create a view for each cell independently, or creating a subclass which sets it on initialisation.

Thanks thebarcodeproject. It kind of makes sense alright, but I don't see why Apple bothered to allow us to set the UITableviewCell background image if it doesnt really work.
I went with a simple solution in the end by just setting the image in the cellForRowAtIndexPath method.
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"list_bg.png"]];
It works fine, but not great if you have lots of tables and want to set it in a generic way.
Cheers
Brian

I solved this by creating a base class called CustomizableUITableCell and setting up a ui appearance value for the background image. Then whenever i create a cell that i need with a custom background i simple subclass CustomizableUITableCell.
//Header
#import <UIKit/UIKit.h>
#interface CustomizableUITableCell : UITableViewCell <UIAppearance>
#property (nonatomic, weak) UIColor *backgroundCellColor UI_APPEARANCE_SELECTOR;
#end
//Implementation
#import "CustomizableUITableCell.h"
#implementation CustomizableUITableCell
#synthesize backgroundCellColor;
#pragma mark - Public Methods.
-(void)setBackgroundCellColor:(UIColor *)backgroundColor
{
[super setBackgroundColor:backgroundColor];
}
#end
You can then set it using the appearance proxy.
[[CustomizableUITableCell appearance] setBackgroundCellColor:[UIColor colorWithPatternImage:backgroundImage]];

cell.backgroundView = [[UIImageView alloc]
initWithImage:[ [UIImage imageNamed:#"1.jpg"]
stretchableImageWithLeftCapWidth:0.0 topCapHeight:5.0] ];
It works for me..

Related

Issue with UIImageView z-index

In the interface designer, I have a very simple setup. The main view (which is a table cell view) has four sub-views, all siblings: UIImageView and three UILabels. I'm using UIImageView to display the background of the table row. I set it so that UIImageView is at the very back of the z-index tree and set its alpha to 0.2 to dim the image - and UILabel's are going over it.
If I set the image into the imageview at the design time in interface designer, then everything is fine, however if I set the image into the imageview at design time (I receive the actual image from the server), then the imageview alpha setting is ignored, the image is displayed at alpha = 1 and the uiimageview is displayed over all the labels. I even tried to use [parent sendSubviewToBack:imageView] but it didn't help.
What am I missing? Here's my whole code for displaying the table row.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[NSBundle mainBundle] loadNibNamed:#"OOItemInfoCell" owner:self options:nil] lastObject];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
OOXMLNode *infoNode = [searchItemList objectAtIndex:indexPath.row];
[(UILabel *)[cell viewWithTag:2] setText:[itemNode elementNodeWithKey:#"title"].value];
[(UILabel *)[cell viewWithTag:3] setText:[itemNode elementNodeWithKey:#"intro"].value];
[(UILabel *)[cell viewWithTag:4] setText:[NSString stringWithFormat:kThemeAge,
[itemNode elementNodeWithKey:#"theme"].value,
[itemNode elementNodeWithKey:#"age"].value]];
UIImageView *bgview = (UIImageView *)[cell viewWithTag:0];
[bgview setImage:[itemNode imageFromKey:#"background"]];
[cell sendSubviewToBack:bgview];
return cell;
}
Ok, this was pretty stupid of me... I had the tag of the UIImageView set to 0 and then was using viewWithTag:0 to find that view. Yet, the parent view also had tag 0, so instead of my background UIImageView, the entire cell was being set to the image (I wouldn't think that a table cell view would respond to setImage - but it did.
I now changed the tag on UIImageView to 5 - and everything is hunky-dory.

Custom UITableViewCell problem loading from NIB

UPDATE: I've solved this now - see my own comment. Amazing how often writing out the problem can lead to the resolution when you go through it again!
I must have read every thread on SO and the examples on Apple, but I just can't get this to work in my case and I wonder what I'm doing wrong?
I have made a custom cell in IB with XCode 4. The file's owner is set to the class of the custom view controller for the table and I have an IBOutlet for the UITableView cell which is hooked up OK from what I can tell. I normally do all my table cells via code and use the fast scrolling method, but for this project I really need to use nibs to load the cell content and I'm having some grief I can't figure out.
So in my interface file I have;
#interface myCustomTableViewController <various delegates> {
UITableViewCell *customNibCell;
...
}
#property (nonatomic,assign) IBOutlet UITableViewCell *customNibCell;
In the customTableViewController I do this in my cellForRowAtIndexPath;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#:"customDirCell"];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"DirectoryCellWithMenu" owner:self options: nil];
cell = customNibCell;
self.customNibCell = nil;
}
[self configureCell:cell atRow:indexPath.row];
And my configureCell method does this to reach into the cell and pull out the things I wish to customise.
UIImageView *fileImageView = (UIImageView *)[cell viewWithTag:3];
UILabel *headingTextLabel = (UILabel *)[cell viewWithTag:4];
UILabel *modifiedTextLabel = (UILabel *)[cell viewWithTag:5];
UILabel *sizeTextLabel = (UILabel *)[cell viewWithTag:6];
The Problem
The cells do load into the table OK in as much as I can see the correct layout. Furthermore, the custom actions I have defined on various buttons do all fire OK so it seems that the cell is hooked up properly.
BUT none of the customisation works - I can't seem to change the text of any of the labels for example and on investigation, the result of every [cell viewWithTag:nn] is nil which clearly signposts the problem, but I can't see what the cause is.
The identifier for the
UITableViewCell is set to
"customDirCell"
The object identities for the labels
etc are indeed 3,4,5,6 according to
IB
The xib contains exactly 1 object,
the UITableViewCell
I can't see anything wrong with either the nib file creation or the code, but I'm obviously missing something!
Can any kind soul shed light on where I should be looking?
Did you try this
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#:"customDirCell"];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"DirectoryCellWithMenu" owner:self options: nil];
cell =[nib objectAtIndex:0];
self.customNibCell = nil;
}
I've resolved this as per my comment.

Customized UITableViewCell

How can i customize a UITableviewCell, as i want to see a label, date and picture in a single cell.
There are two options (may be more). You could use the native UITableViewCell properties to add content to the cell, or create a custom cell (by that I mean, add your own subviews to the Cell). To get started try the first one, it is simple elegant and the results will be quite good. For example try the following cell creation method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// notice the Style. The UITableViewCell has a few very good styles that make your cells look very good with little effort
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
// In my case I get the data from the elements array that has a bunch on dictionaries
NSDictionary *d = [elements objectAtIndex:indexPath.row];
// the textLabel is the main label
cell.textLabel.text = [d objectForKey:#"title"];
// the detailTextLabel is the subtitle
cell.detailTextLabel.text = [d objectForKey:#"date"];
// Set the image on the cell. In this case I load an image from the bundle
cell.imageView.image = [UIImage imageNamed:#"fsaint.png"];
return cell;
}
You can customize UITableViewCell using either Interface Builder or through Code
Have a look at this link.
There you'll find how to make a custom cell with Interface Builder and use it in XCode for your application.
http://www.e-string.com/content/custom-uitableviewcells-interface-builder
I'm a big fan of overriding the UITableViewCell class, and doing custom drawing in self.contentView. This technique is a little more complicated, but it leads to much better scrolling performance.
For example, lets say you override your cell, and have 3 properties on it like so:
#property(nonatomic, retain) UIImage *userPic;
#property(nonatomic, retain) NSString *label;
#property(nonatomic, retain) NSString *date;
Then you can draw them in the cell using the (drawRect:) function:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
[userPic drawInRect: CGRectMake(10, 5, 50, 50)];
[label drawAtPoint:CGPointMake(70, 5) withFont:[UIFont boldSystemFontOfSize:17]];
[date drawAtPoint:CGPointMake(70, 30) withFont:[UIFont systemFontOfSize:14]];
}
For more examples try checking out this framework that uses this style: https://github.com/andrewzimmer906/XCell

How to set the table view cell accessory view to retain a previously initialized UIImageView?

Let's say I have a property in my view controller, defined as follows:
#property (nonatomic, retain) UIImageView *checkmarkOffAccessoryView;
I #synthesize this in the implementation, release it in -dealloc and initialize it in -viewDidLoad as follows:
self.checkmarkOffAccessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
So far so good.
When I use it in my table view delegate as an accessory view for multiple cells, two things happen:
Only one cell's accessory view shows the image
The application UI freezes.
The app doesn't crash, as near as I can tell, the UI simply becomes unresponsive. This is both in the simulator and on the device.
Here is how I use the initialized property with my cell:
- (UITableViewCell *) tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// initialize or dequeue cell...
if (condition)
cell.accessoryView = self.checkmarkOffAccessoryView;
else
cell.accessoryView = nil;
}
With the aforementioned code, only one cell shows the accessory view and the UI freezes.
If I initialize the UIImageView instance directly in the delegate method I get all condition-satisfying cells showing the accessory view and I do not experience the UI freeze:
- (UITableViewCell *) tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// initialize or dequeue cell...
if (condition)
cell.accessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
else
cell.accessoryView = nil;
}
My goal is to initialize as few objects as possible and reuse one UIImageView. I'm curious why the first chunk of code is problematic and what I could do to fix this.
It seems like the cell's accessoryView property should just increment the retain count of self.checkmarkOffAccessoryView but it appears I am missing some detail.
What have I overlooked? Thanks for your advice.
EDIT
I think that:
self.checkmarkOffAccessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
is the same as:
UIImageView *uncheckedView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]];
self.checkmarkOffAccessoryView = uncheckedView;
[uncheckedView release];
Either way, I experience the same freeze symptom.
You cannot add the same view multiple times. The UI handler will go bonkers. To make sure of this, I tried doing what you said above and I got the same issue. The UI freezes up, the image only appears for one of the cells.
The best thing you can do is to store your image as a UIImage allocated, and to have a helper function which returns a new UIImageView per cell.
Using your current method (without a stored UIImage) you might do:
-(UIImageView *) makeCheckmarkOffAccessoryView
{
return [[[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
}
And then do
cell.accessoryView = [self makeCheckmarkOffAccessoryView];
As you may be aware, UIImages on the other hand may be used any number of times. a UIImageView doesn't take up a lot of space, so you can easily have a bunch of those without worrying.
To expand on the one place only deal, imagine that you add a UIView to two places at the same time.
What will [ob removeFromSuperview] do for this object? Will it remove the view from both places? From one of them only? Which value will be returned when you request [ob superview]? Clearly the UI is not made to handle what you're asking for.
Try it without the autorelease in the initializer. I suspect you're over-releasing.
By the way, your console probably is showing a BAD_ACCESS error when it freezes. If you turn on NSZombieEnabled, my guess is you'll see it's making a call to a deallocated UIImage.
maybe this will help
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ShoppingListCell";
HSShoppingListCell *cell = (HSShoppingListCell *)[aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"ShoppingListCell"
owner:self
options:nil];
cell = shoppingListCell;
}
ShoppingListItem *theItem = nil;
theItem = [self.fetchedResultsController objectAtIndexPath:indexPath];
UIImage *selected = [UIImage imageNamed:#"listBullet_checked.png"];
UIImage *notSelected = [UIImage imageNamed:#"listBullet.png"];
cell.imageView.image = ([theItem.checkedOff boolValue] ? selected : notSelected);
cell.shoppingListLabel.text = theItem.productName;
[cell.shoppingListLabel setFont:[UIFont fontWithName:#"Marker Felt" size:26.0]];
return cell;
}
- (void)toggleCellImage:(NSIndexPath *)indexPath
{
ShoppingListItem *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
item.checkedOff = ([item.checkedOff boolValue] ? [NSNumber numberWithBool:NO] : [NSNumber numberWithBool:YES]);
[HSCoreDataUtilities saveContext:item.managedObjectContext];
[self.tableView reloadData];
}
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self toggleCellImage:indexPath];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Reducing your case to the bare essentials (I was going to suggest to put two 'thin' UIView objects around the UIImageView...), I found that it is most probably impossible.
Create 2 empty UIView objects in IB, hook them up to bareView1 and bareView2. Then
UIImageView *imageView = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:#"test.png"]];
[bareView1 addSubview:imageView]; // it shows either here ...
[bareView2 addSubview:imageView]; // ... or here
You can never get the image on sceen more than once like this. As a rule of thumb, I think the first object in line which does not inherit from UIView can be used multiple times, i.e. the UIImage. Like Kalle stated, a UIView can only have one parent in the view hierarchy.
Postponing the second addSubview only makes the UIImageView jump from bareView1 to bareView2.
The freeze happens maybe because the event handling gets mixed up: the accessory can be interactive, how would you know which one was tapped if they are one and the same object? So the code assumes objects are unique, and you manage to violate that assumption.

Cocoa-Touch: How to: custom UITableViewCell with UIColor clearColor background

I have a UITableView with custom cells.
I have MyTableViewCell : UITableViewCell and MyTableViewCellContentView : UIView classes.
What I'm doing is basically what is done in the AdvancedTableViewCells demo app from Apple with a slight change, on some rows I want to use a clearColor background to show the table's background behind the painted text.
So in MyTableView's cellForRowAtIndexPath I'm doing:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"MyCell";
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.someValue = indexPath.section;
[cell finishedSetup];
return cell;
}
And in my MyTableViewCell's finishedSetup:
cellContentView = [[MyTableViewCellContentView alloc] initWithFrame:CGRectMake(0, 0, 320, 80) cell:self];
cellContentView.autoresizingMask = UIViewAutoresizingNone;
cellContentView.contentMode = UIViewContentModeRedraw;
[self.contentView addSubview:cellContentView];
And in MyTableViewCellContentView I implement the drawRect method. And plan to not use any subviews but draw my custom content just as the Apple example does in the CompositeSubviewBasedApplication.
The problem is that for a few sections I want to use a clearColor backgroundColor. This works, until a cell with a non-clearColor backgroundColor is reused to draw a clearColor cell, at which time the background is not cleared and will still have the old color.
How can I make the background redraw?
Edit:
I forgot to mention, I'm setting the background color in MyTableViewCellContentView's init after calling super's init. Setting it via:
self.backgroundColor = [UIColor clearColor];
I've verified that this in fact does get called and is called as expected with clearColor or redColor.
I've also tried setting the table cell's background color, it didn't help.
Edit #2: Here's my drawRect method:
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
static int i = 0;
[[NSString stringWithFormat:#"Cell %d", ++i] drawAtPoint:CGPointMake(3, 3) withFont:[UIFont boldSystemFontOfSize:16]];
}
To make the background color setting take effect you need to do the setting in tableView:willDisplayCell:forRowAtIndexPath - the OS will not alter anything you set here. The reason is that some additional setup of the cell gets done by the OS after you return it from tableView:cellForRowAtIndexPath. If you can, get a look at session 101 from WWDC 09 presented by Jason Beaver.
I haven't found out why it was happening. But I've tried a different strategy. I'm no longer reusing table cells, since I don't have that many of them.
Instead I'm creating all cells at startup and actually get a boost in performance when showing them since this way there is no additional setup needed when scrolling.