How to add spacing between UITableViewCells - Swift - ios

I have a table with some customizations.
Here is my code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
var exercises : [String] = ["Swimming", "Running", "Weight Lifting", "Biking", "Climbing"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
//Necessary for basic tableView setup. Defines number of rows in a specific section.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
//Setting the amount of rows to the number of elements in exercises. This function returns that.
tableView.backgroundColor = UIColor.clearColor()
return exercises.count
}
//Necessary for basic tableView setup. Helps us out content for every cell in the index path. Runs = rows
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
tableView.separatorColor = UIColor.clearColor()
//Setting the footer to default so the extra junk does not show
tableView.tableFooterView = UIView()
//This will be returned. This automatically creates a prototype cell
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//Setting every cell to the respective item in exercises
cell.textLabel?.text = exercises[indexPath.row]
cell.textLabel?.font = UIFont(name: "Avenir-Light", size: 17)
cell.textLabel?.textColor = UIColor.whiteColor()
cell.textLabel?.textAlignment = .Center
//Border Code
cell.layer.borderWidth = 2.0
cell.layer.borderColor = UIColor.whiteColor().CGColor
//Round Corners
cell.layer.cornerRadius = 20
return cell
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.backgroundColor = UIColor.clearColor()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I want to have some spacing between each UITableViewCell. I have already tried the following:
Change the height of each row. This option does not work because I have borders. Adding more height just makes each row look larger.
Convert each row into a section and then use heightForHeader in section.The post. I want to avoid this option because I would have to convert all my rows to sections.
Add a transparent UIView within each row. Again, this option does not work because I have borders.
Is there any other alternative?
Thanks

First of all, you should move tableView related code out of tableView:cellForRowAtIndexPath, preferably to viewDidLoad:
override func viewDidLoad {
super.viewDidLoad()
tableView.separatorColor = UIColor.clearColor()
tableView.tableFooterView = UIView()
}
Secondly, UITableViewCells are reusable objects so they are dequeued by the tableView when required:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
}
...
}
As for your problem, you should either set rowHeight on tableView
override func viewDidLoad {
super.viewDidLoad()
...
tableView.rowHeight = 100.0
}
or implement tableView:heightForRowAtIndexPath: instead:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100.0
}
You should also update textLabel's border and corner radius value instead of the cell:
//Border Code
cell.textLabel.layer.borderWidth = 2.0
cell.textLabel.layer.borderColor = UIColor.whiteColor().CGColor
//Round Corners
cell.textLabel.layer.cornerRadius = 20

I tried ozgur's method but it didn't work because I had borders between my table view cells. Eventually, I used the answer from this post. Hope it helps

You can add spacing by creating an extra view inside the cell that contains the content of the cell that has spacing between the top and the bottom. Make the background color of the cell translucent and it'll appear as though the cell has spacing above and below it

Related

Swift UITableViewCell separatorStyle breaking autolayout on iPhone MIni

I have a UITableView which has a UITableViewCell which contains a UIImageView.
The constraints are setup such that the UIImageView has padding 20 points at the top and sides, and a size ratio of 1:1, so the UIImageView will always be square regardless of the device width.
I apply a cornerRadius to the UIImageView so the image is circular.
However.... the autolayout doesn't seem to work on the first load. But after the first load, it works perfectly.
I have tried every known combination of setNeedsLayout or layoutIfNeeded - both inside the UITableViewCell and in the UITableView code. Nothing works the first time it loads.
Please help!
Code looks like this:
class CircularProfileCell: UITableViewCell {
#IBOutlet weak var circularView: UIView!
func setup() {
circularView.layer.cornerRadius = circularView.bounds.height / 2
}
}
class CircularProfileVC: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.separatorStyle = .none
self.tableView.register(UINib(nibName: "CircularProfileCell", bundle: nil), forCellReuseIdentifier: "CircularProfileCell")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CircularProfileCell", for: indexPath) as! CircularProfileCell
cell.setup()
return cell
}
}
Setup looks like this:
Because corner radius is a layer property it does not always play well with auto layout. In addition, I guess you set it up with frame properties of the view (i.e imageView.layer.cornerRadius = imageView.bounds.height/2).
Hence you should try and set the corner radius on the layoutSubviews() function of the cell. This will make sure to render the correct size
override func layoutSubviews() {
super.layoutSubviews()
imageView.layer.cornerRadius = imageView.bounds.height/2
...
}
This only happens when the tableView.separatorStyle = .none
So to fix it I simply leave the separator on, but set the separator color to clear
self.tableView.separatorStyle = .singleLine
self.tableView.separatorColor = UIColor.clear
Thanks to #CloudBalacing for the help. More info about this problem here

Self sizing tableview inside self sizing tableview cell

