UITableView ContentSize when using dynamic height on UITableViewCells - ios

I've been searching for this for many hours but didn't find a way to make it.
I have a UITableView whose UITableViewCells use AutomaticDimension for Height:
tableView.RowHeight = UITableView.AutomaticDimension;
tableView.EstimatedRowHeight = 160f;
The problem is when I try to get tableView's ContentSize. It seems to be calculated based on EstimatedRowHeight instead of the current height of its rows. Suppose, if there are 10 Cells then ContentSize's returned value is 160x10.
Then my question is if there is a way to do this.
Any help will be really appreciated... I use Xamarin.iOS but Obj-C and Swift answers are obviously welcome :)

I think #Daniel J's answer only works because of the way the animation completion handler is scheduled - on the face of it, there is no guarantee the table reload will have happened before the animation completes.
I think a better solution is (in Swift, as that is what I was using):-
func reloadAndResizeTable() {
self.tableView.reloadData()
dispatch_async(dispatch_get_main_queue()) {
self.tableHeightConstraint.constant = self.tableView.contentSize.height
UIView.animateWithDuration(0.4) {
self.view.layoutIfNeeded()
}
}
}
I actually used the animation delay because of the effect I wanted, but it does also work with a duration of zero. I call this function every time I update the table contents and it animates in and resizes as necessary.

I had a scenario where I needed to know the contentSize as well, to set the frame on a non-fullscreen tableView that had varying cell counts of varying heights. Then I'd adjust the tableView (via NSLayoutConstraint) to be the height of that content.
The following worked, though I feel it's a bit hacky:
Setup all code in 'viewDidLoad'
Set the rowHeight to automatic
Set the estimatedRowHeight to larger than I expected a cell to ever be (in this case, I didn't expect a cell to ever be taller than about 60pts, so set the estimated to 120)
Wrap the tableView reload in an animation block, and then query the contentSize and take appropriate action in the completion block
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 120.0;
[UIView animateWithDuration:0 animations:^{
[self.tableView reloadData];
} completion:^(BOOL finished) {
self.tableViewHeight.constant = self.tableView.contentSize.height;
[self.view layoutIfNeeded];
}];
}

self.coins = coins
tableView.reloadData()
view.layoutIfNeeded()
tableViewHeightConstraint.constant = tableView.contentSize.height
view.layoutIfNeeded()
worked without UITableViewAutomaticDimension and estimatedRowHeight. iOS 11

tableView.reloadData()
view.layoutIfNeeded()
tableViewHeightConstraint.constant = tableView.contentSize.height
view.layoutIfNeeded()
Before getting tablaview.contentSize.height
reload tableView
You need to update the view.

Related

Updating content size height

I have a UIScrollView consisting of a UITableView tucked below a UIView. The UITableView has cells of dynamic height, and I'm having trouble setting the contentSize of the scroll view. If I set it in viewDidLayoutSubviews, it works fine, but calling it from viewDidLoad won't work. Calling it from viewDidLayoutSubviews is a problem though because that function is refreshing every few seconds, and I'm not sure how to change the contentSize of the scrollView if I add a new element to the tableView. This is what I have so far:
DispatchQueue.main.async {
UIView.animate(withDuration: 0, animations: {
self.tableView.layoutIfNeeded()
}) { (complete) in
print(self.tableView.contentSize.height)
self.tableViewHeightConstraint.constant = self.tableView.contentSize.height
self.scrollView.contentSize.height = self.tableView.contentSize.height + self.view.frame.width/2 + 10
print(self.scrollView.contentSize.height)
}
}
So my question is really, how can I make it work from viewDidLoad?
You can use ( Dispatch.main.async After ) to call it from viewDidLoad , or you set it in viewDidLayoutSubviews and put resize code in if statement with once bool value to be executed one time by setting once = false inside the IF statement

UICollectionView isn't scrolling to indexPath

