Is there any way to add spacing between UITableViewCell?
I have created a table and each cell only contain an image. The image is assigned to the cell like this:
cell.imageView.image = [myImages objectAtIndex:indexPath.row];
but this make the image enlarged and fit into the whole cell, and there are no spacing between the images.
Or lets say in this way, the height of image are e.g. 50, and I want to add 20 spacing between the images. Is there any way to accomplish this?
My easy solution using Swift :
// Inside UITableViewCell subclass
override func layoutSubviews() {
super.layoutSubviews()
contentView.frame = contentView.frame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
}
Result
Swift Version
Updated for Swift 3
This answer is somewhat more general than the original question for the sake of future viewers. It is a supplemental example to the basic UITableView example for Swift.
Overview
The basic idea is to create a new section (rather than a new row) for each array item. The sections can then be spaced using the section header height.
How to do it
Set up your project as described in UITableView example for Swift. (That is, add a UITableView and hook up the tableView outlet to the View Controller).
In the Interface Builder, change the main view background color to light blue and the UITableView background color to clear.
Replace the ViewController.swift code with the following.
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
let cellReuseIdentifier = "cell"
let cellSpacingHeight: CGFloat = 5
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// These tasks can also be done in IB if you prefer.
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
tableView.delegate = self
tableView.dataSource = self
}
// MARK: - Table View delegate methods
func numberOfSections(in tableView: UITableView) -> Int {
return self.animals.count
}
// There is just one row in every section
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
// Set the spacing between sections
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return cellSpacingHeight
}
// Make the background color show through
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clear
return headerView
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
// note that indexPath.section is used rather than indexPath.row
cell.textLabel?.text = self.animals[indexPath.section]
// add border and color
cell.backgroundColor = UIColor.white
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8
cell.clipsToBounds = true
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// note that indexPath.section is used rather than indexPath.row
print("You tapped cell number \(indexPath.section).")
}
}
Note that indexPath.section is used rather than indexPath.row in order to get the proper values for the array elements and tap positions.
How did you get the extra padding/space on the right and left?
I got it the same way you add spacing to any view. I used auto layout constraints. Just use the pin tool in the Interface Builder to add spacing for the leading and trailing constraints.
The way I achieve adding spacing between cells is to make numberOfSections = "Your array count" and make each section contains only one row. And then define headerView and its height.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return yourArry.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return cellSpacingHeight;
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *v = [UIView new];
[v setBackgroundColor:[UIColor clearColor]];
return v;
}
I needed to do the same concept of having UITableCells have a "space" between them. Since you can't literally add space between cells you can fake it by manipulating the UITableView's cell height and then adding a UIView to the contentView of your cell. Here is a screen shot of a prototype I did in another test project when I was simulating this:
Here is some code (Note: there are lots of hard coded values for demonstration purposes)
First, I needed to set the heightForRowAtIndexPath to allow for different heights on the UITableViewCell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [self.newsArray objectAtIndex:[indexPath row]];
if ([text isEqual:#"December 2012"])
{
return 25.0;
}
return 80.0;
}
Next, I want to manipulate the look and feel of the UITableViewCells so I do that in the willDisplayCell:(NewsUITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath method.
- (void)tableView:(UITableView *)tableView willDisplayCell:(NewsUITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (cell.IsMonth)
{
UIImageView *av = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, 20, 20)];
av.backgroundColor = [UIColor clearColor];
av.opaque = NO;
av.image = [UIImage imageNamed:#"month-bar-bkgd.png"];
UILabel *monthTextLabel = [[UILabel alloc] init];
CGFloat font = 11.0f;
monthTextLabel.font = [BVFont HelveticaNeue:&font];
cell.backgroundView = av;
cell.textLabel.font = [BVFont HelveticaNeue:&font];
cell.textLabel.textColor = [BVFont WebGrey];
}
if (indexPath.row != 0)
{
cell.contentView.backgroundColor = [UIColor clearColor];
UIView *whiteRoundedCornerView = [[UIView alloc] initWithFrame:CGRectMake(10,10,300,70)];
whiteRoundedCornerView.backgroundColor = [UIColor whiteColor];
whiteRoundedCornerView.layer.masksToBounds = NO;
whiteRoundedCornerView.layer.cornerRadius = 3.0;
whiteRoundedCornerView.layer.shadowOffset = CGSizeMake(-1, 1);
whiteRoundedCornerView.layer.shadowOpacity = 0.5;
[cell.contentView addSubview:whiteRoundedCornerView];
[cell.contentView sendSubviewToBack:whiteRoundedCornerView];
}
}
Note that I made my whiteRoundedCornerView height 70.0 and that's what causes the simulated space because the cell's height is actually 80.0 but my contentView is 70.0 which gives it the appearance.
There might be other ways of accomplishing this even better but it's just how I found how to do it. I hope it can help someone else.
I was in the same boat. At first I tried switching to sections, but in my case it ended up being more of a headache than I originally thought, so I've been looking for an alternative. To keep using rows (and not mess with how you access your model data), here's what worked for me just by using a mask:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
let verticalPadding: CGFloat = 8
let maskLayer = CALayer()
maskLayer.cornerRadius = 10 //if you want round edges
maskLayer.backgroundColor = UIColor.black.cgColor
maskLayer.frame = CGRect(x: cell.bounds.origin.x, y: cell.bounds.origin.y, width: cell.bounds.width, height: cell.bounds.height).insetBy(dx: 0, dy: verticalPadding/2)
cell.layer.mask = maskLayer
}
All you have left to do is make the cell's height bigger by the same value as your desired verticalPadding, and then modify your inner layout so that any views that had spacing to the edges of the cell have that same spacing increased by verticalPadding/2. Minor downside: you get verticalPadding/2 padding on both the top and bottom of the tableView, but you can quickly fix this by setting tableView.contentInset.bottom = -verticalPadding/2 and tableView.contentInset.top = -verticalPadding/2. Hope this helps somebody!
You will have to set frame to your image. Untested code is
cell.imageView.frame = CGRectOffset(cell.frame, 10, 10);
I override this function is subclass of UITableViewCell, and it works OK for me
override func layoutSubviews() {
super.layoutSubviews()
//set the values for top,left,bottom,right margins
let margins = UIEdgeInsets(top: 5, left: 8, bottom: 5, right: 8)
contentView.frame = contentView.frame.inset(by: margins)
contentView.layer.cornerRadius = 8
}
I think the most straight forward solution if your just looking for a little space and probably least expensive would be to simply set the cell border color to your tables background color then set the border width to get desired result!
cell.layer.borderColor = blueColor.CGColor
cell.layer.borderWidth = 3
Use sections instead of rows
Each section should return one row
Assign your cell data using indexPath.section, instead of row
Implement UITableView delegate method heightForHeader and return your desired spacing
I solved it like this way in Swift 4.
I create a extension of UITableViewCell and include this code:
override open var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 10
frame.origin.x += 10
frame.size.height -= 15
frame.size.width -= 2 * 10
super.frame = frame
}
}
override open func awakeFromNib() {
super.awakeFromNib()
layer.cornerRadius = 15
layer.masksToBounds = false
}
I hope it helps you.
Change the number of rows in section to 1
You have changed number of sections instead number of rows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
Here you put spacing between rows
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
If you are not using section headers (or footers) already, you can use them to add arbitrary spacing to table cells. Instead of having one section with n rows, create a table with n sections with one row each.
Implement the tableView:heightForHeaderInSection: method to control the spacing.
You may also want to implement tableView:viewForHeaderInSection: to control what the spacing looks like.
Example in swift 3..
Crease a single view application
add tableview in view controller
add a customcell for tablview cell
view controller code is bellow like
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var arraytable = [[String:Any]]()
override func viewDidLoad() {
super.viewDidLoad()
arraytable = [
["title":"About Us","detail":"RA-InfoTech Ltd -A Joint Venture IT Company formed by Bank Asia Ltd"],
["title":"Contact","detail":"Bengal Center (4th & 6th Floor), 28, Topkhana Road, Dhaka - 1000, Bangladesh"]
]
tableView.delegate = self
tableView.dataSource = self
//For Auto Resize Table View Cell;
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
//Detault Background clear
tableView.backgroundColor = UIColor.clear
}
func numberOfSections(in tableView: UITableView) -> Int {
return arraytable.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
// Set the spacing between sections
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 10
}
// Make the background color show through
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clear
return headerView
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! CustomCell
cell.tv_title.text = arraytable[indexPath.section]["title"] as! String?
cell.tv_details.text = arraytable[indexPath.section]["detail"] as! String?
//label height dynamically increase
cell.tv_details.numberOfLines = 0
//For bottom border to tv_title;
let frame = cell.tv_title.frame
let bottomLayer = CALayer()
bottomLayer.frame = CGRect(x: 0, y: frame.height - 1, width: frame.width, height: 1)
bottomLayer.backgroundColor = UIColor.black.cgColor
cell.tv_title.layer.addSublayer(bottomLayer)
//borderColor,borderWidth, cornerRadius
cell.backgroundColor = UIColor.lightGray
cell.layer.borderColor = UIColor.red.cgColor
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8
cell.clipsToBounds = true
return cell
}
}
Download full source to Github : link
https://github.com/enamul95/CustomSectionTable
Three approaches I can think of:
Create a custom table cell that lays out the view of the entire cell in the manner that you desire
Instead of adding the image to the
image view, clear the subviews of
the image view, create a custom
view that adds an UIImageView for the image and another view, perhaps a simple UIView that provides the desired spacing, and add it as a subview of the
image view.
I want to suggest that you manipulate the UIImageView directly to set a fixed size/padding, but I'm nowhere near Xcode so I can't confirm whether/how this would work.
Does that make sense?
Yes you can increase or decrease the spacing(padding) between two cell by creating one base view on content view in cell.Set clear colour for content view background and you can adjust the height of the base view to create space between cells.
Based on Husam's answer: Using the cell layer instead of content view allows for adding a border around the entire cell and the accessory if need. This method requires careful adjustment of the bottom constraints of the cell as well as those insets otherwise the view will not proper.
#implementation TableViewCell
- (void)awakeFromNib {
...
}
- (void) layoutSubviews {
[super layoutSubviews];
CGRect newFrame = UIEdgeInsetsInsetRect(self.layer.frame, UIEdgeInsetsMake(4, 0, 4, 0));
self.layer.frame = newFrame;
}
#end
Read this after reading other people answers
I'd like to warn everyone who wants to use the solution like adding headers that will serve the purpose of spacing. If you do this, you will not be able to animate cells insertions, deletions, etc.. For example, you may get this kind of error if you use that method
Invalid update: invalid number of sections. The number of sections contained in the table view after the update (6) must be equal to the number of sections contained in the table view before the update (5), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).
In case you need to animate insertions and deletions of rows I would go with adding this space in the cells itself. If you are concern about highlighting, then you can override method
func setHighlighted(_ highlighted: Bool, animated: Bool)
and set the highlighting yourself
I think this is the cleanest solution:
class MyTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
layoutMargins = UIEdgeInsetsMake(8, 0, 8, 0)
}
}
This article helped, it's pretty much what the other answers said but summarize and concise
https://medium.com/#andersongusmao/left-and-right-margins-on-uitableviewcell-595f0ba5f5e6
In it, he only applies them to left and right sides but the UIEdgeInsetsMake init allows to add padding to all four points.
func UIEdgeInsetsMake(_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat) -> UIEdgeInsets
Description
Creates an edge inset for a button or view.
An inset is a margin around a rectangle. Positive values represent margins closer to the center of the rectangle, while negative values represent margins further from the center.
Parameters
top: The inset at the top of an object.
left: The inset on the left of an object
bottom: The inset on the bottom of an object.
right: The inset on the right of an object.
Returns
An inset for a button or view
Note that UIEdgeInsets can also be used to achieve the same.
Xcode 9.3/Swift 4
Using the headers as spacing would work fine I guess if you don't want to use any headers. Otherwise, probably not the best idea. What I'm thinking is create a custom cell view.
Examples:
Using Nib
In code
In the custom cell, make a background view with constraints so that it doesn't fill the entire cell, give it some padding.
Then, make the tableview background invisible and remove the separators:
// Make the background invisible
tableView.backgroundView = UIView()
tableView.backgroundColor = .clear
// Remove the separators
tableview.separatorStyle = .none
If you don't want to change the section and row number of your table view (like I did), here's what you do:
1) Add an ImageView to the bottom of your table cell view.
2) Make it the same colour as the background colour of the table view.
I've done this in my application and it works perfectly. Cheers! :D
Using a bunch of different sections is not needed. The other answers use frame insets and CGRect and layers and... BLAH. Not good; use auto layout and a custom UITableViewCell. In that UITableViewCell, instead of sub viewing your content inside the contentView, make a new containerView (a UIView), subview the container view inside the contentView, then subview all your views inside the container view.
To make the spacing now, simply edit the layout margins of the container view, like so:
class CustomTableViewCell: UITableViewCell {
let containerView = UIView()
let imageView = UIImageView()
required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder)}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
containerView.translatesAutoResizingMaskIntoConstraints = false
imageView.translatesAutoResizingMaskIntoConstraints = false
contentView.addSubview(containerView)
containerView.addSubview(imageView)
contentView.layoutMargins = UIEdgeInsets(top: 15, left: 3, bottom: 15, right: 3)
containerView.layoutMargins = UIEdgeInsets(top: 15, left: 17, bottom: 15, right: 17) // It isn't really necessary unless you've got an extremely complex table view cell. Otherwise, you could just write e.g. containerView.topAnchor
let cg = contentView.layoutMarginsGuide
let lg = containerView.layoutMarginsGuide
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: cg.topAnchor),
containerView.leadingAnchor.constraint(equalTo: cg.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: cg.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: cg.bottomAnchor),
imageView.topAnchor.constraint(equalTo: lg.topAnchor),
imageView.leadingAnchor.constraint(equalTo: lg.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: lg.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: lg.bottomAnchor)
])
}
}
Try looking into
- (UIEdgeInsets)layoutMargins;
on the cell
My situation was i used custom UIView to viewForHeader in section also heightForHeader in section return constant height say 40, issue was when there is no data all header views were touched to each other. so i wanted to space between the section in absent of data so i fixed by just changing "tableview style" plane to "Group".and it worked for me.
Check out my solution on GitHub with subclassing of UITableView and using runtime features of Objective-C.
It basically uses Apple's private data structure UITableViewRowData that I got searching private runtime header of UITableView:
https://github.com/JaviSoto/iOS10-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UITableView.h,
and here's desired private class that contains everything you need to layout your cells' spacings however you want without setting it in cells' classes:
https://github.com/JaviSoto/iOS10-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UITableViewRowData.h
I was having trouble getting this to work alongside background colours and accessory views in the cell. Ended up having to:
1) Set the cells background view property with a UIView set with a background colour.
let view = UIView()
view.backgroundColor = UIColor.white
self.backgroundView = view
2) Re-position this view in layoutSubviews to add the idea of spacing
override func layoutSubviews() {
super.layoutSubviews()
backgroundView?.frame = backgroundView?.frame.inset(by: UIEdgeInsets(top: 2, left: 0, bottom: 0, right: 0)) ?? CGRect.zero
}
You can simply use constraint in code like this :
class viewCell : UITableViewCell
{
#IBOutlet weak var container: UIView!
func setShape() {
self.container.backgroundColor = .blue
self.container.layer.cornerRadius = 20
container.translatesAutoresizingMaskIntoConstraints = false
self.container.widthAnchor.constraint(equalTo:contentView.widthAnchor , constant: -40).isActive = true
self.container.heightAnchor.constraint(equalTo: contentView.heightAnchor,constant: -20).isActive = true
self.container.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
self.container.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
}
it's important to add subview (container) and put other elements in it.
Just adding to the pool of answers with what worked for me.
I’ve added a view (purple view) within the TableViewCell that I use as my cell’s content view. And constrain the purple view to have padding on top and bottom, or however you’d like, but I think this way creates a little more flexibility.
TableViewCell ->
override open var frame: CGRect {
get {
return super.frame
}
set {
var frame = newValue
frame.size.height -= 2
super.frame = frame
}
}
add a inner view to the cell then add your own views to it.
Related
In a section of my UITableView, there are 5 cells, three of which have been configured to expand/collapse to provide a more detailed view when selected. One of these cells shows a diagram of a number of small squares, which displays perfectly, until another cell is expanded, like this:
When the cell is collapsed, however, the subviews in the cell display in different cells, in different sections, like this:
and this:
To create the subviews in the cell, this is my code in the cellForRow method, which just uses an array of UIViews:
for vote in vote_array {
cell.contentView.addSubview(vote as? UIView ?? UIView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0)))
}
I tried removing all the subviews before I added them by doing this, but it doesn't change anything:
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
Edit: This is inside a switch statement, but here is the relevant cell/case cellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.contentView.clipsToBounds = true
cell.clipsToBounds = true
let vote_array = getVoteArray()
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
for case let vote as UIView in vote_array {
cell.contentView.addSubview(vote)
}
Edit:
The core of getVoteArray:
func getVoteArray() -> NSMutableArray {
var i = 0
var x = 20
var y = 4
let blockViews : NSMutableArray = []
for color in blocks {
let block = UIView.init(frame: CGRect.init(x: x, y: y, width: 20, height: 20))
block.backgroundColor = color as? UIColor
blockViews.add(block)
x = x + 24
i = i + 1
if i == num_blocks_per_row { i = 0; y = y + 24; x = 20 }
}
diagramHeight = y + 24
return blockViews
}
I can't seem to figure out why the subviews are generating randomly all over the tableView.
Ended up adding
for case let cell as UITableViewCell in tableView.subviews {
for subview in cell.contentView.subviews {
if subview.tag == 115 {
subview.removeFromSuperview()
}
}
}
to my didSelectRowAt method, after adding the tag when each view is created. I'm still not sure why the views were being added to different cells, but this got rid of them at least.
Try to implement unique ReuseIdentifiers for collapsed and expanded states.
If the cell is collapsed then don't load all those views in it by dequeuing a collapsedCell where the height of all those UIViews is either 0 or they are not added to subview.
If the cell is expanded than deque a expandedCell where the views are layed out as in the first screenshot.
After expanding and or collapsing call tableview.reloadData()
It used to be a long long time ago that UIViews clipped their children, but that hasn't been true for a very long time. If you want clipping on you need to either change UIView.clipsToBounds to true or use the underlying CALayer property maskToBounds.
cell.contentView.clipsToBounds = true
Or you can check the box in the storyboard/nib.
This is the code I used to hide the separator for a single UITableViewCell prior to iOS 11:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
// Remove separator inset
if ([cell respondsToSelector:#selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsMake(0, tableView.frame.size.width, 0, 0)];
}
// Prevent the cell from inheriting the Table View's margin settings
if ([cell respondsToSelector:#selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}
// Explictly set your cell's layout margins
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsMake(0, tableView.frame.size.width, 0, 0)];
}
}
}
In this example, the separator is hidden for the first row in every section. I don't want to get rid of the separators completely - only for certain rows.
In iOS 11, the above code does not work. The content of the cell is pushed completely to the right.
Is there a way to accomplish the task of hiding the separator for a single UITableViewCell in iOS 11?
Let me clarify in advance that I do know that I can hide the separator for the entire UITableView with the following code (to hopefully avoid answers instructing me to do this):
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
EDIT: Also to clarify after a comment below, the code does exactly the same thing if I include the setSeparatorInset line at all. So even with only that one line, the content of the cell is pushed all the way to the right.
If you are not keen on adding a custom separator to your UITableViewCell I can show you yet another workaround to consider.
How it works
Because the color of the separator is defined on the UITableView level there is no clear way to change it per UITableViewCell instance. It was not intended by Apple and the only thing you can do is to hack it.
The first thing you need is to get access to the separator view. You can do it with this small extension.
extension UITableViewCell {
var separatorView: UIView? {
return subviews .min { $0.frame.size.height < $1.frame.size.height }
}
}
When you have an access to the separator view, you have to configure your UITableView appropriately. First, set the global color of all separators to .clear (but don't disable them!)
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorColor = .clear
}
Next, set the separator color for each cell. You can set a different color for each of them, depends on you.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SeparatorCell", for: indexPath)
cell.separatorView?.backgroundColor = .red
return cell
}
Finally, for every first row in the section, set the separator color to .clear.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
cell.separatorView?.backgroundColor = .clear
}
}
Why it works
First, let's consider the structure of the UITableViewCell. If you print out the subviews of your cell you will see the following output.
<UITableViewCellContentView: 0x7ff77e604f50; frame = (0 0; 328 43.6667); opaque = NO; gestureRecognizers = <NSArray: 0x608000058d50>; layer = <CALayer: 0x60400022a660>>
<_UITableViewCellSeparatorView: 0x7ff77e4010c0; frame = (15 43.5; 360 0.5); layer = <CALayer: 0x608000223740>>
<UIButton: 0x7ff77e403b80; frame = (0 0; 22 22); opaque = NO; layer = <CALayer: 0x608000222500>>
As you can see there is a view which holds the content, the separator, and the accessory button. From this perspective, you only need to access the separator view and modify it's background. Unfortunately, it's not so easy.
Let's take a look at the same UITableViewCell in the view debugger. As you can see, there are two separator views. You need to access the bottom one which is not present when the willDisplay: is called. This is where the second hacky part comes to play.
When you will inspect these two elements, you will see that the first (from the top) has a background color set to nil and the second has a background color set to the value you have specified for entire UITableView. In this case, the separator with the color covers the separator without the color.
To solve the issue we have to "reverse" the situation. We can set the color of all separators to .clear which will uncover the one we have an access to. Finally, we can set the background color of the accessible separator to what is desired.
Begin by hiding all separators via tableView.separatorStyle = .none. Then modify your UITableViewCell subclass to something as follows:
class Cell: UITableViewCell {
var separatorLine: UIView?
...
}
Add the following to the method body of tableView(_:cellForRowAt:):
if cell.separatorLine == nil {
// Create the line.
let singleLine = UIView()
singleLine.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
singleLine.translatesAutoresizingMaskIntoConstraints = false
// Add the line to the cell's content view.
cell.contentView.addSubview(singleLine)
let singleLineConstraints = [singleLine.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 8),
singleLine.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor),
singleLine.topAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -1),
singleLine.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: 0)]
cell.contentView.addConstraints(singleLineConstraints)
cell.separatorLine = singleLine
}
cell.separatorLine?.isHidden = [Boolean which determines if separator should be displayed]
This code is in Swift, so do as you must for the Objective-C translation and make sure to continue your version checking. In my tests I don't need to use the tableView(_:willDisplayCell:forRowAt:) at all, instead everything is in the cellForRowAtIndexPath: method.
Best way IMO is just to add a simple UIView with 1pt height.
I wrote the following protocol which enables you to use it in any UITableViewCell you like:
// Base protocol requirements
protocol SeperatorTableViewCellProtocol: class {
var seperatorView: UIView! {get set}
var hideSeperator: Bool! { get set }
func configureSeperator()
}
// Specify the separator is of a UITableViewCell type and default separator configuration method
extension SeperatorTableViewCellProtocol where Self: UITableViewCell {
func configureSeperator() {
hideSeperator = true
seperatorView = UIView()
seperatorView.backgroundColor = UIColor(named: .WhiteThree)
contentView.insertSubview(seperatorView, at: 0)
// Just constraint seperatorView to contentView
seperatorView.setConstant(edge: .height, value: 1.0)
seperatorView.layoutToSuperview(.bottom)
seperatorView.layoutToSuperview(axis: .horizontally)
seperatorView.isHidden = hideSeperator
}
}
You use it like this:
// Implement the protocol with custom cell
class CustomTableViewCell: UITableViewCell, SeperatorTableViewCellProtocol {
// MARK: SeperatorTableViewCellProtocol impl'
var seperatorView: UIView!
var hideSeperator: Bool! {
didSet {
guard let seperatorView = seperatorView else {
return
}
seperatorView.isHidden = hideSeperator
}
}
override func awakeFromNib() {
super.awakeFromNib()
configureSeperator()
hideSeperator = false
}
}
And that's all. You are able to customize any UITableViewCell subclass to use a separator.
Set separator visibility from tableView:willDisplayCell:forRowAtIndexPath by:
cell.hideSeperator = false / true
I also followed this pattern once. Over the years I adjusted it. Just today I had to remove the directionalLayoutMargins part to be able to make it work. Now My function looks like this:
func adjustCellSeparatorInsets(at indexPath: IndexPath,
for modelCollection: ModelCollection,
numberOfLastSeparatorsToHide: Int) {
guard modelCollection.isInBounds(indexPath) else { return }
let model = modelCollection[indexPath]
var insets = model.separatorInsets
let lastSection = modelCollection[modelCollection.sectionCount - 1]
let shouldHideSeparator = indexPath.section == modelCollection.sectionCount - 1
&& indexPath.row >= lastSection.count - numberOfLastSeparatorsToHide
// Don't show the separator for the last N rows of the last section
if shouldHideSeparator {
insets = NSDirectionalEdgeInsets(top: 0, leading: 9999, bottom: 0, trailing: 0)
}
// removing separator inset
separatorInset = insets.edgeInsets
// prevent the cell from inheriting the tableView's margin settings
preservesSuperviewLayoutMargins = false
}
See this link if you prefer to inspect it on Github.
The PR of the removal with an explanation can be found here.
Actually when i work with UITableView, i always create custom cell class and for separators and usually make my own separator as UIView with height 1 and left and right constraints, in Your case make those steps:
1. Create custom cell.
2. Add UIView as separator.
3. Link this separator to your custom class.
4. Add hideSeparator method to your class.
-(void)hideSeparator{
self.separator.hidden == YES;
}
5. Hide the separator for any cell you want.
Hope that solves your question.
i'm using tableview in iOS 9, Swift 2
I don't understand why , when i create a table view with simple cells, i get a wrong width of the contentview, even if i set container view to 1000.
The cell width is exactly 1000, but the textLabel inside is less than 1000. Also separators are centered and with wrong width.
How can i get my cells displayed correctly , and covering the entire container view?
Everything is created programmatically.
Here is my code:
if tableView == nil {
tableView = UITableView(frame: CGRect(x: 0, y: 44/*88*/, width: self.view.bounds.size.width, height: self.view.bounds.size.height - 44/*88*/), style: UITableViewStyle.Plain)
tableView!.delegate = self
tableView!.backgroundColor = .clearColor()
tableView!.dataSource = self
tableView!.rowHeight = 40.0
tableView!.allowsSelection = false
tableView!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView!)
tableView!.reloadData()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var MyIdentifier: String = "MyReuseIdentifier"
var cell: UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(MyIdentifier)
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: MyIdentifier)
}
cell!.textLabel!.text = "Test"
cell!.backgroundColor = .clearColor()
cell!.textLabel!.textColor = .blackColor()
return cell!
}
tableView!.cellLayoutMarginsFollowReadableWidth = false
This is the solution!
It is not necessary to set contentInsets.
While the cells's width is the width of the entire view, it's content view is inset on all sides by (I think) 8 points, so it makes sense for the label not to be across the whole screen
To have a label that goes across the entire width of the screen create a custom cell class and add constraints to its label to account for this. (Label's leading to content view leading = -8.0)
You can check
tableView.contentInset
And then update left inset. Also, check that you have not given any constraint, like leadingSpace. Or margin to the UITableView itself. Similarly you have
cell.separatorInset
By default there is left padding of 5, so make this 0.
I have a static TableView set up in my storyboard and i'm trying to remove the separator in the last cell for aesthetic purposes. I've looked at the various answers on this site and other websites and they all point to this type of code for IOS8
self.tableView.tableFooterView = UIView()
However when I run the app the separator is still there. Not too sure what i'm doing wrong.
Swift 5 Version of top voted answer:
tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 1))
It works even if the number of cells is dynamic.
One smart way to remove the separator from last cell is to add a plain view with height of 1 only.
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 1)];
I'm using the code showing below for iOS 15+ since other solutions stop working.
This solution requires subclassing UITableViewCell, and manual top/bottom cell checks on cell creation time, so it's not that ideal. I would love to know a better solution.
class TableViewCell : UITableViewCell {
var hidesTopSeparator = false
var hidesBottomSeparator = false
override func layoutSubviews() {
super.layoutSubviews()
let topSeparator = subviews.first { $0.frame.minY == 0 && $0.frame.height <= 1 }
let bottomSeparator = subviews.first { $0.frame.minY >= bounds.maxY - 1 && $0.frame.height <= 1 }
topSeparator?.isHidden = hidesTopSeparator
bottomSeparator?.isHidden = hidesBottomSeparator
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let reuseID = "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: reuseID) as? TableViewCell ?? .init(style: .default, reuseIdentifier: reuseID)
let numberOfRows = self.tableView(tableView, numberOfRowsInSection: indexPath.section)
cell.hidesTopSeparator = indexPath.row == 0
cell.hidesBottomSeparator = indexPath.row == numberOfRows - 1
return cell
}
I solved this problem by moving the Separator out of the view, so that I cannot see it
if indexPath.row == settings.count-1{
cell.separatorInset = UIEdgeInsets.init(
top: 0, left: 400,
bottom: 0, right: 0)
}else{
cell.separatorInset = .zero
}
This solution will remove the last separator in each section of grouped tables, without needing to subclass the table or the cells.
Works in iOS 8. (Probably works in iOS 6+).
Add the following to your delegate:
#pragma mark - UITableViewDelegate
- (CGFloat) tableView:(UITableView*)table
heightForFooterInSection:(NSInteger)section {
// Hide the separator when the table is first displayed
// of any sections that are visible on the screen.
[self hideBottomSeparator:table];
return 0.01f; // Or whatever you already return.
}
- (void) scrollViewDidScroll:(UITableView*)table {
// Hide separators that are visible after table scrolls.
// Check that event did not come from other scrollers
// in the table.
if ([table isKindOfClass:UITableView.class]) {
[self hideBottomSeparator:table];
}
}
- (void) hideBottomSeparator:(UITableView*)table {
for (UIView* cell in table.visibleCells) {
[self removeBottomSeparatorFromCell:cell];
}
}
- (void) removeBottomSeparatorFromCell:(UITableViewCell*)cell {
for (UIView* view in cell.subviews) {
if (view.frame.origin.x == 0 &&
view.frame.origin.y > cell.frame.size.height - 2 &&
view.frame.size.width == cell.frame.size.width &&
view.frame.size.height < 2) {
[view removeFromSuperview];
}
}
}
It works by removing any thin UIViews in the visible cells that are full width and at the bottom of the cell (it leaves the top separator visible).
To remove the top separator as well, comment out the origin.y check
If you want to remove the separator from a particular section(s) you would have to get the section numbers from the cells.
Note: because it removes separators by size (and position) alone, it could fail for non-default table (or cell) inset values, or if iOS changes the look of the separator. Also, it could fail if iOS is changed so that the separators are not subviews of the cells, in which case some more rigorous scanning would be required to find the separator views.
It relies on iOS adding the cells to the table before calling heightForFooterInSection:
Also, as above, it will remove all separators from a plain table.
The following solution should work, since all that is required is a simple calculation:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return self.tableView.frame.height/X+1/X
}
X is the number of cells you want in that table.
Swift 5
the frame has to be 1 pixel above. So set y coord to -1
tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: -1, width: tableView.frame.size.width, height: 1))
iOS 15
Since things changed since iOS 15 here is a simple and clean solution how to control a cell's separators.
The original separators are not accessible in a clean way, therefore I build custom ones that look the same, plus are easy to customize.
class CustomTableViewCell: UITableViewCell {
private let topSeparator = UIView(frame: .zero)
private let botSeparator = UIView(frame: .zero)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupSeparators()
}
private func setupSeparators() {
// Removes original separators
separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: .greatestFiniteMagnitude)
topSeparator.backgroundColor = .lightGray
botSeparator.backgroundColor = .lightGray
contentView.addSubview(topSeparator)
contentView.addSubview(botSeparator)
topSeparator.translatesAutoresizingMaskIntoConstraints = false
botSeparator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topSeparator.topAnchor.constraint(equalTo: contentView.topAnchor),
topSeparator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
topSeparator.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
topSeparator.heightAnchor.constraint(equalToConstant: 0.5),
botSeparator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
botSeparator.trailingAnchor.constraint(equalTo: topSeparator.trailingAnchor),
botSeparator.leadingAnchor.constraint(equalTo: topSeparator.leadingAnchor),
botSeparator.heightAnchor.constraint(equalTo: topSeparator.heightAnchor)
])
}
func setup(hideTopSeparator: Bool, hideBotSeparator: Bool) {
topSeparator.isHidden = hideTopSeparator
botSeparator.isHidden = hideBotSeparator
}
//...
}
You can then set up the separators and your data through the controller like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.cellIdentifier) as! CustomTableViewCell
cell.setup(hideTopSeparator: indexPath.row == 0,
hideBotSeparator: indexPath.row == datasource.count - 1)
return cell
}
As others pointed out, setting a footer view no longer removes the last separator on iOS 15+. By pushing the separator out of frame, we can achieve the same result. This is my general solution:
if indexPath == tableView.lastCellIndexPath {
// Push the separator line out of frame
cell.separatorInset = UIEdgeInsets(top: 0, left: tableView.bounds.width + 1, bottom: 0, right: 0)
} else {
cell.separatorInset = .zero
}
With UITableView extension:
extension UITableView {
/// Calculates the last cell index path if available
var lastCellIndexPath: IndexPath? {
for section in (0..<self.numberOfSections).reversed() {
let rows = numberOfRows(inSection: section)
guard rows > 0 else { continue }
return IndexPath(row: rows - 1, section: section)
}
return nil
}
}
Caveat
This solution won't work when you're not reloading the entire table view or cells are moved in the last index path. Also, it turns out if you're using Diffable Data Sources and try to reload the previous last items, this reload will happen in the cell provider before the table view data source reports the updated numberOfRows. I could not figure out an easy way around that so far.
Solution for xamarin.iOS:
ActionsTableView.TableFooterView = new UIView() { Frame = new CGRect(0, 0, ActionsTableView.Frame.Size.Width, 1) };
I am setting a footer view in the viewDidLoad method:
UIView *fView = [[UIView alloc] initWithFrame:CGRectMake(0, 718, 239, 50)];
fView.backgroundColor =[UIColor yellowColor];
self.table.tableFooterView = fView;
Unfortunately, the footer is not drawing in the specified (x,y) specified above, but it stick with the cells, so if the table view has 4 cells, the footer will be drawn in the 5th cell.
I even tried the protocol method, tableView:viewForFooterInSection
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
UIView *fView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 239, 50)];
fView.backgroundColor =[UIColor yellowColor];
return fView;
}
the problem is not resolved, I am sure tableFooterView property should fi the footer view at the bottom of the table view but I am not sure what I may be missing here? Thanx in advance.
Since your goal is to have a footer that stays fixed at the bottom of the screen, and not scroll with the table, then you can't use a table view footer. In fact, you can't even use a UITableViewController.
You must implement your view controller as a UIViewController. Then you add your own table view as a subview. You also add your footer as a subview of the view controller's view, not the table view. Make sure you size the table view so its bottom is at the top of the footer view.
You will need to make your view controller conform to the UITableViewDataSource and UITableViewDelegate protocols and hook everything up to replicate the functionality of UITableViewController.
A footer view will always be added to the bottom of content.
This means that a section footer will be added below the cells of a section, a table footer view to the bottom of all sections - regardless of the position you set in your view.
If you want to add a "static" content, you should consider adding a view outside of the table view (superview) - which isn't possible if you use UITableViewController - or you use [self.table addSubView:view] and adjust the position/transform to the table view's contentOffset property in the scrollViewDidScroll: delegate method (UITableView is a subclass of UIScrollView so you also get it's delegate calls) like in this code:
#implementation YourTableViewController {
__weak UIView *_staticView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView *staticView = [[UIView alloc] initWithFrame:CGRectMake(0, self.tableView.bounds.size.height-50, self.tableView.bounds.size.width, 50)];
staticView.backgroundColor = [UIColor redColor];
[self.tableView addSubview:staticView];
_staticView = staticView;
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
_staticView.transform = CGAffineTransformMakeTranslation(0, scrollView.contentOffset.y);
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// this is needed to prevent cells from being displayed above our static view
[self.tableView bringSubviewToFront:_staticView];
}
...
Another way is to use UITableViewController in a storyboard, and embed it within a UIViewController as a container view. Then you can use auto layout to set the relationship between the footer and the container view which contains the UITableView
If your table view or table view controller is wrapped by a navigation controller consider using the navigation controller's UIToolbar. It will always stick to the bottom.
[self.navigationController setToolbarHidden:NO];
It looks like something similar to below works quite well:
import PlaygroundSupport
import UIKit
let testVC = UITableViewController(style: .grouped)
testVC.view.frame = CGRect(x: 0, y: 0, width: 400, height: 700)
testVC.view.backgroundColor = .white
class TableViewDataSourceDelegate : NSObject {
var rows = 2
}
extension TableViewDataSourceDelegate : UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rows
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.backgroundColor = .red
return cell
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
let tableViewHeight = tableView.bounds.size.height
let varticalMargin: CGFloat
if #available(iOS 11.0, *) {
varticalMargin = tableView.directionalLayoutMargins.bottom + tableView.directionalLayoutMargins.top
} else {
varticalMargin = tableView.layoutMargins.bottom + tableView.layoutMargins.top
}
let verticalInset: CGFloat
if #available(iOS 11.0, *) {
verticalInset = tableView.adjustedContentInset.bottom + tableView.adjustedContentInset.top
} else {
verticalInset = tableView.contentInset.bottom + tableView.contentInset.top
}
let tableViewContentHeight = tableView.contentSize.height - varticalMargin
let height: CGFloat
if #available(iOS 11.0, *) {
let verticalSafeAreaInset = tableView.safeAreaInsets.bottom + tableView.safeAreaInsets.top
height = tableViewHeight - tableViewContentHeight - verticalInset - verticalSafeAreaInset
} else {
height = tableViewHeight - tableViewContentHeight - verticalInset
}
if (height < 0) {
return 0
} else {
return height
}
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let extraButtonSpace = UIView()
extraButtonSpace.backgroundColor = .clear
return extraButtonSpace
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
tableView.beginUpdates()
rows += 1
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
} else if indexPath.row == 1 {
tableView.beginUpdates()
rows -= 1
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
} else {
tableView.beginUpdates()
tableView.endUpdates()
}
}
}
let controller = TableViewDataSourceDelegate()
testVC.tableView.delegate = controller
testVC.tableView.dataSource = controller
testVC.tableView.reloadData()
let extraButtonSpace = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 80))
extraButtonSpace.backgroundColor = .yellow
testVC.tableView.tableFooterView = extraButtonSpace
PlaygroundPage.current.liveView = testVC.view
I was able to get a label to be fixed to the bottom of my static UITableViewController. Not the perfect solution for all scenarios, but worked for my simple needs.
UIView* v = [[UIView alloc] initWithFrame:self.view.bounds];
CGFloat labelHeight = 30;
CGFloat padding = 5;
UILabel* l = [[UILabel alloc] initWithFrame:CGRectMake(0, v.frame.size.height - labelHeight - padding, self.view.frame.size.width, labelHeight)];
l.text = #"Hello World";
[v addSubview:l];
[self.tableView setBackgroundView:v];
If you want to make footer fixed at bottom, you should create custom footerView and change footer frame when tableView content size is changing:
-(void)changeCustomTableFooterYPositionWithTableFrame:(CGRect)tableFrame tableContentSize: (CGSize) tableContentSize {
CGFloat originalTableViewTopEdgeInset = self.tableView.contentInset.top;
CGFloat originalTableViewBottomEdgeInset = self.tableView.contentInset.bottom - self.tableFooterView.frame.size.height;
CGFloat footerViewYPositionByContentSize = tableContentSize.height;
CGFloat footerViewYPositionByTableSize = tableFrame.size.height - self.tableFooterView.frame.size.height - originalTableViewTopEdgeInset - originalTableViewBottomEdgeInset;
CGFloat tableFooterViewYPosition = MAX(footerViewYPositionByContentSize, footerViewYPositionByTableSize);
self.tableFooterView.frame = CGRectMake(self.tableFooterView.frame.origin.x, tableFooterViewYPosition, self.customTableFooterView.frame.size.width, self.customTableFooterView.frame.size.height);
}
To detect when contentSize was changed add observer to contentSize:
[self addObserver: self forKeyPath: #"tableView.contentSize" options: NSKeyValueObservingOptionNew + NSKeyValueObservingOptionOld context: ContentSizeContext];
Do not forget to change tableView.edgeInsets when insert footer:
self.tableView.contentInset = UIEdgeInsetsMake(self.tableView.contentInset.top, self.tableView.contentInset.left, self.tableView.contentInset.bottom + self.customTableFooterView.frame.size.height, self.tableView.contentInset.right);
You can see inherited class and example at the link below:
TableViewWithFooterAtBottom
You can use this to make the table look smaller according to how many rows do you have :
let tblView = UIView(frame: CGRectZero)
tableView.tableFooterView = tblView
tableView.tableFooterView!.hidden = true
tableView.backgroundColor = UIColor.clearColor()
Another alternative would be to just change the height for row at index path depending on for what number minimum rows you have that problem.
The following is the solution for this footer problem, when we do NOT want the footer to stick in the bottom all the time, AKA. it only sticks to the bottom when there are not enough rows to fill the screen, or when the user scrolls all the way down of the screen.
Add your self.footerView to your self.tableView as a subview on -viewDidLoad: or somewhere like that, then set the delegate for self.tableView, update the content inset of the tableview to self.tableView.contentInset = UIEdgeInsetsMake(0, 0, CGRectGetHeight(self.footerView), 0); and set up the following methods:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self updateFooterView];
}
- (void)updateFooterView
{
CGRect sectionFrame = [self.tableView rectForSection:0];
CGFloat bottomSpace = self.tableView.contentOffset.y + CGRectGetHeight(self.tableView.frame) - CGRectGetMaxY(sectionFrame);
CGFloat footerHeight = CGRectGetHeight(self.footerView.frame);
CGFloat transformY = self.tableView.contentOffset.y + footerHeight - MIN(bottomSpace,footerHeight);
CGRect footerFrame = self.footerView.frame;
footerFrame.origin.y = self.tableView.bounds.size.height - footerFrame.size.height + transformY;
self.footerView.frame = footerFrame;
}
Whenever you need to update the footer (i.e. after adding a new row), just call -updateFooterView and you should be good
Im not super proud of this solution, but it worked for me using only IB as of today. It will use the toolbar area of your UITableViewController, if that works for you.
Create a new temporary UIViewController
Drag a Toolbar into this UIViewController
Drag a UIView on this toolbar. I used the elements tree on the left for that, was easier. This will create a BarButtonItem you'll move on step 5.
Drag a BarButtonItem on your UITableViewController, this will create a Toolbar items section.
Drag the BarButtonItem created on step 3 into the Toolbar items section created on step 4.
Delete the UIViewController and edit the BarButtonItem as you wish.