When exactly does layoutSubviews get called on a custom UITableViewCell in a UITableViewCells cellForRowAtIndexPath method? Below, I need layoutSubviews to be called AFTER I set the FiltersTableViewCellItem property. Do I have this set up correctly? I'd like to be able to use layoutSubviews because I heard it's better for performance.
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"FiltersTableViewCell";
FiltersTableViewCell *filtersTableViewCell = [[self dequeueReusableCellWithIdentifier:cellIdentifier] retain];
FiltersTableViewCellItem *filtersTableViewCellItem = [[self.filtersTableViewCellItems objectAtIndex:[indexPath row]] retain];
if (!filtersTableViewCell)
{
filtersTableViewCell = [[FiltersTableViewCell alloc] initWithFiltersTableViewCellItem:filtersTableViewCellItem];
filtersTableViewCell.delegate = self;
}
else
{
filtersTableViewCell.filtersTableViewCellItem = filtersTableViewCellItem;
}
return [filtersTableViewCell autorelease];
}
layoutSubviews is called at some point after tableView:willDisplayCell:, which is called after tableView:cellForRowAtIndexPath:. You can verify this by setting breakpoints in the relevant methods and seeing the order in which they get hit. For example, set a breakpoint at the end of tableView:cellForRowAtIndexPath:. Then add the following method to FiltersTableViewCell
- (void)layoutSubviews
{
[super layoutSubviews];
}
and set a breakpoint there. Then run the app and see what happens.
Try calling setNeedsLayout after setting your item.
filtersTableViewCell.filtersTableViewCellItem = filtersTableViewCellItem;
[filtersTableViewCell setNeedsLayout];
As mentioned in this answer ,
there are many instances where it will be called. I would also say that it might be called when a reusable item is dequeued from the reusable cells queue. Try setting the FiltersTableViewCellItem before looking for reusable items.
Related
I'm creating a Settings View for my app, and in that view is a UITableView. I'm creating custom cells to meet my needs, but I'm having issues - only the last cell is getting [layoutSubviews]. Am I doing something wrong?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//int type = (indexPath.row == 0?1:0);
//if(indexPath.row == 6) type = 2;
NSLog(#"row %i created", indexPath.row);
TableCell *cell = [[TableCell alloc] initWithType:indexPath.row];
cell.textLabel.text = #"Test cell";
return cell;
}
And in my custom cell:
#implementation TableCell
UIImageView *shadowView;
int row;
- (id) initWithType:(int)type {
row = type;
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
self.backgroundColor = [UIColor clearColor];
self.backgroundView = [[UIView alloc] init];
UIImage *shadowImage = [UIImage imageNamed:#"CellShadow"];
shadowImage = [shadowImage resizableImageWithCapInsets:UIEdgeInsetsMake(14, 14, 14, 14)];
shadowView = [[UIImageView alloc] initWithImage:shadowImage];
[self.contentView addSubview:shadowView];
//[self.contentView sendSubviewToBack:shadowView];
NSLog(#"agreed, row %i created", row);
[self layoutSubviews];
return self;
}
- (void) layoutSubviews {
NSLog(#"row: %i", row);
[super layoutSubviews];
shadowView.frame = CGRectMake(
0, 0,
self.contentView.frame.size.width,
self.contentView.frame.size.height
);
}
#end
Continuously, only the last cell #6, is reported when I rotate, or when layoutSubviews should be called. Any suggestions?
Do not call layoutSubviews directly. Use [self setNeedsLayout] or [self layoutIfNeeded]. But do not call these at all in the cell's init method.
Also, do not call [[TableCell alloc] initWithType:indexPath.row]; directly, either. Instead, use...
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath
Once you've built that cell, you can tell it it's row, but be aware that the cells get recycled as the table scrolls, so you must update that value on every call to cellForRowAtIndexPath.
The cells ought to get layout again (without you making any calls direct or indirect) when the table view is resized.
See the tableview doc here.
You should never call layoutSubviews directly, it will be called automatically by iOS once the cell is ready to display. You should also deque the cell as #danh is recommending. If you're not very comfortable with all this, then I'd really recommend you have a look at the free Sensible TableView framework, which automates creating these kind of settings views (I create mine in a couple of lines, really).
The issue was of my own poor code. Using cell.backgroundView helped a lot here.
Never Call layoutSubviews by yourself. It will be called when ever frames of subview in cell are changed. Even if just change the text of labels in your custom cell wont call layoutSubviews. Ue the deque of cells for reusing for better performance. As it wont allocate cell every time. And in you code looks like has lot of memory issues since cell allocated wont be released and new cell is created.
I can not set the custom cells accessory view while the page loads for the first time, any ideas?
In DetailView customcell's class I have this:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if(self.selected)
self.accessoryType=UITableViewCellAccessoryCheckmark;
else
self.accessoryType=UITableViewCellAccessoryNone;
}
And In tableview controller:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewCell *cell = [DetailViewCell cellForTableView:tableView];
if(somelogichere){
[cell setSelected:YES];
}
return cell;
}
I debug and hit that line so logic is true, but in debug I also see that after it sets the accesory then again it calls twice the selected method, which overrrides the accessory to none in the else inthe second call. setSelected causes the selected method to be called twice and overrides the setting on the second call cause somehow on second call self.selected returns false;
UPDATE: I solved the problem by creating a boolean cellSelected property in the custom cell class and changing and checking its status rather than setting and changing the selected property of the cell, this is also better cause I can support multi selection tableview's better in future.
Does it work the second time after the view has loaded?
It most probably has something to do with following line:
if(somelogichere){
[cell setSelected:YES];
}
What is somelogichere ?
Depending on somelogichere your selected property might not be true the first time you run your code:
if(self.selected)
self.accessoryType=UITableViewCellAccessoryCheckmark;
else
self.accessoryType=UITableViewCellAccessoryNone;
Make sure you set your cell's selected property in the beginning. Or change the code inside your setSelected.
HTH
You try this code its help u
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
UIImage *selectionBackground = [UIImage imageNamed:#"list_BG.jpg"];
UIImageView *iview=[[UIImageView alloc] initWithImage:selectionBackground];
self.selectedBackgroundView=iview;
}
OR
In cellForRowAtIndexPath method of tableView:
cell.accessoryType = UITableViewCellAccessoryCheckmark;
I've created a custom UITableViewCell but am having trouble updating the contents of the cell. When I have multiple cells in the table, the table is not drawing the correct images in the cell. The images in each cell should be unique, however I am seeing different cells with the same image. The table seems to be placing the cells at random.
I've checked my data source with NSLog and the names are correct. I can correct the issue when I don't use - (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier, but instead create a new cell each time in - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath.
Any suggestion on what I may be doing wrong? Please have a look at my code below.
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ScoreCell *cell = (ScoreCell *)[_tableView dequeueReusableCellWithIdentifier:#"CellID"];
if (cell == nil)
{
cell = [[[ScoreCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellID"] autorelease];
}
BoxScore *boxScore = [_gameDayData objectAtIndex:indexPath.row];
[cell setScoreImage:[UIImage imageNamed:boxScore.name]];
return cell;
}
ScoreCell.h
#interface ScoreCell : UITableViewCell
{
UIImage *scoreImage;
}
#property(nonatomic, retain)UIImage *scoreImage;
#end
ScoreCell.m
#implementation ScoreCell
#synthesize scoreImage;
- (void)dealloc
{
[scoreImage release], scoreImage = nil;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[scoreImage drawAtPoint:CGPointMake(5,5)];
}
#end
In addition to the other comments, be sure to implement the -prepareForReuse method in your ScoreCell class. It gets called when the cell is to be reused, at which point you should clear the image. Be sure to call [super prepareForReuse]; in your implementation. This will prevent the cell from being reused with the wrong image.
There are two non-related problems with your image handling.
Scrolling off and on the screen will cause a cell to load the image twice (or a hundred times, depending on the user).
You want a
- (UIImage *)boxScoreImageForIndex:(NSInteger)index
method to (lazy) load, hold on to, and provide the image for the cell.
You also don't want to use imageNamed:, in your case it will cause twice as much memory usage than needed. Use imageWithContentsOfFile: instead.
You are not clearing the previous image. When a cell is dequeued it is not dealloced.
So sometimes the image that was drawn on the cell the times before it show through in front of the new image.
In drawRect you need to clear everything.
Either do:
CGContextClearRect( context , [self bounds] );
Or set clearsContextBeforeDrawing on the cell when it is created.
After parsing JSON data in a Data class, I set the UIViewController's NSArray *headlines property in a fillArrays method of the same Data class. In the viewDidAppear method of my UIViewController, I call reloadData on my UITableView. numberOfSectionsInTableView fires and returns 1, then numberOfRowsInSection fires and returns an array count of 4 (for 4 strings in the array). However, control never gets to cellForRowAtIndexPath and I'm having the hardest time understanding why, especially since I have valid sections and rows. The cells are all visible.
I've added the UITableViewDataSource and UITableViewDelegate protocols to the UIViewController interface and set the UITableView's delegate and dataSource to self in viewDidLoad (which also is verified by the row and section count methods being called).
I'm wondering if it has something to with me reinitializing the UIViewController in Data.m in order to set its properties.
In Data.m:
- (void)fillArrays:(NSArray *)jsonObjs {
NSLog(#"fillArrays");
HeadlinesRootViewController *hrvc = [[HeadlinesRootViewController alloc] init];
hrvc.headlines = [self getJsonValuesForKey:#"headline" inArrayOfObjects:jsonObjs];
[hrvc viewDidAppear:NO];
}
In ViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad");
// Table view
headlineTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 180, self.view.bounds.size.width, 300) style:UITableViewStylePlain];
[headlineTableView setDelegate:self];
[headlineTableView setDataSource:self];
// Temporary
self.headlines = [[NSMutableArray alloc] initWithObjects:#"headline1", #"headline2", #"headline3", #"headline4", nil];
[self.view addSubview:headlineTableView];
self.headlineTableView = headlineTableView;
[headlineTableView release];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"viewdidappear");
NSLog(#"headlines: %#", self.headlines); // Returns an array of 4 headlines
if( [self.headlines count] != 0 ){
[self.headlineTableView reloadData];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"numberOfSectionsInTableView: 1");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"numberOfRowsInSection: %d", [self.headlines count]);
return [self.headlines count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
cell.text = [[NSString alloc] initWithFormat:#"%#", [self.headlines objectAtIndex:indexPath.row]];
return cell;
}
In fillArrays, you create another view controller - but you never do anything with it or its view, you would never see that view. You would never call viewDidAppear manually either, that happens automatically when a view controllers view is displayed (ONLY in the context of a navigation controller though).
Normally the flow is, you create a view controller and either add that view as a subview of a current view, or push it as a new window via a navigation controller. I'm pretty sure your whole issue is that they table is never added to a view anyone actually sees, so the table calls the other methods but never calls cellForRow because its layoutSubviews code is simply not being called.
Have you added your tableView to the view of UIViewController?
It happened to me, and when I added this
[self.view addSubview:table];
[table release];
then cellForRowAtIndexPath started working.
For Google's sake:
If tableView:numberOfRowsInSection returns zero for whatever reason tableView:cellForRowAtIndexPath will not get called because there are no rows to call it for.
Check to make sure that the tableView delegate and dataSource are pointed to the viewController
I cannot see anything wrong with the code as-is, have you verified with breakpoints that cellForRow is never reached (even though I see you have a log statement)?
Also I would try just for a sanity check to return "1" explicitly in rowsInSection, and hardcode a string in the cell you are returning in cellForRow.
If all else fails, create a new table view controller from the XCode templates and put your calls in there - then when that works, work backwards to why your code does not.
Also, it would be good to see your viewDidLoad setup code (add to answer above please).
if you're setting the delegate and datasource at viewDidLoad, then that may be the source of your bug. Can you set the datasource and delegate in init?
I'm not sure that you add your UITableView as subview to UIViewController.view. This was my approach anyway.
In this approach, I found execution did not get into cellForRowAtIndexPath until I sent UIViewController.view to the back after adding UITableView as subview.
Getting this far was only part of the problem. At this point, it seemed that my other view controllers no longer respond to touch events. I found that when I also add the UITableView as a subview to the rootViewController, all my views got the appropriate touch events.
Thank you so much pxl. When I move the UITableView initialization from viewDidLoad to:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil"
it works perfectly when ever I make delete or update some rows the UITableView gets reloded to my UIView.
Swift version
Add self.table.layoutIfNeeded() and then self.tableView.reloadData()
I previously used the following code for changing the size of my cells which use a custom UITableViewCell:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 61;
}
However I've created a new one on a different view and the above code is never being called.
The UITableViewCell is actually a control on a more generic view however all the other similar methods cellForRowAtIndexPath etc are
Make sure you've set your class as the delegate for the table view, in addition to the dataSource. They are two separate protocols, and this is easy to overlook.
Seems I needed this as well
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {
CGRect CellFrame = CGRectMake(0, 0, 300, 60);
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CellFrame reuseIdentifier:cellIdentifier] autorelease];
return cell;
}
Also, you can accomplish the same thing (I think) in IB by tweaking the values on the UITableView (not the UITableViewCell).
heightForRowAtIndexPath is only a UITableView delegate method, not a UITableViewCell method.
So you'd have that code in your delegate class, not in the table cell code.