UITableVIew dynamic height with aspectratio contentview - ios

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?

Related

UITableViewCell height to expand according to UIImageview's aspect ratio

I am trying to have an UIImageview inside a UITableViewCell. the image shouldn't grow any wider than the safe area of the device and the height should grow any more than 350. Problem: the image scales properly, but the table view doesn't shrink in height to snuggle up against the image when its shorter than 350. I seek your advice, please
Set the imageview width to 414 and made it a high priority, Set the imageview to .scaleaspectFit.
Set the image vertical hugging priority to <= 350 at low priority.
I'm hoping to find a solution or guidance towards a solution to where the image will grow no more than the set height, don't know if this is good practice..
Also for the cell to shrink and snuggle against the image as it shrinks in height while still maintaining a constant width based on the device screen/safe area
Thank you all in advance
Some more code
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let currentImage = images2[indexPath.row]
let imageCrop = currentImage.getCropRatio()
return tableView.frame.width / imageCrop
}
extension UIImage {
func getCropRatio() -> CGFloat {
var widthRatio = CGFloat(self.size.width / self.size.height)
return widthRatio
}
}

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.

Update tableview cell height after updating image height constraint

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

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.

Dynamic height TableView in a Scroll View

I am designing a page having a scroll view and above it a table view(scroll disabled). For doing this I have referred answers in this question - Make UITableView not scrollable and adjust height to accommodate all cells ,but wasn't successful.
Hierarchy of views along with provided constraints-
-Main View
-Scroll view
pinned to all sides of main view(0,0,0,0), constraint to margins
-Content View
pinned to scroll view(0,0,0,0),equal width to main view,equal height to main view(priority - 250)
-Table view inside content view
scroll disabled,having 50 point spaces from all sides,Height(>=),bottom spacing 50(relation >=).I have put greater than equal so as to increase height dynamically.
Now when I populate my table view I use the code as
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableview.dequeueReusableCellWithIdentifier("cellreuse", forIndexPath: indexPath)
cell.textLabel?.text = name[indexPath.row]
tableview.frame.size = tableview.contentSize
return cell
}
So when I run my code, it increases the tableview frame but doesn't stretch the content size and it just becomes weird as my scroll view doesn't scroll to the end of the table view neither my table view obeys the auto layout constraints.
Just I needed to do this -
remove the line - tableView.frame.size = tableView.contentSize
Add a height constraint for table view.
Set priority to High
Create an outlet of the height constraint(Ctrl+Drag).
Wherever you need to reload data of your table, set the height constraint to tableview's content height.
tableHeightConstraint.constant = tableview.contentSize.height
Assign a table height. Let it be constant 0.
Just add below lines.
tableView.heightConstant.constant = CGFloat.greatestFiniteMagnitude
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.heightConstant.constant = tableView.contentSize.height
With this, you can easily achieve dynamic table height. Working on iOS 13, Swift 5.
Had the same issue and resolved it by doing the following:
Create an outlet of the height constraint for the table view with a priority of 1000
#IBOutlet private weak var tableViewHeight: NSLayoutConstraint!
On viewDidLayoutSubview call layoutIfNeeded on the table view and then set the table view height constraint to the height of the content view
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.layoutIfNeeded()
tableViewHeight.constant = tableView.contentSize.height
}
Tested on iOS 14.1 and iOS 16.1

Resources