I have a UICollectionView that I set the translatesAutoresizingMaskIntoConstraints to NO, and added some constraints. When I try scrolling it to an indexPath:
[self.datesCollectionView selectItemAtIndexPath:selectedCellIndexPath animated:YES scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
Then it completely ignores that, and doesn't scroll. But when I remove translatesAutoresizingMaskIntoConstraints, then it scrolls, but the constraints are ignored.
My question is, How can I get the collectionView to scroll to an indexPath when translatesAutoresizingMaskIntoConstraints is set to NO?
After looking at your code, I found the problem. I'm not sure why this is the case, but the contentSize of the collection view was zero after applying the constraints. Adding a call to layoutIfNeeded to either setSelectedDate: or setDates: fixed the problem,
- (void)setDates:(NSArray *)dates {
_dates = dates;
[self.datesCollectionView layoutIfNeeded];
[self.datesCollectionView reloadData];
self.selectedDate = nil;
}

How to stop UITableView from update contentSize

When reloadData gets called, it seems that the UITableView recalculate its contentSize automatically so that the content will fit (see screenshot of the call stack in Xcode). How do I stop that?
I want to have the contentSize to be bigger than its content in some cases, when the table is partly obscured. But any changes of the contentSize will disappear after reloading.
You need to change the contentSize of UITableView after every "reloadData" call, you can do this :
[self.tableView reloadData];
self.tableView.contentSize = CGSizeMake(self.tableView.contentSize.width, self.tableView.contentSize.height < 44 ? 150/*any desired value you like*/ :self.tableView.contentSize.height);
I solved it by using contentInset instead, like this:
float extraSpaceAtTheBottom = 50;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, extraSpaceAtTheBottom, 0.0);
Set a layout constraint for height on the tableView.

UILabel in UITableViewCell with auto layout has wrong height