Let's say I have hierarchy like this:
*TableViewCell
**TableView
***TableViewCell
and all of them should be resizable. Did someone face this kind of problem? In past I've used many workarounds like systemLayoutSizeFitting or precalculation of height in heightForRowAt, but it always breaks some constraints, because TableViewCell has height constraint equal to estimated row height and there appear some kinds of magic behavior. Any ways to make this live?
Current workaround:
class SportCenterReviewsTableCell: UITableViewCell, MVVMView {
var tableView: SelfSizedTableView = {
let view = SelfSizedTableView(frame: .zero)
view.clipsToBounds = true
view.tableFooterView = UIView()
view.separatorStyle = .none
view.isScrollEnabled = false
view.showsVerticalScrollIndicator = false
view.estimatedRowHeight = 0
if #available(iOS 11.0, *) {
view.contentInsetAdjustmentBehavior = .never
} else {
// Fallback on earlier versions
}
return view
}()
private func markup() {
contentView.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(ReviewsTableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.snp.makeConstraints() { make in
make.top.equalTo(seeAllButton.snp.bottom).offset(12)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
make.bottom.lessThanOrEqualTo(contentView.snp.bottom)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
cell.setNeedsLayout()
cell.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)
return size.height
}
}
Self sizing tableView class:
class SelfSizedTableView: UITableView {
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
self.setNeedsLayout()
self.layoutIfNeeded()
return contentSize
}
}
This is actually not an answer to the question, but just an explanation.
(Wrote here because of the character count limitation for the comments).
The thing is that you're trying to insert a vertically scrollable view inside another vertically scrollable view. If you don't disable the nested tableview's scroll ability, you will have a glitch while scrolling, because the system wouldn't know to whom pass the scroll event (to the nested tableview, or to the parent tableview).
So in our case, you'll have to disable the "scrollable" property for the nested tableviews, hence you'll have to set the height of the nested tableview to be equal to its content size. But this way you will lose the advantages of tableview (i.e. cell reusing advantage) and it will be the same as using an actual UIScrollView. But, on the other hand, as you'll have to set the height to be equal to its content size, then there is no reason to use UIScrollView at all, you can add your nested cells to a UIStackView, and you tableview will have this hierarchy:
*TableView
**TableViewCell
***StackView
****Items
****Items
****Items
****Items
But again, the right solution is using multi-sectional tableview. Let your cells be section headers of the tableview, and let inner cells be the rows of the tableview.
here is an example of how to make a tableview inside a table view cell with automatic height for the cells.
You should use the 'ContentSizedTableView' class for the inner tableViews.
class ViewController: UIViewController {
#IBOutlet weak var outerTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
outerTableView.rowHeight = UITableView.automaticDimension
outerTableView.estimatedRowHeight = UITableView.automaticDimension
outerTableView.delegate = self
outerTableView.dataSource = self
}
}
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
sizeToFit()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableTableViewCell
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
Use xib files to simplify the hierarchy.
Get a tableView on your storyboard, and create a nib file for your tableViewCell(say CustomTableViewCell). Inside it create a tableView and again create one more tableViewCell xib file. Now, no need of setting labels into your xib file,(if you want only labels in cells and nothing else, if not, there is another way of adding constraints)
Say you have an array of text, some strings are long and some are short.
register nib file in CustomTableViewCell and extend it to use Delegate and DataSource.
register this CustomTableViewCell in ViewController.
While declaring a cell in CustomTableViewCell, just do=
cell.textLabel?.text = content
cell.textLabel?.numberOfLines = 0
Use heightForRowAt to set outer tableViewCell's height, and let the inner tableView to scroll inside.

Auto layout multiple lines label in UITableview // Swift 4.2

I wrote such extension to display my data in UITableview. But sometimes my data can contain more than 1 line and I need to create something to display full content. How could I change my code (below) to do it?
extension ViewController: UITableViewDataSource, UITableViewDelegate {
// Define no of rows in your tableView
func tableView(_ chatHistoryTable: UITableView, numberOfRowsInSection section: Int) -> Int {
return messagesData.count
}
func tableView(_ chatHistoryTable: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = chatHistoryTable.dequeueReusableCell(withIdentifier: "userMessage")! as UITableViewCell
cell.textLabel!.text = messagesData[indexPath.row]
cell.textLabel!.textAlignment = .right
return cell;
}
}
I think that I should write something for UITableViewCell too, but I don't know, am I correct.
Please help me with this question.
in here you need to follow two changes.
initially set the estimate height and auntomaticdimension for Self Sizing Cell, on your page loads call the following line.
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
for e.g
#IBOutlet weak var yourTableviewName: UITableView!{
didSet{
yourTableviewName.tableFooterView = UIView()
yourTableviewName.estimatedRowHeight = 44.0
yourTableviewName.rowHeight = UITableView.automaticDimension
}
}
secondary for your word wrap and numberOfLines for your lables. follow the below line on your cellforRow method
func tableView(_ chatHistoryTable: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = chatHistoryTable.dequeueReusableCell(withIdentifier: "userMessage")! as UITableViewCell
cell.textLabel!.numberOfLines = 0
cell.textLabel!.lineBreakMode = .byWordWrapping
cell.textLabel!.text = messagesData[indexPath.row]
cell.textLabel!.textAlignment = .right
return cell;
}
}
Step 1. Add a chain of unbroken vertical constraints in Custom Table View Cell
Step 2. Set the cell estimated height
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
Step 3:Make the label support multiple lines
textLabel.LineBreakMode = UILineBreakMode.WordWrap;
textLabel.Lines = 0;

