I have a UITableView with a header that I am shrinking as I scroll down, but it is creating a gap between the header and the cells.
Current code:
func moveLogoBarUp(_ scrollView: UIScrollView) {
self.tableView.bounces = true
let scrollDiff = self.tableView.contentOffset.y - self.previousScrollOffset
let newHeight = (self.tableView.tableHeaderView?.frame.height)! - abs(scrollDiff)
if newHeight <= self.tableHeaderCollapsedHeight {
self.tableView.tableHeaderView?.frame.size.height = self.tableHeaderCollapsedHeight
} else {
self.logoBarTopConstraint.constant -= abs(scrollDiff)
self.tableView.tableHeaderView?.frame.size.height = newHeight
self.tableView.contentOffset = CGPoint(x: self.tableView.contentOffset.x, y: self.previousScrollOffset)
self.previousScrollOffset = scrollView.contentOffset.y
//self.tableView.reloadData()
}
}
Note: If I use self.tableView.reloadData() it will fix the issue, but this doesn't seem ideal as it calls the method very rapidly as it scrolls.
How do I make the cells go up as the header shrinks?
The table view should recalculate the layout for the header if you set the property again. Something like this should do it:
self.tableView.tableHeaderView = self.tableView.tableHeaderView
I made a UICollectionView with a horizontal scroll.
I want to scroll only one direction i.e right to left my cell view size is as full view. once I scrolling cell, it should not scroll left to right.
Please Try this,
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let row = scrollView.contentOffset.x / cellWidth
currentIndexShown = Int(row)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x < cellWidth * CGFloat(currentIndexShown){
scrollView.contentOffset = CGPoint(x: cellWidth * CGFloat(currentIndexShown), y: -20)
scrollView.bounces = false
} else {
scrollView.bounces = true
}
}
I've subclassed a UICollectionViewFlowLayout to achieve a small scaling effect during horizontal scroll. Therefore I had to subclass a UICollectionViewCell as well as to change the layer.anchorPoint of the cell (my scaling is from the bottom left of the cell rather than from the default center). Now all fine and well except the fact that when I am scrolling horizontally , my cell is reused too soon (I still can see the half cell when it suddenly disappear ).
I have the feeling that collection view still bases its calculations for reusing the cell on the anchor point positioned in the center of the cell...
However , this is my collection view . You can see how the item getting bigger as it reaches the left side of the collection view. This is the scaling I wanted.
Now here I am scrolling to the left. You can see how the right item became bigger and the left is getting out of the screen.
And here you see that the left item didn't get off the screen but already dissapeared. Only the right item remeained visible :/
So what I want is , to make the left item disappear only when it's right boundaries reaching the very left of the screen.Simply saying , to dissapear only when I don't see it anymore.
And here is my code:
class SongsCollectionViewCell : UICollectionViewCell {
#IBOutlet weak var imgAlbumCover: UIImageView!
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
//we must change the anchor point for propper cells positioning and scaling
self.layer.anchorPoint.x = 0
self.layer.anchorPoint.y = 1
}
}
Here is the layout itself :
class SongsCollectionViewLayout : UICollectionViewFlowLayout {
override func prepareLayout() {
collectionView?.decelerationRate = UIScrollViewDecelerationRateFast
self.scrollDirection = UICollectionViewScrollDirection.Horizontal;
//size of the viewport
let size:CGSize = self.collectionView!.frame.size;
let itemWidth:CGFloat = size.width * 0.7//0.7//3.0;
self.itemSize = CGSizeMake(itemWidth, itemWidth);
self.sectionInset = UIEdgeInsetsMake(0,0,0,0);
self.minimumLineSpacing = 0
self.minimumInteritemSpacing = 0
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes:[UICollectionViewLayoutAttributes] = super.layoutAttributesForElementsInRect(rect)!
var visibleRect:CGRect = CGRect()
visibleRect.origin = self.collectionView!.contentOffset;
visibleRect.size = self.collectionView!.bounds.size;
for layoutAttributes in attributes {
if (CGRectIntersectsRect(layoutAttributes.frame, rect)) {
//we must align items to the bottom of the collection view on y axis
let frameHeight = self.collectionView!.bounds.size.height
layoutAttributes.center.y = frameHeight
layoutAttributes.center.x = layoutAttributes.center.x - self.itemSize.width/2
//find where ite, left x is
let itemLeftX:CGFloat = layoutAttributes.center.x
//distance of the item from the left of the viewport
let distanceFromTheLeft:CGFloat = itemLeftX - CGRectGetMinX(visibleRect)
let normalizedDistanceFromTheLeft = abs(distanceFromTheLeft) / self.collectionView!.frame.size.width
//item that is closer to the left is most visible one
layoutAttributes.alpha = 1 - normalizedDistanceFromTheLeft
layoutAttributes.zIndex = abs(Int(layoutAttributes.alpha)) * 10;
//scale items
let scale = min(layoutAttributes.alpha + 0.5, 1)
layoutAttributes.transform = CGAffineTransformMakeScale(scale, scale)
}
}
return attributes;
}
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// Snap cells to centre
var newOffset = CGPoint()
let layout = collectionView!.collectionViewLayout as! UICollectionViewFlowLayout
let width = layout.itemSize.width + layout.minimumLineSpacing
var offset = proposedContentOffset.x + collectionView!.contentInset.left
if velocity.x > 0 {
//ceil returns next biggest number
offset = width * ceil(offset / width)
} else if velocity.x == 0 { //6
//rounds the argument
offset = width * round(offset / width)
} else if velocity.x < 0 { //7
//removes decimal part of argument
offset = width * floor(offset / width)
}
newOffset.x = offset - collectionView!.contentInset.left
newOffset.y = proposedContentOffset.y //y will always be the same...
return newOffset
}
}
Answering my own question.
So as I suspected , the layout was taking an old center into account that is why I had to correct the center of the cell right after changing the anchor point . So my custom cell now looks like this :
class SongsCollectionViewCell : UICollectionViewCell {
#IBOutlet weak var imgAlbumCover: UIImageView!
override func prepareForReuse() {
imgAlbumCover.hnk_cancelSetImage()
imgAlbumCover.image = nil
}
override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) {
super.applyLayoutAttributes(layoutAttributes)
//we must change the anchor point for propper cells positioning and scaling
self.layer.anchorPoint.x = 0
self.layer.anchorPoint.y = 1
//we need to adjust a center now
self.center.x = self.center.x - layoutAttributes.size.width/2
}
}
Hope it helps someone
I have got TableView in the MainstoryBoard and the number of rows is random every time.
I want the height of the whole TableView to be flexible - by that I mean, for example: if I got 4 rows in the TableView and each TableView row height is 22 so the TableView height will be 88.
Another example:
number of rows: 2
row height = 22
TableView will be 44.
How can I make it?
You can change the UITableView height as per the contentSize as below:
Swift 2.2
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height)
Swift 3 or 4+
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
and make sure you write the above line in viewDidAppear method
You need to write the above line in viewDidLayoutSubviews also.
Swift 2.2
func viewDidLayoutSubviews(){
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height)
tableView.reloadData()
}
Swift 3 or 4+
func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
}
Simply take outlet for tableview height constraint and update it in willDisplay cell: UITableViewCell method of UITableView
#IBOutlet weak var tblHConstraint: NSLayoutConstraint!
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.tblHConstraint.constant = self.tblView.contentSize.height
}
nothing worked here is the solution i ended up using which gives accurate height.
extension UITableView {
var contentSizeHeight: CGFloat {
var height = CGFloat(0)
for section in 0..<numberOfSections {
height = height + rectForHeader(inSection: section).height
let rows = numberOfRows(inSection: section)
for row in 0..<rows {
height = height + rectForRow(at: IndexPath(row: row, section: section)).height
}
}
return height
}
}
Usage:
tableView.contentSizeHeight
will give you the actual calculated height of your table view content.
Take outlet of the Height Constraint of the parent view and assign it the height of table view's content + constant(extra height of other contents in the view)
heightConstraintView.constant = tableView.contentSize.height + constantValue //use the value of constant as required.
and write this code in the cellForRowAt method.
To use with with autolayout:
Somewhere in viewDidLoad()
tableView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor)
and then in your viewDidLayoutSubViews()
tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
DispatchQueue.main.async {
var frame = tableView.frame
frame.size.height = tableView.contentSize.height
tableView.frame = frame
}
OR
DispatchQueue.main.async {
self.TblViewHeightConstraint.constant = CGFloat((self.array.count) * 30)//Here 30 is my cell height
self.TblView.reloadData()
}
Please find the below code.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UIScreen.main.bounds.height
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let tableViewHeight = self.tableView.frame.height
let contentHeight = self.tableView.contentSize.height
let centeringInset = (tableViewHeight - contentHeight) / 2.0
let topInset = max(centeringInset, 0.0)
self.tableView.contentInset = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
}
Use this for Swift 3
override func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
}
Note: when your are increasing the tableview height it will goes out side the view. and you will get a problem with scrolling.
Ex: take a view inside that keep tableview and give constraints to left,right,bottom,top to Zero(0).
now reload the tableview assume 2 rows each row height is 20 now total rows height is 40. its fine and gave those height to table view, Table view will show only 40 height (you can assign tableview height by taking constraints (or) table view content size both are give same height according to rows height).
But if you reload 50 rows you will get scrolling problem because those rows height given to table view height, here table view height expands more than your screen height.
To solve this problem you should use scroll view outside the tableview.
Take outlet of tableview height and set it with your tableview row height in cellForRowAt like this,
tableviewHeight.constant = (tableView.contentSize.height * no_of_rows) + bottom_space
everyone, i have issue about uitableviewcell
custom uitableview cell which has subviews, one of which is custom image view named CustomIntrinsicImageView, and code as below:
class CustomIntrinsicImageView: UIImageView {
var _preferredMaxLayoutWidth: CGFloat!
var preferredMaxLayoutWidth: CGFloat! {
get {
return _preferredMaxLayoutWidth
}
set {
_preferredMaxLayoutWidth = newValue
invalidateIntrinsicContentSize()
}
}
override func intrinsicContentSize() -> CGSize {
if let size = image?.size {
let width: CGFloat
let height: CGFloat
if let maxLayoutWidth = preferredMaxLayoutWidth where size.width > maxLayoutWidth {
width = maxLayoutWidth
height = maxLayoutWidth * size.height/size.width
} else {
width = size.width
height = size.height
}
return CGSizeMake(width, height)
}
return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric)
}
}
the subviews have layout constraints to cell, and the image view update preferredMaxLayoutWidth in cell's layoutSubview body, code like this
override func layoutSubviews() {
super.layoutSubviews()
image🐶.preferredMaxLayoutWidth = CGRectGetWidth(contentView.bounds) - CGFloat(leftMarginImage + rightMarginImage)
}
the result of custom image view is perfect, but cell not recalculate height.
some of system UI like as UILable, UITextView has own instrinsicContentSize implement,which cooperate well with cell
so, the question is how to send information to cell, and let it know subview content size has change and recalculate height
thanks