How to update UITableview Header height while scrolling programatically - swift

In My application I have top navigation bar and a tableview below the navigation bar. I have CollectionViewCell with two rows which added inside the UITableViewHeader programmatically. When ever I scroll the the TableView to top, i want the header to stop just below the navigation bar, and update the TableView Header height so I can show only one row. I just want to do an animation (like Shrinked)when the TableViewHeader sticks to the navigationbar the two collectionview rows should turn into one row by decreasing the Header Height. How can I do it programmatically
Below is my code for showing CustomHeaderView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 183))
let headerCell = tableView.dequeueReusableCell(withIdentifier: kLastPlayedidentifier) as! LastPlayedTVC
headerCell.frame = headerView.frame
headerCell.category = lastPlayedData
headerView.addSubview(headerCell)
return headerView
}
Also i'm checking for the scroll position to set the tableview header height progmmatically which isn't successful for me.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset)
if scrollView.contentOffset.y > 237 //This value is to check when the header reached the top position {
//Condition to check and animate the headerview height to make collectionview cell two rows into one rows.
}
How can I achieve the TableViewHeader height update when header sticks on top while scrolling.
Any help is appreciated.

What you are looking for is "sticky header"
and you want to change the header as well.
Sticky part is built in automatically I think if you just use UITableViewController(style: .plain), if that doesn't work for you, you can just google sticky header and there are lots of answers.
the part about changing the height or animating it. you are doing it right, just do something like:
// update your viewForHeader method to account for headerRows variable above
// update your viewForHeader method to account for headerRows variable above
// default 2, you modify this in your scroll
var headerRows = 2
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let height = headerRows == 2 ? 183 : 91
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: height))
let headerCell = tableView.dequeueReusableCell(withIdentifier: kLastPlayedidentifier) as! LastPlayedTVC
headerCell.frame = headerView.frame
headerCell.category = lastPlayedData
headerView.addSubview(headerCell)
return headerView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset)
if scrollView.contentOffset.y > 237 {
updatedHeader.frame.size.height = 40
self.tableviewObj.tableHeaderView = updatedHeader
headerRows = 1 } else {
headerRows = 2
}
self.tableView.reloadSectionHeaders()
}
If you want to do some animating instead, what you would do is store reference to your headerView in a variable of your view controller and inside your scrollViewDidScroll animate it using UIView.animate{...}
hope this helps man.

Related

UILabels and UICollectionView inside of UITableViewCell - need dynamic height

I have a custom UITableViewCell class that contains various UILabels, UIButtons, and a UITextField in the top and mid-sections, and then a UICollectionView at the bottom.
As a user enters more search terms into the UICollectionView, the collection view grows vertically, but I can't get my TableViewCell to adjust its height dynamically.
I have added constraints for every item in the TableViewCell so AutoLayout should work. I even set the heightForRowAt function to return UITableView.automaticDimension. But I'm apparently still missing something.
When the user adds/subtracts items to the collection view, how can I have the TableViewCell update its height dynamically?
I also tried the following block, but it doesn't work either.
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
collectionView.layoutIfNeeded()
collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height + titleLabel.frame.height + filterButton.frame.height + collectionView.frame.height)
return collectionView.frame.size
}

Top to Bottom Collection View Flow Layout with first cell starting at bottom

I am trying to mimic Instagram live comments section where the first cell in the UICollectionView appears at the bottom but the flow is still top to bottom. I.e. everything about the flow layout is the exact same except that the first cell is at position (x: 0, y: heightOfCollectionView)...
Below is my naive implementation. I also thought about having the collection view increment in height per item in the UICollectionView until themaxY >= centerY of the superview.
import UIKit
class CommentsLayout: UICollectionViewFlowLayout {
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let layoutAttribute = super.layoutAttributesForItem(at: indexPath)?.copy() as! UICollectionViewLayoutAttributes
if indexPath.section == 1 {
print(collectionView?.frame.size.height)
layoutAttribute.frame = CGRect(x: 0, y: 600, width: collectionViewContentSize.width, height: 40)
}
return layoutAttribute
}
}

CollectionView: HeaderView Bounce