tableView does not appear when centered into its parent view

I have a viewController with the following (static) tableView:
class viewController: UIViewController, UITableViewDelegate {
private let tableView: UITableView = {
let tv = UITableView()
tv.separatorStyle = .singleLine
tv.allowsSelection = true
tv.isScrollEnabled = false
return tv
}()
private let tableData = ["row1", "row2", "row3", "row4"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.tableFooterView = UIView()
view.addSubview(tableView)
NSLayoutConstraints.activate([
tableView.centerXAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
tableView.centerYAnchor.constraints(equalTo: view.safeAreaLayoutGuide.centerYAnchor)
)]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
}
When I run the app, this viewController shows a blank screen. I know that the way I am setting up the tableview's constraints is the problem because when I set up the tableView using topAnchor, bottomAnchor, leftAnchor, and rightAnchor (and with some other tweaking) the tableview appears. Any idea why the app is behaving this way?
Your table view is probably there, and centered, but you didn't define a size, so it's probably being set to zero width and height, that's why you don't see it.
You can fix this by setting a constraint on it's width and height, either to a constant or related to it's superview, depending on what you want.
The problem is this is NOT a static table view. If it were, you would not have implemented cellForRowAt. It is a normal table view and it needs a data source and delegate. Plus it needs a height and a width.

Swift Tableview always showing separator lines

I seem to have a weird problem with one of my tableviews where I can't hide the separator lines ( I think they're separator lines, except that they're centred on the screen instead of hard up against the right hand side ).
I create everything programmatically so no storyboard issues here.
You can see the tiny 1px line above each of the cells below.
I load my table using:
self.tableView.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.tableView.backgroundColor = UIColor.whiteColor()
self.tableView.scrollEnabled = true
self.tableView.bounces = false
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
self.view.addSubview(self.tableView)
I also have a custom header which I implement using the following code:
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header:UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.textLabel.textColor = UIColor.whiteColor()
header.textLabel.frame = header.bounds
header.textLabel.textAlignment = NSTextAlignment.Center
view.tintColor = constants.getTintColor()
header.textLabel.textColor = UIColor.whiteColor()
}
I've tried loading the table without the willDisplayHeaderView and the issue persists.
I have also tried adding
tableview.separatorStyle = UITableViewCellSeparatorStyle.None
and
tableView.separatorColor = UIColor.clearColor()
to the following methods:
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return self.boards.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return self.boards[section].items.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return self.boards[section].name
}
EDIT:
The cells are standard UITableViewCells with alternating colors for the cells, this is being set through:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
if indexPath.row % 2 == 0 {
// Even
cell.backgroundColor = constants.UIColorFromRGB(0x1EACE0)
} else {
// Odd
cell.backgroundColor = constants.UIColorFromRGB(0x6FBEE5)
}
cell.textLabel?.textColor = UIColor.whiteColor()
cell.textLabel?.text = self.boards[indexPath.section].items[indexPath.row].title
return cell
}
EDIT 2:
I've now added separator lines in and these aren't separators because you can still see them below the separator line.
HELP! I'm confused. I have a number of other tableviews setup the same (as far as I can see) and they work fine.
Thanks SO!
You can either use the Table View property for the seperatorStyle and use the property as such UITableViewCellSeparatorStyleNone
or use remove the separator from the StoryBoard
Set the property of the Seperator to None
Or one thing more while you are using header and Footer so the separator line would be Zero but there might be a extra separator or header and footer so refer to this link
Eliminate extra separators below UITableView
in your viewDidLoad() function, add this:
tableView.tableFooterView = UIView()
Since you are not using custom cell's you need to do the following.
Create cell's as let cell : UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Subtitle, reuseIdentifier:"cell")
and remove self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
Here's a little additional information about eliminating the spurious separators that might be helpful. The solution that the originator said worked was this one that creates a new cell:
let cell : UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Subtitle, reuseIdentifier:"cell")
But the originator did not say how he worked this into his code, whose original version used the two argument version of dequeueReusableCellWithIdentifier:
let cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
I experienced the same problem of spurious separators and found that using the single argument version of dequeueReusableCellWithIdentifier also fixes the problem:
let cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
I ran into same issue recently, and finally fixed by this in cell's init:
self.contentView.backgroundColor = UIColor.AppBgColor;
self.backgroundColor = UIColor.AppBgColor;
If you look closely in View Hierarchy, the cell's background and it's contentView's background are not same, which leads to this issue.

Resources