Update tableview cell height after updating image height constraint - ios

I have tableview cell like this.
And I update that image height constraint and reload. I am using auto-resizing cell and testing on iOS 10.
How to animate UITableViewCell height using auto-layout?
imageHeightConstraint.constant = 200 //example only. it will be dynamic. Some image height will be 200, 400, etc
self.contentView.layoutIfNeeded()
UIView.setAnimationsEnabled(false)
self.delegate?.getTableView!().beginUpdates()
self.delegate?.getTableView!().endUpdates()
UIView.setAnimationsEnabled(true)
I tried to reload only 1 row too and not okay.
self.contentView.layoutIfNeeded()
let indexPath = IndexPath(item: self.tag, section: 0)
self.delegate?.getTableView!().beginUpdates()
self.delegate?.getTableView!().reloadRows(at: [indexPath], with: .none)
self.delegate?.getTableView!().endUpdates()
Problem is it doesn't change height at all unless I reload table like this.
imageHeightConstraint.constant = 200
self.delegate?.getTableView!().reloadData()
But I don't want to call reloadData since it will reload all visible cell and resource intensive (laggy too when user scroll quickly). How shall I update my image constraint properly and update table view cell (auto-resizing) height correctly?
Edit - Using with intrinsic size
I delete my height constraint and just use property of intrinsic size in UIImageview. Then height is correct but width is too big (since it is intrinsic). How shall I do?

ImageViews have intrinsic size. That means they take size based on the content of image. Unless you apply the width and height constraint on image ur imageView should be able to resize itself as per the image size.
You can make use of tableView's height property
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 100
Now select your cell and set the leading, trailing,top and bottom constraint. Make sure not to set height/aspect ratio constraint manually.
Now if you simply load the image to imageView, cell will automatically take size based on the size of the image.
Edit:
This works if the image you are loading is in specific size range. If you want to scale down the image or set the height constraint to image and update its height once you download the image, you need not reload the whole tableView rather you can reload a specific cell in tableView
self.tableView.reloadRows(at: [yourIndexPath], with: .none)
In this case make sure you implement
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
}
EDIT:
Set your imageView content mode to AspectFit or AspectFill from ScaleTofill

Related

Pin cell height to UICollectionView height

I have a horizontal UICollectionView that's contained inside a UICollectionView header of a vertical UICollectionView. The header of the vertical UICollectionView has a dynamic height that changes due to user interaction (dragging the outer UICollectionView down expands it, scrolling up collapses it). The horizontal UICollectionView is constrained to the size of the header and will also shrink in height when the header is reduced in height. When this happens I get this error
the behavior of the UICollectionViewFlowLayout is not defined because:
2018-07-19 13:42:09.959 IDAGIO[81891:2239798] the item height must be
less than the height of the UICollectionView minus the section insets
top and bottom values, minus the content insets top and bottom values.
2018-07-19 13:42:09.959 IDAGIO[81891:2239798] Please check the values
return by the delegate.
because the UICollectionView shrinks in height, but the cells of it keep their old size and are therefore higher than allowed (I only get this error while scrolling up, so it's not related to any insets, the heights are fine, they just don't match exactly at that point in time).
I already tried to call collectionView.collectionViewLayout.invalidateLayout() every time I get a scroll event of the vertical collection view and then return the collection view size in
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
but the layout update then is always a bit too late, so I still get the above error. Also it looks weird as the cell height also visually is a bit behind.
My question: is there a way to dynamically respond to height changes of a horizontal UICollectionView so the cell height is updated accordingly and in time? (Maybe setup some height constraint that automatically keeps the cell height equal to the collection view height?)
I know about dynamic cell sizing, but that usually means to constrain a cell to the size of it's content. What I want to achieve is to always constrain the height of the cell (and it's content) to the collection view height.
I found an easy solution myself, without having to pass or store the scroll offset at all:
Just subclass UICollectionViewFlowLayout, override shouldInvalidateLayout and set the itemSize to the size of the new bounds.
override func shouldInvalidateLayout(forBoundsChange: CGRect) -> Bool {
if !forBoundsChange.size.equalTo(collectionView!.bounds.size) {
itemSize = forBoundsChange.size
return true
}
return false
}
In Swift 4.2
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if !newBounds.size.equalTo(collectionView!.bounds.size) {
itemSize = newBounds.size
return true
}
return false
}
UICollectionView has a property derived from UIScrollView called contentinsetadjustmentbehavior, setting that to false should fix your issue if you are setting the size of the items on the collectionView to the size of the bounds of the collectionView.
Because the scrollView can be adjusting the insets which in turns changes the collectionView.adjustedContentInset
https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior
So basically setting:
collectionView.contentInsetAdjustmentBehavior = .never
Reloading data on scroll event is one of the options but it is definitely not the best one because of all the calculations involved in reloading data again and again. You'll probably find a better solution by subclassing UICollectionViewLayout and do all the calculations on prepare(). Then you can just use delegation to pass the scroll offset to your custom layout and make the cell sizing accordingly.