pretty new to CollectionView so hope to have a direction here.
I have a CollectionView with an header enabled. All works fine but I want the HeaderView stay still while scrolling the main section.
Question:
Is that possible in iOS given a collectionview? Or I have to create a dedicated still view for the header?
I set the below in the UICollectionViewFlowLayout
if let layout: UICollectionViewFlowLayout = self.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .vertical
layout.sectionHeadersPinToVisibleBounds = true
}
which makes the header behave as below:
When scrolling down the main collection, the header goes up until it's lower margin reaches the top of the screen, then bunches in the middle of the sceen
When going up the header follows the main collection and become hided
I am pretty sure I am not the only one which has had this issue. Maybe I am doing something wrong here?
layout.sectionHeadersPinToVisibleBounds = true
that makes your header:
stick to the cells, when you scroll down
begin to hide after bottom of cells reaches bottom of the header
I see that you want the header to be still at all times. Of course, as you said, you could add a UIView on top of the collectionView, which would do the job. A different solution may be this:
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath)
headerContainer.backgroundColor = .yellow
headerContainer.frame = header.frame
header.layer.masksToBounds = false // that's important
header.addSubview(self.headerContainer)
return header
}
So basically, we're adding a subview inside our header (header needs to be transparent then). Now, we're using the fact that UICollectionView is scrollable:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y
if offset < 0 {
headerContainer.transform = CGAffineTransform(translationX: 0, y: offset)
}
}
So if we are scrolling down by offset value, we transform the container by adding offset value to its Y position.
Unfortunately, I haven't come up with the case of scrolling up (it still begins to hide after some time) - I guess it's doable.
I based on this topic:
TableView header bouncing
I think I'd rather make a UIView outside of UICollectionView anyway.

Swift: Dynamic Cell in table view

I'm trying to make the height of table view cell = the content of the cell.
I implemented these 2 lines in the viewDidLoad():
tableView.estimatedRowHeight = 200.0
tableView.rowHeight = UITableViewAutomaticDimension
Still, the cell is changing its height!
TextView will not expand to fit the entire text by default because it has scrolling capabilities, for what you want you should disable scrolling in the textView.
Select the textView and in the Attributes Inspector tab scroll down and uncheck the "Scrolling Enabled"
It appears to me that your issue may be that the height of the UITextView is not explicitly stated. The natural behaviour of the text view is not to be a tall as it's content.
I would suggest adding a height constraint within interface builder, hooking it up to an outlet, and then within the cell layoutSubviews function calculating the height like so:
#IBOutlet var textViewHeightConstraint: NSLayoutConstraint!
func layoutSubviews() {
super.layoutSubviews()
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
textViewHeightConstraint.constant = newSize.height
}
try to use heightForRowAt indexpath
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {return UITableViewAutomaticDimension}
+
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
In ViewDidLoad
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
And put your both Title Label and Text Label (replace Text View) in a UIView. and give constraints to UIView
1. trailing, leading, bottom and top space as ZERO
2. give fixed hight as 200 and change relation as Greater than or equal (>=)
Then give constrains to Title label
1. trailing, leading and top space as ZERO
2. give fixed hight as 20 (your choice)
Give constrains to Text Label
1. trailing, leading, bottom and top space as ZERO
2. give fixed hight as 180 and change relation as Greater than or equal (>=)

TableView reloadData resets scrollView contentOffset in header cell

