I have a scroll view and inside of if couple labels and a tableView. I would like for that tableView to be scrolled by a outer scrollView and not the tableView's scrollView, so what I did is to set constraint for tableView height to be equal to contentSize height. But I have this problem that it is sized correctly only when push animation is completed (and viewDidLayoutSubviews gets called, I guess)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableHeightConstraint?.constant = tableViewController.tableView.contentSize.height
}
Video Link
Content size will refresh with scroll view logic. So layout subviews is surely not enough. I have one case where I resize table view depending on it's content. What I do is use intrinsic size (it is that compression priority thing in storyboard). I subclass the table view and override these:
override var contentSize:CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
}
The rest may then be done with constraints. Im my case I do not resize it further then it's superview but you do what you must. Still a bit of caution here: If this is not restricted you destroy the table view dequeuing feature and all the cells may be loaded instantly which may consume loads of memory and CPU. I would avoid that if possible. And if not, the next best thing is using a vertical stack view on a scroll view which should produce the same result you seem to expect.
Related
I have an issue at the moment I am trying to put two table views inside a scrollview in one controller and these tableview are placed one below another. these two tableview uses scrollview for scrolling.
so I used vertical stackview inside scrollview. but when I create cell, both tableview height is not increases as well as scrollview is not able to scroll.
How should i use scrollview scroll for scrolling tableview?
-- scrollview
-----VerticalStackView
--------Tableview 1
--------Tableview 2
I'm really lost with this.Any help will be greatly appreciate it.
You need to make each UITableView define it's own size based on their content. To do that subclass both of them using the class below.
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
Then, for each UITableView you need to set isScrollEnabled = false. Otherwise their defined size will be 0.
Then just add each table view to the stack view you're using inside the scrollview. If their combined height is larger than the screen height, it'll scroll.
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.
I am using autolayout anchors to place my collectionView in my view. In my collectionView, I have a list of users. Since the number of cells is not definite, the height is changed based on the height of the content inside the collectionView. This is what have:
override func viewDidAppear(_ animated: Bool) {
let contentViewHeight = collectionView.getContentHeight() // Returns height of the content of the collectionView
collectionView.heightAnchor.constraint(equalToConstant: contentViewHeight).isActive = true
// I create a height constraint based on the contentHeight
self.collectionView.layoutIfNeeded()
}
This code works great, but the only side-effect is that my collectionView loads in a bit late when the view is shown. Everything else in the view is loaded, and the collectionView just pops up after a while. Is there any way I can add my constraint without causing this issue?
I tried moving my code to viewWillAppear and viewWillLayoutSubviews, but the anchor isn't even applied.
I have UITableView with UITableViewAutomaticDimension and some estimatedRowHeight. For this table I am using custom UITableViewCell which contains some label and custom UIView with overridden intrinsicContentSize(). Constraints setup is correct and table is able to determine actual height for each row. So far so good.
Now I started to modify internal logic of my custom view to adapt it's appearance based on available width i.e. when table cell size is not wide enough my view can rearrange subviews to fit new limitation and this have impact to resulting height, so I have code like that:
var internalSize: CGSize = ...
override func intrinsicContentSize() -> CGSize {
return internalSize
}
override func layoutSubviews() {
super.layoutSubviews()
fitIntoWidth(frame.size.width)
}
private func fitIntoWidth(width: CGFloat) {
let height = // calculate based on content and width
internalSize = CGSizeMake(width, height)
invalidateIntrinsicContentSize()
}
Now, when I populate table view, intrinsicContentSize() returns some desired value but it is not good fit for current layout, then control goes to layoutSubviews() where size get recalculated and system again calls intrinsicContentSize() and now it returns good value. However, first time table loads data and cell heights calculated based on incorrect intrinsicContentSize() values. If I call reloadData() again all becomes fine and layout is also ok for all upcoming cells in table.
Where is my mistake and how to modify code to make cell sizing work correctly without calling reloadData() twice?
A number of views in a UIStackView are adjusted to fit the stack. The views are initialised with no frame because they are resized by the stack view. Is there a way which I can get the size of the views after they have been resized by the stack view?
The sizes are available after UIStackView.layoutSubviews() finishes. You can subclass UIStackView and override layoutSubviews:
class MyStackView: UIStackView {
override func layoutSubviews() {
super.layoutSubviews()
print("arrangedSubviews now have correct frames")
// Post a notification...
// Call a method on an outlet...
// etc.
}
}
Yes. In your View's layoutSubviews().
However you need to force the UIStackView to layout first, using stack.layoutIfNeeded() before using its size.
eg:
public override func layoutSubviews() {
super.layoutSubviews()
// Force the UIStackView to layout, in order to get the updated width.
stack.layoutIfNeeded()
let tabWidth = stack.arrangedSubviews[0].frame.size.width
}
I think the better way to get a Stackview's subview frame is to use convert.
yourStackView.convert(viewInsideStackView.frame to: parentView)
This will return viewInsideStackView's frame inside parentView's coordinate system.