I have a UITableView with cells that have a fixed height of 100 points. The cells are created in a xib file that uses 3 constraints to pin a UILabel to the left, right and top edges of the cell's contentView. The label's vertical hugging priority is set to 1000 because I want the cell's height to be as small as possible.
When the width of the cell in the xib file is set to 320 points, the same as the tableView's width on the iPhone, autolayout works as expected. However, when I set the width of the cell to less than 320 points, I get unexpected results. (I want to use the same cell in tableViews that have different widths, e.g. in a universal app)
For example: when I set the width to 224 points and give the label a text that takes up 2 lines at that width, the label's height will increase to fit the 2 lines, but when the cell is then resized to 320 points to fit in a tableView of that width, the text only takes up 1 line, but the height of the label remains at 2 lines.
I have put a sample project on GitHub to demonstrate the problem: https://github.com/bluecrowbar/CellLayout
Is there a way to make the UILabel always resize to hug its text content?
Adding this in the cell subclass works:
- (void)layoutSubviews
{
[super layoutSubviews];
[self.contentView layoutIfNeeded];
self.myLabel.preferredMaxLayoutWidth = self.myLabel.frame.size.width;
}
I found this on http://useyourloaf.com/blog/2014/02/14/table-view-cells-with-varying-row-heights.html.
Update 1: This answer was for iOS 7. I find auto layout in table view cells to be very unreliable since iOS 8, even for very simple layouts. After lots of experimentation, I (mostly) went back to doing manual layout and manual calculation of the cell's height.
Update 2: I've run some tests on iOS 9 and it seems that UITableViewAutomaticDimension finally works as advertised. Yay!
Stupid bug! I've lost almost one day in this problem and finally I solved It with Steven Vandewghe's solution.
Swift version:
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.layoutIfNeeded()
self.myLabel.preferredMaxLayoutWidth = self.myLabel.frame.size.width
}
Since you're constraining the label's width, the intrinsicContentSize honors that width and adjusts the height. And this sets up a chicken and egg problem:
The cell's Auto Layout result depends on the label's intrinsicContentSize
The label's intrinsicContentSize depends on the label's width
The label's width depends on the cell's Auto Layout result
So what happens is that the cell's layout is only calculated once in which (2) is based on the static width in the XIB file and this results in the wrong label height.
You can solve this by iterating. That is, repeat the Auto Layout calculation after the label's width has been set by the first calculation. Something like this in your custom cell will work:
- (void)drawRect:(CGRect)rect
{
CGSize size = self.myLabel.bounds.size;
// tell the label to size itself based on the current width
[self.myLabel sizeToFit];
if (!CGSizeEqualToSize(size, self.myLabel.bounds.size)) {
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
[super drawRect:rect];
}
original solution does not work reliably:
- (void)layoutSubviews
{
[super layoutSubviews];
// check for need to re-evaluate constraints on next run loop
// cycle after the layout has been finalized
dispatch_async(dispatch_get_main_queue(), ^{
CGSize size = self.myLabel.bounds.size;
// tell the label to size itself based on the current width
[self.myLabel sizeToFit];
if (!CGSizeEqualToSize(size, self.myLabel.bounds.size)) {
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
});
}
I'm using XCode 10 with iOS 12 and I still get autolayout problems with cells not being given the correct height when the table is first presented. Timothy Moose's answer didn't fix the problem for me, but based on his explanation I came up with a solution which does work for me.
I subclass UITableViewController and override the viewDidLayoutSubviews message to check for width changes, and then force a table update if the width does change. This fixes the problem before the view is presented, which makes it look much nicer than my other efforts.
First add a property to your custom UITableViewController subclass to track the previous width:
#property (nonatomic) CGFloat previousWidth;
Then override viewDidLayoutSubviews to check for width changes:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat width = self.view.frame.size.width;
if (self.previousWidth != width) {
self.previousWidth = width;
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
}
This fixed issues with my table cells sometimes being given the wrong height initially.
I know this is an old issue, but maybe this UILabel subclass can also help for some:
class AutoSizeLabel: UILabel {
override var bounds: CGRect {
didSet {
if bounds.size.width != oldValue.size.width {
self.setNeedsUpdateConstraints()
}
}
}
override func updateConstraints() {
if self.preferredMaxLayoutWidth != self.bounds.size.width {
self.preferredMaxLayoutWidth = self.bounds.size.width
}
super.updateConstraints()
}
}
Note: works also for cases when your UILabel won't size itself correctly when inside of a StackView
I usually add these two lines to viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 96
This will automatically resize the cell

Get height of UITableView without scroll bars

I need to get the full height of a UITableView (i.e. the height at which there would be nothing more to scroll). Is there any way to do this?
I've tried [tableView sizeThatFits:CGSizeZero], but that only returns a 0x0 CGSize.
Try the contentSize method, which is inherited from UITableView’s superclass, UIScrollView. However, you may find that contentSize returns an incorrect or out of date value, so you should probably call layoutIfNeeded first to recalculate the table’s layout.
- (CGFloat)tableViewHeight
{
[tableView layoutIfNeeded];
return [tableView contentSize].height;
}
Obligatory Swift 3.0 & 2.2 answer.
var tableViewHeight: CGFloat {
tableView.layoutIfNeeded()
return tableView.contentSize.height
}
Try passing in a different CGSize parameter instead of CGSizeZero. The sizeThatFits: method uses that parameter to calculate its result. Try passing in self.view.size from whatever class is making that call.
If a table view rows count changed and you indeed need to know the content size of table view incorporating the last changes, I didn't find that layoutIfNeeded method actually helps.
After a little bit hacking, I get to know how to force table view recalculate its content size. In my case, it is enough to reset table view frame to get it working:
- (CGSize)com_lohika_contentSize
{
CGRect theFrame = self.frame;
self.frame = CGRectZero;
self.frame = theFrame;
[self layoutIfNeeded];
return [self contentSize];
}

Resources