Non-floating UITableView footer - ios

I have a UITableView with a bunch of custom cells (and each cell has a custom height and dynamic height).
I want to add a footer view to the tableview that will always be on the bottom of the tableView contentSize.
If the contentSize is bigger than the screen size so in the bottom of the tableView (you scroll down, like a regular last cell).
If the contentSize is smaller than the screen size so the footer will still be in the bottom of the screen and there will be a gap from the last cell to the footer.
I've tried to add a footer like this:
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let myFooterView = Bundle.main.loadNibNamed("MyFooterView", owner: self, options: nil)?.first as! MyFooterView
myFooterView.delegate = self
myFooterView.frame = CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: 100)
return myFooterView
}
But the footer is floating in on the bottom when I scroll. Tha'ts not what I want to achieve.
Any idea how can I achieve a non-floating footer like I want?
Thanks! :)

Add a view as a subview to the table view and adjust the position in scrollView:didScroll:. To be able to do this, add conformance to UIScrollViewDelegate to your view controller. I think a UITabelViewController is automatically the delegate for the table view (which is also a UIScrollView).
The tricky part is the calculation of the position based on the content size, the content offset, the size of the view and the size of your footer.

Related

How to pass tap gesture through point on screen that is not in full-screen UITableView's UIEdgeInsets:

I have created a screen in which I have a full-screen UITableView, I set UIEdgeInsets to it, which I have configured as follows:
categoriesTableView.contentInset = UIEdgeInsets.init(top: HEADER_VIEW_HEIGHT, left: 0, bottom: 0, right: 0)
where HEADER_VIEW_HEIGHT is CGFloat = 160.
This allowed me to add a "header view" to the UITableView which is getting covered when I start scrolling the UITableView (...and not getting stuck above the UITableView, as a real header view will).
The Problem: The problem is that I need to have 3 clickable views in the header view, So I designed 3 views in the storyboard and configured Tap Gesture Recognizers on them. But when I try to use them the tap gestures are not passed throw the UITableView even though I see those views on screen (as a result of the contentInset configuration). The only way I can make them tapable/clickable is if I set User Interaction Enabled to false on the UITableView (which I can't do because I need the UITableView to be draggable and clickable as well).
The Question: How would I pass the tap events to the lower "header view" clickable parts when it's not covered by the UITableView as a result of the contentInset setting?
Here is the UI image, in it you can see that there is a full-screen UITableView, behind it there a view with 3 subviews that contains 3 favorite items which I can present to the user for an easier access. when the screen starts, there is a contentInset for the UITableView hence the user can see those easy access options and press them (which he can't do right now). When the user starts scrolling, the UITableView goes on top of the layout with the 3 views and the user able to scroll the list in a full screen. kind of like a parallax effect.
I have a very ugly solution for this..
Now you have a table view and below that there is a header view right? Add one more view on top of table view which contains 3 sub views in it and all transparent in colour.
Position this newly added subview same as that of header view (Either by programmatically giving same frame as that of header view or by connecting its top, left, bottom and right constraints to the header view). Similarly position the 3 sub views of this new view as same as that of the subviews inside the header view. And give tap gesture to the subviews of this new view. So our user will think he is tapping the header view, whereas he is actually tapping in this invisible view.
And if you want to get touch in the table view once it scrolls up and cover the header view, then you can use one of these two..
Call the UIScrollViewDelegate delegate method scrollViewDidScroll() and inside if scrollView.contentOffset.y >= 160, set the User Interaction Enabled to false for this newly added view and revert that back to true if scrollView.contentOffset.y < 160.
Or from inside scrollViewDidScroll(), assign outletOfTopConstraintOfYourNewView.constant = -(scrollView.contentOffset.y), so that the new view will also move up according to the scroll, thereby changing its visible tappable area.
Another not so elegant idea is..
Instead of giving contentInset, add one more section in this table view at the 0th index with only one cell whose height is 160, user interaction enabled is false and colour is transparent. Then you don't have to worry about the logic in scrollViewDidScroll().
Update:
Below solution is not perfect but it may give you a direction to start with.
A) Put the top constraint of tableView top to bottom constraint of headerview.
B) Create IBOutlet of height constraint of headerview in your ViewController
C) Listen to tableview's ScrollViewDidScroll and put code like this
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = scrollView.contentOffset.y
if y > 50{
if heightConstant.constant != 0{
view.layoutIfNeeded()
heightConstant.constant = 0
UIView.animate(withDuration: 0.5, delay: 0, options: .allowUserInteraction, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}else{
view.layoutIfNeeded()
heightConstant.constant = 160
UIView.animate(withDuration: 0.5, delay: 0, options: .allowUserInteraction, animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
Here 50 is a point form where it should start animating. It is somewhat similar to Collapsable Toolbar in Android. Just make sure headerView.isClipsToBounds = true.
Convert your headerview into a UITableViewCell and in change your UITableViewDataSource as
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 1 : yourList.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
// set your header view here
}else{
// Your normal cell configuration
}
}
}
With this, your headerview will become a part of table view but as a UITableViewCell thus it will not behave like sticky header like the normal one.

How to make tableView cell "stick" while scrolling

I have a SegmentedControl on a Cell at the top of my tableView (the tableView consists of all static cells)...I want the top cell (and segmentedControl) to "stick" so that it is always visible as a user scrolls through the table (see screenshot below). Is this possible?
Usually, for applying this behavior you should add it as a header view instead of a cell. Header views in table view does "stick". But since you are adding static cells (UITableViewController), even if you tried to add a header view to the table view it won't apply the sticking behavior.
As a workaround you might need to add a new UIViewController contains a view on the top (the header view) and a container view, as follows:
The one hackish solution which pops to my mind is:
Declare header in your VC, or hold the reference:
private var header: UIView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 40))
Add it as a subview of tableView and set content inset equal to height of the header.
tableView.addSubview(header)
tableView.contentInset.top = header.frame.height
If your header use autolayout you will need to call
view.layoutIfNeeded() before setting content inset
and then for header to stick always to the top of tableView:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
header.frame.origin.y = scrollView.contentOffset.y
}