I have a tableView with two custom cells. One for the header and one for the cells inside the tableView.
The Header cell looks like this:
Label Label ScrollView(inside scrollView is an imageView)
The other cell looks like this:
Label TextField ScrollView(inside scrollView is an imageView)
When I scroll one cell horizontally all other cells (including header cell) will get the same contentOffset. This works like a charm.
I added another function which adds another imageView inside the scrollView on top of the existing imageView for all cells except the header cel. Inside the new imageView I add a line which is draggable. This is realized by a longPressureGesture. Inside this gesture I need to do a tableView.reloadData() so that each time the line moves it will be updated for all cells. LongPressureGesture allows me to do this without loosing the control of the line while doing a reloadData().
But here is my problem:
The contentOffset of the scrollView inside the headerCell is reseted after calling reloadData() inside the longPressureGesture. But the contentOffset for all other scrollViews in the cells are still the same.
I tried to add the contentOffset in the headerCell so that each time the reloadData is called the contentOffset will be set. But this is not working because the contentOffset will be called to early and has no effect.
If I add a delay and then set the contentOffset again it is working. But this ends up in flickering which is not good.
Edit: Tried to use only one cell (used cell for header instead of specific header cell). Ended up with the same result. So this is an general issue for headers in tableView?
My code for the cells is:
/*
* This method fills all the given information for each signal to a custom cell by type SignalCell.
* Each cell has a signalName, a value at specific time and a wave image.
*/
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! SignalCell
cell.delegate = self
// Get the signal for this row
let signal = VCDHelper.sharedInstance.signals[indexPath.row] as! VCDSignal
// Set the name of the signal to the label
cell.signalLabel.text = signal.name
cell.name = signal.name
// cellContainer is set inside renderWave every time a new Cell appears
renderTimePicker(cell, signal: signal, indexPath: indexPath)
renderWave(cell, signal: signal, indexPath: indexPath)
cellContainer.setObject(cell, forKey: signal.name)
return cell
}
Code for header:
// Display header cell
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCellWithIdentifier(cellHeaderIdentifier) as! HeaderCell
// Add object to renderer dictionary
var headerRenderer = headerToRender.objectForKey("header") as? TimeRenderer
// Render waves
if let headerRenderer = headerRenderer {
cell.timeImageView.image = headerRenderer.renderedTime()
} else {
// There is no renderer yet
headerRenderer = TimeRenderer()
headerToRender.setObject(headerRenderer!, forKey: "header")
// Creates and returns an NSBlockOperation object with block
let operation = NSBlockOperation(block: { () -> Void in
let renderedHeaderImage = headerRenderer!.renderedTime()
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
cell.timeImageView.image = renderedHeaderImage
cell.cellContainer = self.cellContainer
})
})
queue.addOperation(operation)
headerToRenderOperations.setObject(operation, forKey: "header")
}
cellContainer.setObject(cell, forKey: "header")
return cell
}
And here the code for saving/setting the contentOffset for each Cell realized in SignalCell class:
/*
* This method allows only horizontal scrolling.
* It also handles that scrolling out of bounds is not allowed
*/
func scrollViewDidScroll(scrollView: UIScrollView) {
// Set contentOffsetX to minimum bound value because we are out of range
if scrollView.contentOffset.x < 0 {
scrollCells(scrollView, contentOffsetX: 0.0)
VCDHelper.sharedInstance.waveScrollContentOffsetX = 0.0
}
if scrollView.contentOffset.x > 0 {
// ContentOffsetX is the point at the beginning of the scrollView
// We know the total size of the image and need to subtract the size of the scrollView
// This will result in the max contentOffset.x for scrolling
if scrollView.contentOffset.x <= CGFloat(VCDHelper.sharedInstance.imageWidth) - scrollView.bounds.width {
scrollCells(scrollView, contentOffsetX: scrollView.contentOffset.x)
VCDHelper.sharedInstance.waveScrollContentOffsetX = Float(scrollView.contentOffset.x)
} else {
// Set contentOffsetX to maximal bound value because we are out of range
scrollCells(scrollView, contentOffsetX: CGFloat(VCDHelper.sharedInstance.imageWidth) - scrollView.bounds.width)
}
}
}
/*
* This method scrolls all visible images inside the scrollView at once
*/
func scrollCells(scrollView: UIScrollView, contentOffsetX: CGFloat) {
for (key, cell) in self.cellContainer {
if key as! String == "header" {
let headerCell = cell as! HeaderCell
let scrollContentOffsetY = headerCell.timeScrollView.contentOffset.y
// Dont use setContentOffset because this will call scrollViewDidScroll each time
headerCell.timeScrollView.contentOffset = CGPoint(x: contentOffsetX, y: scrollContentOffsetY)
} else {
let signalCell = cell as! SignalCell
let scrollContentOffsetY = signalCell.signalScrollView.contentOffset.y
// Dont use setContentOffset because this will call scrollViewDidScroll each time
signalCell.signalScrollView.contentOffset = CGPoint(x: contentOffsetX, y: scrollContentOffsetY)
}
}
}