Sending heightForRow:atIndexPath height value after calculating it in-cell

I have a UITableView that has few cells with different heights. Each row's is dynamically adjusted according to the content it contains.
I have a custom UITableViewCell with a UICollectionView in it that should be resized according to how many cells it has (all the cells should be visible, without inside scrolling).
I also have a UITableViewCell with another UITableView that should be resized according to how many cells it has (all the cells should be visible, without inside scrolling).
The problem is that I don't have the contentSize of the collectionView and the tableView while heightForRow:atIndexPath gets called so I can't set the values to something right.
I've tried to set it to UITableViewAutomaticDimension and to set the cell.contentView.frame.size.height to the contentSize when it got set (added an observer on "contentSize") but then the cells were on top of each other (the collectionView was ontop of the tableView instead of above it).
The tableView code is a regular tableView code.
The collectionView code is:
self.collectionView.collectionViewLayout = UICollectionViewLeftAlignedLayout()
let collectionViewFlowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
collectionViewFlowLayout.estimatedItemSize = CGSize(width: 34, height: 50)
What is the best way to adjust the size of the tableView and the collectionView?
Thank you!
You can just ask UITableView to adjust its cell height again with this piece of code:
self.tableView.beginUpdates()
self.tableView.endUpdates()

UITableViewCell dynamic row height + custom UIView + Auto Layout