Hiding a view above UITableView causes the header of the tableview to be stretched

I have a UIView with a UITableView right below it. The tableview has a custom header which hold a UISearchbar and a UISegmententedController. The top view and tableview are constrained to each other and to the superview. If I hide the top view, the header of the tableview gets stretched.
Top view visible:
Top view hidden (height constraint set to 0):
My expectation would be that when I hide the top view my tableview content should scroll up, but instead of repositioning the cells, the tableview stretches out the header view.
Thanks for any help!
I'm not sure why but if the custom tableView header is created from a xib then it gets messed up. But if the view is created programmatically it works. I don't know why but to fix the problem just needed to create the tableview header programmatically
Bad:
if let headerView = Bundle.main.loadNibNamed("HeaderView", owner: self, options: nil)?[0] as? HeaderView {
tableview.tableHeaderView = headerView
}
Good:
tableview.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 80))
If you want to make the tableView scroll up, I would recommend getting the height constraint of the top view and reducing it to 0. You can update the height constraint in a UIView.animate block to have a smoother transition when showing and hiding the top view.
Refer here for more details: AutoLayout with hidden UIViews?

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

Can't center views in tableHeaderView with auto layout and storyboards

I can't center a view that has been placed as a subview of a UITableView's tableHeaderView in a storyboard using auto layout.
In a storyboard, I have a UITableView. I dragged a UIView (the red view) to the top, released, and it created a table header view automatically. I then dragged and dropped another UIView (the yellow view) on top of the table header view, resized, and applied some constraints to ensure it stays centered:
When I run the app on the simulator, here's what I get:
The yellow view is obviously not centered. However, the "Filter" button at the bottom is.
I know it's tricky to get the height right using auto layout and storyboards and table header views (and you can see that the height of the red view is definitely incorrect), but at this point, I'm just trying to solve for horizontally centering my yellow view.
Am I able to set this all up in my storyboard without having to configure my constraints in code?
Make sure that your UITableView has the leading, trailing, bottom, top constraints set up against its superview.
Check the table header view and all sub views have Autoresize Subviews enabled:
You can also force the table to re-render the header view by re-setting it to the same view:
[self.tableView setTableHeaderView:self.outletToHeaderView];
Update: to resize the table header view, give give it an appropriate frame in viewWillAppear:
CGRect newFrame = self.outletToHeaderView.frame;
newFrame.size.width = self.tableView.bounds.size.width;
newFrame.size.height = 44;
[self.outletToHeaderView setFrame:newFrame];
// Then reset it to force the table view to re-render/accommodate
[self.tableView setTableHeaderView:self.outletToHeaderView]
In your file TableViewHeader:
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
yourView.translatesAutoresizingMaskIntoConstraints = false
yourView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
}
You need to use prototype cell from table view, than dequeue it with reusable identifier and return it contentView. Only that's do the trick
var headerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.headerView = (self.tableView.dequeueReusableCellWithIdentifier("CustomHeaderCell") as! UITableViewCell).contentView
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return self.headerView
}
upd:
Oh sorry, my example is written in Swift. But it's easy to understand how to do the same in Obj-C

Resources