I'm constructing a UITableView with variable height custom table cells, their height determined by the size of a contained multi-line UILabel. I've got the tableView:heightForRowAtIndexPath: delegate method wired up and calculating the final height correctly using sizeWithFont:constrainedToSize:.
I've run across strange issue: by the time the data source method tableView:cellForRowAtIndexPath: is called, the correct per-row height has already been determined as described above, but the frame of the cell does not match that height. Instead, the frame.size.height property of the cell is the default cell height of the table view (86 px, as I've set it in Interface Builder, the correct height when the contained UILabel has just one line of text), instead of being the height that tableView:heightForRowAtIndexPath: determined correct for that index path.
I'm producing the cells in cellForRowAtIndexPath: using dequeuing, that is,
// Using storyboards, this never returns nil, no need to check for it
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"SomeIdentifier"];
NSLog(#"%f", cell.frame.size.height); // 86, not correct if the cell contains a multi-line UILabel
It seems, then, that whatever iOS is doing behind the scenes, the dequeuing is not setting the frame property of the cell to match the calculated height. This in itself is not that surprising, dequeuing concerns itself with cell instances, not their geometry. The cells are rendered correctly, though, so the height property is being set somewhere, but it happens after cellForRowAtIndexPath:.
So: when I initially populate the table view, cell.frame.size.height is 86 for all the cells as they appear for the first time when I scroll the list downwards. Since the correct geometry is set sometime after the first cellForRowAtIndexPath: for each row before it's displayed, when I scroll back up, the height property is correct for each cell that comes back into view after being reused.
After this I can scroll the table view back and forth at will, and the height property remains correct for each cell from that point on.
What's the correct way of getting the correct cell height the first time around, before any dequeue-based reuse happens? I need this to do a bit of re-positioning of the subviews of the table cell. Do I need to manually call heightForRowAtIndexPath: in cellForRowAtIndexPath: and then manually set the frame of the freshly created CustomCell instance to match that height? This seems redundant, and I'd need to create a mechanism to detect when the cell is created for the first time with the wrong frame height vs. when it is dequeued with the correct frame height later to avoid this redundancy.
So, if someone can shed some light into what the logic is behind this, I'd appreciate it.
As suggested by Flexo, answering this myself is apparently better than adding an edit to the question. So, here's the previous edit as an answer:
Nevermind, I should read the docs better. I can get the correct frame in the tableView:willDisplayCell:forRowAtIndexPath: method of UITableViewDelegate, so that is the correct place to do subview customization that relies on the correct frame being set, not cellForRowAtIndexPath:.
Interesting that the docs say this, though:
After the delegate returns, the table view sets only the alpha and frame properties, and then only when animating rows as they slide in or out.
...since the correct frame is already there when this delegate method is called. But anyway, problem solved.
Don't forget that the cell is a UIView, so overriding layoutSubviews is also a valid way to get the correct frame and adjust size/position of subviews. Just don't forget to call [super layoutSubviews].
Easiest way I found was just to call cell.layoutIfNeeded() before you do any setup on the cell. This makes sure all the layout constraints are calculated and the frames are set.
Related
I have a custom table view cell. I have a UIView in the tableview cell that is shown only when the table is expanded. I toggle the height for table view cell each time on tap to show the UIView. I also need to detect clicks on some of the components of UIView.
->tablecell1
-->UIView1 height h1
->tablecell2
-->UIView2 height h2
The cell height of the cell should vary according to the size of UIView. Currently I am calling
tableView:heightForRowAtIndexPath: for varying the height on cell click. However this doesn't work if the UIViews are of variable heights and the bigger view gets clipped.
Is there a better way of solving it?
Ok so real quick tableView:heightForRowAtIndexPath: shall not be called by your controller.
The biggest problem I always run into when dynamically sizing cells is that that heightForRowAtIndexPath is called very early on in the table layout process, so essentially the height of each cell must be know prior to asking the tableview to layout. Anyway I am going to assume that you are using a custom table view cell and have placed a UIView inside there... If you haven't, do.
First: Throw a class function into the CustomTableViewCell called heightNeededForTableViewCellWithView:(UIView *)view and determine the height you would want, handle the condition where view is nil and what the default size shall be.
Second: Call this class function when tableView:heightForRowAtIndexPath: is called and since you own the datasource in your controller you can dynamically return the heightNeededForTableViewCellWithView:viewAtRow based off the view you would want to show for that row!
Third: When a user taps a cell, remember which index they tapped and call [tableView reloadData] which will call tableView:heightForRowAtIndexPath:
My problem is very similar to this one, though the selected correct answer (which is just to use tags) didn't work for me (nor did any other answer): Changing label position in UICollectionViewCell.
I have a UICollectionView with a custom UICollectionViewCell. The cell has a UIView in it, which is referenced via IBOutlet property. I'm simply trying to change the frame within the cellForItemAtIndexPath method, and while the frame of the UIView is in fact changed, it is subsequently ignored, or not honored.
However, once the UICollectionView is scrolled a bit and it begins reusing cells, the reused cells do honor the previous change to the UIView's frame, and the UIView looks just like it should. So for some reason the first time cellForItemAtIndexPath is called for a particular cell, the frame change is ignored, but is subsequently honored once the cell is reused.
Actually, something more curious which I just noticed is that a reused cell has the size of the frame that had been previously changed, but keeps the original origin, which happens to be (0, 0), though I'm trying to update it to something like (0, 50).
Changing other properties of the UIView works fine, such as changing the background color. What might be happening to prevent the UIView's frame from initially changing?
UICollectionViewCell does not update frames for views that are defined in storyboard or xib files.
You have to create the view programmatically in your custom cells initWithFrame and/or awakeFromXib files.
Cheers!
hmmmm
I may be mistaken. I know this is true for UITableViewCell
Cannot update UI object's frame in UITableViewCell
For some odd reason, my table view sets the height of each custom UITableViewCell to a default value of 44.0f despite me explicitly returning a value of 113.0f in the datasource method. I made sure that I set my view controller as the datasource and delegate so that's not the issue but I'm completely stumped. I saw another SO post where someone was asking a similar question, the tableview wasn't getting the proper cell height until after a forced refresh. I'm even calling [tableView reloadData] in every possible uiviewcontroller method and that's not changing anything. Anyone have any ideas/possible fixes?
EDIT:
So here's the weird part. The cell's are visually getting set to the right height but their frame's are returning a value of 44.0f. What's more, all the subviews are sized down to 44.0f even though when I tap a cell, all 113 pixels gets highlighted.
If you use prototype cells, or if you load their interface from a .xib file, check if they have the right height in the Interface Builder
In iOS6, I cannot seem to get the width of the cell in cellForRowAtIndexPath for the grouped table style. Logging either the frame or the bounds for either the cell or its contentview returns 320 - even on iPad. I need to determine the cell width programmatically for any device as I need to calculate text sizes. Any advice in getting the correct cell width for a grouped tableview in cellForRowAtIndexpath would be appreciated please
The method you're using is the wrong place to calculate any kind of view-related constraints. The -tableView:cellForRowAtIndexPath: method is part of the table view's data source, not its delegate. You cannot rely on the frame or anything else here to be meaningful, it's meant as the place to configure the cell's /data/.
If you need to make calculations to view frames and such, and you're not using a custom subclass of UITableViewCell (i.e., you're just adding views to a default instance of UITableViewCell or configuring stock views), you would set up any frame-related / view specific attributes in the /delegate/ callback -tableView:willDisplayCell:forRowAtIndexPath: method. This is the place to configure any of the visible/view-related properties of your cell, and you will now have accurate layout information for the cell (its bounds will be correct, any layout/configuration of internal views will be complete, etc.).
If you have a custom subclass already, you can either do your view related property configuration in the delegate callback above, or you can do it in UIView's -layoutSubviews method, depending on your exact needs. For more information, see the documentation for -tableView:willDisplayCell:forRowAtIndexPath:.
In my table view I am using custom image for the UITableView cell's background. It works perfectly except for the fact it hides the row separator. And now there is no row visible.
Is there a way to increase the distance so that cells will get separated.
Please help!!!
Thanks,
Without seeing what you are seeing, it's a bit hard to tell. That said, you could try adding the separator to the image so that it shows up in the cell.
If you want to change the height of the cell, implement the -tableView:heightForRowAtIndexPath: method in the table view delegate. Just return the correct height for the row at indexPath. I believe there is also a way to set the rowHeight in the table view properties in interface builder if all cells will be the same, but I tend to stay away from IB when possible.
As far as my knowledge you can add a line image to the cell as a footer.So that you can see the separator
I believe you have to override the -setFrame: method in your custom cell and return the same frame with height minus 1 pixel.
Also be sure to call [super layoutSubviews]; in your -layoutSubviews method if you have overridden this method with some custom implementation.
LineSeparator may be invisible to you because of your background image in your cell. Try to change the style of your separator.
Alternative Way:
Otherwise You have to add a one pixel line to the bottom of your Custom cell background image by your designer and if it's still not visible in your tableView, then you have to increase a height for your cell in heightForRowAtIndexPath: method.
Make sure your view if you have one that is inside your Cell is exactly the same size as your cell's size.
Make sure:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
is setting your cell heights correctly for all cells.