I am trying to get a custom photo container with multiple UIImageViews to fit in my tableview cell. The view contains a variable number of images (1 ~ 9), and its height would change correspondingly from 1x to 3x imageHeight.
I used AutoLayout to define the top/bottom/leading/trailing margins with the tableview and the custom UIView inside, and to enable self-sizing cells, I have set
tableView.estimatedRowHeight = X
tableView.rowHeight = UITableViewAutomaticDimension
I initialize these cells with
tableView.register(nib: forCellReuseIdentifier:)
and in tableView(_ tableView: cellForRowAt:) method, I setup the cell with:
let cell = tableView.dequeueReusableCell(
withIdentifier: "test9cell",
for: indexPath) as! SocialFeedTableViewCell
cell.photoContainer.setup(with: urls)
cell.photoContainer.loadImages()
return cell
where setup() hooks each imageView in the container with a URL
func setup(with urls: [URL]) {
self.imageUrls = urls
for i in 0 ..< urls.count {
let imageView = UIImageView(frame: CGRect.zero)
self.addSubview(imageView)
self.imageViews.append(imageView)
}
self.setNeedsLayout()
}
func loadImages() {
self.imageViews.forEach { imageView in
imageView.frame = // Calculate position for each subview
imageView.sd_setImage(...) // Load web image asynchronously
}
}
Defining intrinsicContentSize for the view:
override var intrinsicContentSize {
let frameWidth = self.frame.size.width
var frameHeight: CGFloat
switch self.imageUrls.count { // range from 1...9
case 1...3:
frameHeight = frameWidth / 3
case 4...6:
frameHeight = frameWidth / 3 * 2
default:
frameHeight = frameWidth
return CGSize(frameWidth, frameHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
self.imageViews.forEach { imageView in
imageView.frame = // Calculate position for each subview
}
}
The problem here is: after I set the initial intrinsicContentSize, the container's frame size changes in layoutSubviews() afterwards. Although by then I can position the imageView subviews correctly, the cell height will not be changed anymore.
Hope I am not making this problem more confusing. Could someone point out how would I resize the cell height AFTER modifying the contents of its UIView subview? Thanks!
There are a few things here that will be causing you some issues with dynamically sized cells.
You are adding multiple items to the cell but are not defining any auto layout constraints on the image views, so it does not know how to properly place / stack the items.
from the code above you are adding UIImageView with a frame of CGRectZero, without autulayout rules they will either stay zero or try to adjust to the contentsize when you add an image, but they wont adjust the cells height.
If you are loading images from the network they will likely be added/rendered after the tableviewcell has done its initial rendering. So you will likely need to cache the loaded images and reload the cell so that they can load in at the right time.
Now this last point is alot more complicated.
Dynamic UITableViewCell's calculate their height based on the autolayout rules of the content within them. You MUST have enough constraints from your content to the UITableViewCell's contentView property (to all edges) that give the cell enough information to place each item, calculate its overall height and width and therefore it is able to calculate the new height.
Using just one image isn't too bad for dynamic sized cells. but placing multiple items dynamically without any rules after the cell has initially rendered will not work.
You need to decide how these images should be laid out in your cell. once you have this you can look at adding the required constraints as you add the image views. Personally I would only add the image views once i have received each image.
Previously when I have done this I have cached the images once received and re-loaded the cell so that the image can be placed in the cell as it is rendered, allowing the tableview cell to calculate its height based off the image dimensions and constraints.
You may want to also consider using a collection view or merging the images together into a single image and using that in your cell. You could do this on device at runtime or server side if you have that kind of access to the images.

UITableVIew dynamic height with aspectratio contentview

Already read Auto-Layout: Get UIImageView height to calculate cell height correctly,the answer works well in ios 8 with UITableViewAutomaticDimension, however it is not suitable for lower ios version.
I have created a dynamic height UITableView with systemLayoutSizeFittingSize.
There is a square UIImageView in each cell, I want UIImageView keep square in both iphone 5 and iphone 6/plus. So I set constraints to the imageview as :
The image view's height is set aspect ratio 1:1 to its width, and both the constraints on leading and trailing keep the imageview's width is equal to the cell. The bottom space from the imageview's bottom to the content view's bottom is 29 pixel.
Moreover, I wrote the code in the tableview delegate like
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height+1;
}
However, the result is not as same as what I predicate, the imageview is very long :
the red color is the background of the image view.
So what's going on?

iOS Auto-Layout: Dynamic height for table view cell

I have a table view with a bunch of cells (custom cell, which only has its content view).
In tableView:cellForRowAtIndexPath: I'm adding a predefined UIView (which has several subviews) to the content view of the custom cell. I set up all constraints for the UIView and its subviews before.
Last but not least, I set the vertical and horizontal constraints for the content view of my custom cell (superview) and the UIView, which was added before (subview).
The constraint strings look like this:
H:|[view]|
V:|[view]|
Unfortunately, I still get the default height for all table view cells. I'm wondering If there's a way to let auto layout do the calculation of the height automatically according to content size.
Check out my detailed answer to this question here: https://stackoverflow.com/a/18746930/796419
It takes a bit of work to set up, but you can absolutely have Auto Layout constraints driving a completely dynamic table view without a single hardcoded height (and let the constraint solver do the heavy lifting and provide you with the row height).
Auto Layout won't help with the cell height. You'll need to set that in tableView:heightForRowAtIndexPath. I guess you're probably asking this because your cell heights are variable, not fixed. i.e., they depend on the content.
To resolve that, pre-calculate the cell heights and store them in an array. Return the value for the appropriate indexPath in the tableView:heightForRowAtIndexPath method.
Be sure to calculate content sizes on the main thread, using sizeThatFits of UILabel classes and such like.
If your calculation is intensive, do the majority of it off main apart from the view related methods such as sizeThatFits.
I solved the problem by using CGSize size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; in tableView:heightForRowAtIndexPath:.
To set automatic dimensions for row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

Resources