I'm doing my project with storyboards and I'm trying to implement a custom UITableViewCell.
I would like to do the following in my custom cell:
#import "CustomCell.h"
#implementation CustomCell
#synthesize myLabel, myButton;
- (instancetype)initWithCoder:(NSCoder *)decoder
{
if ((self = [super initWithCoder:decoder]))
{
//want to custom setup of properties placed in the cell
self.myButton.backgroundColor = [UIColor redColor];//this does NOT work
//and so forth...
}
return self;
}
But the background color is not set. It only works when I set the background color of the button in the tableViewController's cellForRowAtIndexPath function, like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
MyObject *obj = self.myObjects[indexPath.row];
cell.myButton.backgroundColor = [UIColor redColor];//This works!
cell.myLabel.text = obj.name;
return cell;
}
And I have trying debugging by setting break points and NSLog and the initWithCoder gets called before cellForRowAtIndexPath for every cell??
But the background color of the button in the cell does not show when I set it in the custom cell.
Can any help?
Try using the awakeFromNib method instead of initWithCoder: to do any initial customization of the cell. And as mentioned in comments, for simple things like background colors of controls, you can probably just do that in Xcode via the storyboard.
Related
I have a custom UITableViewCell in which I have two UIViews . I want to change the BackgroundColor radius and some more properties of UIViews.
But I am unable to do so.
Here is my setUp.
Step 1:
Create A Custom Cell with XIB.
Step 2: Entered the Cell Identifer name in XIB for CustomCell.
Step 3: Instantiated NIB in viewDidLoad
UINib *nib = [UINib nibWithNibName:#"CustomCell" bundle:nil];
[[self mTableView] registerNib:nib forCellReuseIdentifier:#"CustomCell"];
Step 4: Cell For Row At Index Method:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
[cell updateCellView];
return cell;
}
Step 5: Checked Twice the Outlet Connections all are Fine.
Step 6: Custom Cell Class:
#import "TransportCell.h"
#implementation TransportCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)updateCellView
{
self.backgroundView.backgroundColor = [UIColor redColor];
}
#end
This code has no effect on my Cell View.
I debug the code. When I logged the backgroundView I get nil when updateCellView method get called:
Here is my CustomCell's Xib:
I have to change the Properties of Inner UIView.(Blue in color)
Instead of calling the method in the cellForRowAtIndexPath try this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
return cell;
}
and then in your custom method
- (void)updateCellView
{
self.backgroundView.backgroundColor = [UIColor redColor];
// reload the table view
[self.tableView reloadData];
}
you need to add [ tableview reloadData ]
in method update cell view
Ideally all the info needed to render the cell should come from either the data source or other events. For example, if you are changing the border-color it should mean that either some entity (say user) has performed an event or something has changed in the data-source like a value passed its threshold.
The change in data-source or those events should either trigger refreshing the whole table:
-(void) reloadData
or some cells:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Only then the cells will get updated.
you are changing the self.backgroundview.backgroundColor
Default it is nil for cells in UITableViewStylePlain, and non-nil for UITableViewStyleGrouped.
The 'backgroundView' will be added as a subview behind all other views.
#property (nonatomic, strong, nullable) UIView *backgroundView;
try this method in TransportCell, it's swift code make same for objective c
override func layoutSubviews()
{
self.backgroundView.backgroundColor = [UIColor redColor];
}
Use this:
self.contentView.backgroundColor = [UIColor redColor];
Hope this helps!
Earlier I was creating custom cells without prototype, just custom class, register that class for reuse identifier with my UITableView and use like that.
In that case, I used: - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier and that worked great (except for UITableViewHeaderFooterView, they never knew their frame even though that method was called, frame was 0 0 0 0 - but that's not my question here).
Now I was creating some custom UITableViewCells using Interface Builder. Created their custom class, connected outlets from Interface Builder to my custom class and I'v noticed that - (id)initWithCoder:(NSCoder *)aDecoder is being called and that's where I need to do my modifications.
BUT, I wasn't able. That's the only init method being called, but if I try to change font of my label there, it wont be changed. I also tried to round rect button but that is also not working (my button is also having frame 0 0 0 0 - not yet existing) in that method.
My question is, where should I modify things like font, background color of elements created using Prototype Cells?
(id)initWithCoder:(NSCoder *)aDecoder is the correct initializer.
Try this:
#implementation TESTCell
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.textLabel.font = [UIFont boldSystemFontOfSize:25.0f];
}
return self;
}
#end
Other customizations (of own subviews) goes to:
- (void) awakeFromNib
{
[self.myButton setTitle:#"Hurray" forState:UIControlStateNormal];
}
It works fine. Of course, you have to set the identifier of the cell in your StoryBoard or Interface Builder file to the same identifier you use in your UITableViewController.
I use this piece of code in my UITableViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TESTCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = #"TEST";
return cell;
}
I have created .h and .m files for UITableView called mainTableViewgm.h and mainTableViewgm.m resp. and I am calling -initWithFrame: method from my main view controller to this mainTableViewgm.m implementation file
[[mainTableViewgm alloc]initWithFrame:tableViewOne.frame]
Note that this tableview is in my main view controller. But I have created separate files for the tableView and have also set the custom class to mainTableViewgm in storyboard.
the -initWithFrame: methods appears as follows
- (id)initWithFrame:(CGRect)frame
{
//NSLog(#"kource data");
self = [super initWithFrame:frame];
if (self)
{
[self setDelegate:self];
[self setDataSource:self];
[self tableView:self cellForRowAtIndexPath:0];
[self tableView:self numberOfRowsInSection:1];
// Initialization code
}
return self;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"kource data");
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"kource data2");
UITableViewCell*cellOne =[[UITableViewCell alloc]init];
cellOne.detailTextLabel.text=#"text did appear";
return cellOne;
}
the -initWithFrame: is being called fine along with the 'if (self)' block in this method. But the problem is numberOfRowsInSection: and cellForRowAtIndexPath: are not being automatically called here . kource data/kource data2 never appear in log. What do I do to load the table? Are the delegate/datasource being set incorrectly?
I must mention that I have also set the UITableViewDelegate and UITableviewDataSource protocols:
#interface mainTableViewgm : UITableView <UITableViewDelegate,UITableViewDataSource>
#end
Help will be much appreciated. Thank you.
Your tableview is not loaded when the controller is initializing, so you cannot do that in the init methods. You have to move your code to the viewDidLoad method.
Also you are not setting the delegate and datasource on the tableview object (probably a type, you are setting them on the view controller). It should look like this:
- (void)viewDidLoad:(BOOL)animated {
[super viewDidLoad:animated];
[self.tableView setDelegate:self];
[self.tableView setDataSource:self]; // <- This will trigger the tableview to (re)load it's data
}
Next thing is to implement the UITableViewDataSource methods correctly. UITableViewCell *cellOne =[[UITableViewCell alloc] init]; is not returning a valid cell object. You should use at least initWithStyle:. And take a look how to use dequeueReusableCellWithIdentifier:forIndexPath:. A typical implementation would look like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
// Reuse/create cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Update cell contents
cell.textLabel.text = #"Your text here";
cell.detailTextLabel.text=#"text did appear";
return cell;
}
I can't believe I've been doing XCode programming for two years, and still hit this issue.
I had the same problem with XCode 6.1 - I was setting my UITableView's delegate & dataSource in the viewWillAppear function, but none of the delegate functions were kicking in.
However, if I right-clicked on the UITableView on the Storyboard, the circles for delegate and dataSource were empty.
The solution, then, is to hold down the CTRL key, and drag from each of these circles up to the name of your UIView which contains your UITableView:
After doing this, my UITableView happily populated itself.
(So, we're upto v6.1 of XCode now are we ? Do you think Apple ever going to make this thing, you know, friendly...? I would quite like to add a Bookmark in my code... that'd be a nice feature.)
This is working fine for my plain style table views, but not for my grouped style. I'm trying to customize how the cell looks when it is selected.
Here is my code:
+ (void)customizeBackgroundForSelectedCell:(UITableViewCell *)cell {
UIImage *image = [UIImage imageNamed:#"ipad-list-item-selected.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
cell.selectedBackgroundView = imageView;
}
I have verified that the correct cell is indeed being passed into this function. What do I need to do differently to make this work?
It's not clear from your question whether or not you're aware that the tableViewCell automatically manages showing/hiding it's selectedBackgroundView based on its selection state. There are much better places to put that method other than in viewWillAppear. One would be at the time you initially create the tableViewCells, i.e.:
- (UITableViewCell *)tableView:(UITV*)tv cellForRowAtIP:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
cell = [tv dequeueCellWithIdentifier:#"SomeIdentifier"];
if (cell == nil) {
cell = /* alloc init the cell with the right reuse identifier*/;
[SomeClass customizeBackgroundForSelectedCell:cell];
}
return cell;
}
You only need to set the selectedBackgroundView property once in the lifetime of that cell. The cell will manage showing/hiding it when appropriate.
Another, cleaner, technique is to subclass UITableViewCell, and in the .m file for your subclass, override:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithBla....];
if (self) {
UIImageView *selectedBGImageView = /* create your selected image view */;
self.selectedBackgroundView = selectedBGImageView;
}
return self;
}
From then on out your cell should show it's custom selected background without any further modifications. It just works.
Furthermore, this method works better with the current recommended practice of registering table view cell classes with the table view in viewDidLoad: using the following UITableView method:
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier
You would use this method in your table view controller's viewDidLoad method, so that your table view cell dequeuing implementation is much shorter and easier to read:
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[SomeClass class]
forCellReuseIdentifier:#"Blah"];
}
- (UITableViewCell *)tableView:(UITV*)tv cellForRowAtIP:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"Blah"
forIndexPath:indexPath];
/* set your cell properties */
return cell;
}
This method is guaranteed to return a cell as long as you have registered a class with the #"Blah" identifier.
I have some issues with a custom UITableViewCell and how to manage things using storyboards. When I put the styling code in initWithCoder: it doesn't work but if I put it in tableView: cellForRowAtIndexPath: it works. In storyboard I have a prototype cell with its class attribute set to my UITableViewCell custom class. Now the code in initWithCoder: does get called.
SimoTableViewCell.m
#implementation SimoTableViewCell
#synthesize mainLabel, subLabel;
-(id) initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder:aDecoder]) ) return nil;
[self styleCellBackground];
//style the labels
[self.mainLabel styleMainLabel];
[self.subLabel styleSubLabel];
return self;
}
#end
TableViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NearbyLandmarksCell";
SimoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//sets the text of the labels
id<SimoListItem> item = (id<SimoListItem>) [self.places objectAtIndex:[indexPath row]];
cell.mainLabel.text = [item mainString];
cell.subLabel.text = [item subString];
//move the labels so that they are centered horizontally
float mainXPos = (CGRectGetWidth(cell.contentView.frame)/2 - CGRectGetWidth(cell.mainLabel.frame)/2);
float subXPos = (CGRectGetWidth(cell.contentView.frame)/2 - CGRectGetWidth(cell.subLabel.frame)/2);
CGRect mainFrame = cell.mainLabel.frame;
mainFrame.origin.x = mainXPos;
cell.mainLabel.frame = mainFrame;
CGRect subFrame = cell.subLabel.frame;
subFrame.origin.x = subXPos;
cell.subLabel.frame = subFrame;
return cell;
}
I have debugged the code and found that the dequeue... is called first, then it goes into the initWithCoder: and then back to the view controller code. What is strange is that the address of the cell in memory changes between return self; and when it goes back to the controller. And if I move the styling code back to the view controller after dequeue... everything works fine. It's just I don't want to do unnecessary styling when reusing cells.
Cheers
After initWithCoder: is called on the cell, the cell is created and has its properties set. But, the relationships in the XIB (the IBOutlets) on the cell are not yet complete. So when you try to use mainLabel, it's a nil reference.
Move your styling code to the awakeFromNib method instead. This method is called after the cell is both created and fully configured after unpacking the XIB.