spacing between UITableViewCells swift3 - ios

I am creating a IOS app in swift and want to add spacing between cells like this
I would like to give space of each table view cell same like my attach image.
How I can do that? and Right now all cells are coming without any space.
swift3

you can try this in your class of tableView cell:
class cell: UITableViewCell{
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 4
frame.size.height -= 2 * 5
super.frame = frame
}
}
}

From Storyboard, your view hierarchy should be like this. View CellContent (as highlighted) will contain all the components.
Give margin to View CellContent of 10px from top, bottom, leading & trailing from its superview.
Now, select the tblCell and change the background color.
Now run your project, make sure delegate and datasource are properly binded.
OUTPUT
NOTE: I just added 1 UILabel in View CellContent for dummy purpose.

Update: UIEdgeInsetsInsetRect method is replaced now you can do it like this
contentView.frame = contentView.frame.inset(by: margins)
Swift 4 answer:
in your custom cell class add this function
override func layoutSubviews() {
super.layoutSubviews()
//set the values for top,left,bottom,right margins
let margins = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
contentView.frame = UIEdgeInsetsInsetRect(contentView.frame, margins)
}
You can change values as per your need
***** Note *****
calling super function
super.layoutSubviews()
is very important otherwise you will get into strange issues

If you are using UITableViewCell to achieve this kind of layout, there is no provision to provide spacing between UITableViewCells.
Here are the options you can choose:
Create a custom UIView within UITableViewCell with clear background, so that it appears like the spacing between cells.
You need to set the background as clear of: cell, content view.
You can use UICollectionView instead of UITableView. It is much more flexible and you can design it the way you want.
Let me know if you want any more details regarding this.

One simple way is collection view instead of table view and give cell spacing to collection view and use
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let widthSize = collectionView.frame.size.width / 1
return CGSize(width: widthSize-2, height: widthSize+20)
}
And if you want tableview only then add background view as container view and set background color white and cell background color clear color set backround view of cell leading, trilling, bottom to 10
backgroundView.layer.cornerRadius = 2.0
backgroundView.layer.masksToBounds = false
backgroundView.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor

Please try it. It is working fine for me.
You can use section instead of row.
You return array count in numberOfSectionsInTableView method and set 1 in numberOfRowsInSection delegate method
Use [array objectAtIndex:indexPath.section] in cellForRowAtIndexPath method.
Set the heightForHeaderInSection as 40 or according to your requirement.
Thanks,Hope it will helps to you

- Statically Set UITableViewCell Spacing - Swift 4 - Not Fully Tested.
Set your tableView Row height to whatever value you prefer.
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = <Your preferred cell size>
tableView.rowHeight = UITableViewAutomaticDimension
// make sure to set your TableView delegates
tableView.dataSource = self
tableView.delegate = self
}
extension YourClass : UITexFieldDelegate, UITextFieldDataSource {
//Now set your cells.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath) as! UITableViewCell
//to help see the spacing.
cell.backgroundColor = .red
cell.textLabel?.text = "Cell"
return cell
}
//display 3 cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
//now lets insert a headerView to create the spacing we want. (This will also work for viewForHeaderInSection)
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//you can create your own custom view here
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44) //size of a standard tableViewCell
//this will hide the headerView and match the tableView's background color.
view.backgroundColor = UIColor.clear
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
}

Related

UICollectionView inside UITableViewCell how to adjust tableview cell height based on screen size

I have the following layout
UITableView
UITableViewCell (class: CategoryCell)
Label
Button
UICollectionView
UICollectionViewCell (class: ItemCell)
UIImageView
UILabel
I am trying to make UICollectionView to always show 3 items no matter the screen size. I was able to get items to be shown based on screen width and was able to set the height. However, the height of table view does not change from inside CategoryCell. Here is the code:
// CategoryCell.swift
func awakeFromNib() {
super.awakeFromNib()
// Calculate width and height based on screen width
let screenWidth = UIScreen.main.bounds.width
let itemWidth = screenWidth / 3.0
let itemHeight = itemWidth / 0.75 // 3:4 aspect ratio
// Change height of table view cell
self.bounds.size.height = self.bounds.size.height - collectionView.bounds.size.height + itemHeight
// This is the line does nothing
// It should remove collectionView height from tableView (height of collectionView is set through autolayout) then add he new height to it.
// Set collection view height to equal to item height
collectionView.bounds.size.height = itemHeight
// set item height
let layout = collectionViewProducts.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: itemWidth, height: itemHeight - 1.0)
// -1.0 because item/cell height needs to be SMALLER than collection view height
}
How can I change table view cell height from inside the cell class itself? My only guess is that I should not be doing these operations inside awakeFromNib but I am not able to find another function to call these from.
EDIT: I am using RxSwift, so my data source code is the following:
let observable = Observable.just(data)
observable.bindTo(tableView.rx.items(cellIdentifier: "CategoryCell", cellType: CategoryCell.self)) { (row, element, cell) in
cell.setCategory(category: element)
}.disposed(by: disposeBag)
tableView.rx.setDelegate(self).disposed(by: disposeBag)
You could implement UITableViewDelegate's heightForRowAt and return a value based on a variable. And you can set the variable wherever you do your UICollectionView itemHeight calculation. So, when you are done with the calculation, you should be able to do a table view data reload and the layout should update using the new itemHeight value.
I have not tested the code but the above should work. If you run into any issues, or I've misunderstood your requirements somehow, do let me know.
Provide the constraints to the collection view and make collectionviewcell outlet in tableviewcell page as well as collectionviewheight constraint outlet in the same tableviewcell page.
Like this in tableviewcell page:
class HomeServiceTableCell: UITableViewCell {
#IBOutlet weak var dealsCollectionView: UICollectionView!
#IBOutlet weak var dealsCollectionHeight: NSLayoutConstraint!
#IBOutlet weak var dealsImage: UIImageView!
// like this in the main page cellforRowAt indexPath function
func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = homeServiceTable.dequeueReusableCell ( withIdentifier: "homeServiceTableCell6" ) as! homeServiceTableCell6
cell.dealsImage.image = UIImage(named: "deals.png")
cell.dealsCollectionView.tag = indexPath.row
cell.dealsCollectionView.delegate = self
cell.dealsCollectionView.dataSource = self
cell.dealsCollectionView.reloadData()
cell.dealsCollectionHeight.constant = cell.dealsCollectionView.collectionViewLayout.collectionViewContentSize.height
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
// lastly add this line in viewWillAppear function
override func viewWillAppear(_ animated: Bool) {
self.homeServiceTable.layoutSubviews()
}
}
You need to provide tableview's cell height for every cell and based on that you can calculate your collectionview cell's height. for dynamic height of tableview's cell you need to implement these method.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
or if you need fixed height then simply use these method and provide your cell height based on your calculation.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return fixedSize //e.g 100
}
try this hope it will help you.
Thank you #Prince and #Fahim. Your answers gave me an idea to move my row height logic to tableView delegate. Since, I am calculating the height based on screen width and aspect ratio. I just moved the same logic to tableView height:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let screenWidth = UIScreen.main.bounds.size.width
// 3 items + 10px gap between cells
let itemWidth = (screenWidth / 3.0) - 20
// 3:4 aspect ratio + other elements
let itemHeight = (itemWidth) / 0.75 + 110
return itemHeight
}
For now this solution works normally for me, I would like to change it so it takes into account the cell itself.
I know it is weird to ask a question in the answer but it is related to this thread, so I think it is best to ask it here. How can I get the cell object from indexPath? Using tableView.cellForRow(at: indexPath) gives me bad access.

Custom UITableViewCell from XIB: contentView width is always the same

I've created a custom UITableViewCell with an xib file. In there I have placed several labels and views relative to the width of the contentView. Unfortunately the contentView.bounds.width property stays always the same no matter which device is selected.
My ViewController is also loaded from nib:
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
init(title: String) {
super.init(nibName: "ViewController", bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerNib(UINib(nibName: "CustomTableViewCell", bundle: nil), forCellReuseIdentifier: "CustomTableViewCell")
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell", forIndexPath: indexPath) as! CustomTableViewCell
return cell
}
}
In the CustomTableViewCell class I print out the width property of the content view:
override func awakeFromNib() {
super.awakeFromNib()
print("self.contentView.bounds.width: \(self.contentView.bounds.width)")
}
This always prints out the width set in the Interface Builder, even though it should follow the width of the tableView which uses AutoLayout:
Trying to set the cell's frame before returning didn't work:
cell.frame = CGRectMake(0, 0, self.tableView.frame.size.width, 71)
If you are using autolayout then count your width in cellforrowAtindexpath and you will get proper width. awakeFromNib gets called before your cell get autolayout so it is giving same width as set in interface builder.
Second thing if you are using autolayout then setting frame has no meaning. if you want to change height or width then you should take outlet of your constraint (height or width) and you can change it's constant to desired value!
If you want to change height only then you can play with heightForRowAtIndexPath with if else which return desire height as per condition!
Swift 4+
You can use following code, it will set the frame of XIB cell and you can adjust content also with help of XIB height and width.
let cellRect = CGRect(x: 0, y: 0, width: postTableView.frame.size.width, height: imageHeight)
cell?.frame = cellRect
Hope it will work

tableview's Row height is not changing , CollectionView is capturing all the unused space instead of resizing itself in the tableViewCell

I put a UICollectionView into the UITableViewCell by following this tutorial and in my UICollectionViewCell, there's a Image View. So when I run my app, the collection view is not resizing itself at the same time in my cell I put a Text View which is resizing itself according to content, see the below images:
In this first image, I have a text view at the top which have some text in it, and below it with (pink background) is my collection view and inside of that with greenBackground is my image view, as you can see that collection view is taking the extra space instead of reducing itself as Text View Did.
in this second image you can see that my textView haves more content then before so its resized itself now overlapping the CollectionView
this is my TableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var txtView: UITextView!
#IBOutlet private weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
// collectionView.frame = self.bounds;
// collectionView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCollectionViewDataSourceDelegate
<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>
(dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row
collectionView.reloadData()
}
var collectionViewOffset: CGFloat {
get {
return collectionView.contentOffset.x
}
set {
collectionView.contentOffset.x = newValue
}
}
}
this is my collectionViewCell
class CollectionViewCell: UICollectionViewCell {
#IBOutlet var imgView: UIImageView!
override func awakeFromNib() {
self.setNeedsLayout()
//
// self.contentView.frame = self.bounds;
// self.contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}
}
and this is my TableviewController
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return imageModel.count
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell",
forIndexPath: indexPath) as! TableViewCell
cell.txtView.text = txtArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
tableViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
override func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
storedOffsets[indexPath.row] = tableViewCell.collectionViewOffset
}
}
extension TableViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return imageModel[collectionView.tag].count
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",
forIndexPath: indexPath) as! CollectionViewCell
cell.imgView.frame = CGRectMake(0, 0, collectionView.frame.width, collectionView.frame.height)
cell.imgView.contentMode = .ScaleAspectFit
//cell.addSubview(imageView)
cell.imgView.image = ResizeImage(UIImage(named: imageModel[collectionView.tag][indexPath.item])!, targetSize: CGSizeMake( cell.imgView.frame.width , cell.imgView.frame.height))
//imageView.image = UIImage(named: imageModel[collectionView.tag][indexPath.item])
return cell
}
}
How can I make this collection view to AutoLayout itself according to the content in it? I also tried this:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 100;
but didn't worked (my collection view got disappear) if anybody knows how to do this, then please guide me..
I faced a similar issue when i used collection view inside a table view cell. No matter what i did i couldn't get the table view to resize automatically but the collection view did. Soo instead of autolayout i did it using code.
I ended up having to calculate the size of the label in the collection view numberOfSections in collection view and passing this height using a delegate to the view controller that has the tableView's delegate and dataSource and reloading the appropriate row of the table view.
As it happens, the numberOfSections in collectionview data source gets called everytime and the delegate resizes the table view height.
Some thing like this-
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
[self.delegate setViewHeight:[self getHeightForCellAtIndexPath:[NSIndexPath indexPathForRow:currentSelected inSection:0]]];
return 1;
}
This ought to give you a general idea.
EDIT: Sorry i misunderstood, your question before. Here is something that should work for you:
As per my understanding, you have a table view cell with a label view and collection view inside of it.
Now, inside your table view cell, you should add top, leading and trailing constraints space to the label. Now inside your collection view position your image vertically in the center and add an appropriate top and bottom to the cell superview. Your collection view itself should have a CONSTANT value in leading, trailing, top to label and bottom to superview. Also add a fixed height constraint to your collection View (assuming you want the image sizes to remain the same).
Now lets says View Controller A is the data source for your table view and the table view cell is the data source for your collection view.
In your viewController A, you should write your height for row at indexPath as-
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGSize stringSize = [yourStringArray[indexPath.row] boundingRectWithSize:CGSizeMake(_yourCollectionView.frame.size.width, FLT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:[UIFont yourFont size:yourFontSize]} context:nil].
return stringSize.height + 35; //magic number 35, play around with it to suit your need. Did this to always have a minimum fixed height.
}
This will allow your tableViewRowForHeight for that particular index to have the height of your label added to it and the constraints ought to do the rest.

Prevent footer overlapping tableViewCell in UITableView - Swift

I have this table view which works how i want it to however i have a problem where the footer overlap the cells in the table view as seen in
How can i prevent this? This is my code for the footer
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
// self.myTableView.tableFooterView = footerView;
let sectionString = Array(foodArray.keys)[section]
for value in caloriesArray[sectionString]! {
calories += value
}
totalCalories += calories
print(calories)
print(totalCalories)
let label = UILabel(frame: CGRectMake(footerView.frame.origin.x - 15, footerView.frame.origin.y, footerView.frame.size.width, 20))
label.textAlignment = NSTextAlignment.Right
label.text = "Total Calories: \(calories) "
footerView.addSubview(label)
calories = 0
return footerView
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 20.0
}
#IBAction func addFoodTapped(sender: AnyObject) {
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let sectionString = Array(foodArray.keys)[indexPath.section]
foodArray[sectionString]?.removeAtIndex(indexPath.row)
caloriesArray[sectionString]?.removeAtIndex(indexPath.row)
print(foodArray)
viewDidAppear(true)
}
You can do that, Just make Style Grouped as shown below:
Just add some padding to the bottom of the content so that it doesn't overlap with the footer:
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, FOOTER_HEIGHT, 0)
I know this is an old thread, so this is more so for others that encountered this (like myself).
The floating section footer is default behavior from my understanding. There are a couple of options that I can think of:
Provide your footer view with a background color (footerView.backgroundColor = UIColor.white) thus cleaning up the overlap.
or
Replace the section footer with a custom "Total Calories" cell that you add to the table after the last cell in that section, effectively acting like a footer cell.
Sorry for the delay. If you have modified the standard contentInsetAdjustmentBehavior of the tableView, you must adjust the tableView contentInset property to take into account the total height of the views at the bottom of the UITableView, like the tab bar. If contentInsetAdjustmentBehavior is set to "automatic" (or you didn't change the default value), then set the clipsToBounds property of your footer view to true so that its child views cannot be painted outside the footer view layer's frame. That should solve your issue.
Try to override this method
override func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 44.0
}
Just set your label's background color to UIColor.white. and you are done !
label.backgroundColor = UIColor.white
Of course it overlaps. This is how footers and header work in UITableViews. You can set the footerView.backgroundColor to UIColor.gray, for example, to make it look better.

Self Sizing Cell in Swift, how do I make constraints programmatically?

I'm trying to do a sidebar with self sizing cells in swift. I found this great tutorial : http://www.appcoda.com/self-sizing-cells/
As far as I know you need 3 things to make a self sizing cell:
Define auto layout constraints for your prototype cell
Specify the estimatedRowHeight of your table view
Set the rowHeight of your table view to UITableViewAutomaticDimension
The last two are covered in the tutorial by code, but the first one is explained by the story board, and my problem is how do I implement it by code??
I have this method where I get my custom cell, I think that I have to implement the constraints(in the tutorial you can see what kind of constrains) here but I don't know how, so please could you give me some help?
override func tableView(tableView: (UITableView!), cellForRowAtIndexPath indexPath: (NSIndexPath!)) -> UITableViewCell{
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CellId")
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame:CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.orangeColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
//asignar valores a la celda
cell!.textLabel?.text = tableData[indexPath.row]
cell!.textLabel?.numberOfLines = 0
cell!.setTranslatesAutoresizingMaskIntoConstraints(false)
}
return cell!
}
Update 1:
The text goes beyond the 3rd break of line in the 1st and the 3rd row, but here only show me max. of 3 breaklines
Thanks!
Don't add layout constraints in tableView(_:cellForRowAtIndexPath:).
You should not instantiate a table view cell in that method yourself. Instead, you should register one with the UITableView (probably in the viewDidLoad() method of your view controller) (registerClass(_:forCellReuseIdentifier:) or registerNib(_:forCellReuseIdentifier:)).
In tableView(_:cellForRowAtIndexPath:) you use dequeueReusableCellWithIdentifier(_:forIndexPath:) to get an instance of the cell. The table view will reuse cells as you scroll, so that it doesn't have to create new ones all the time. Essentially, let's say you never see more than 15 cells on screen at the same time, then there won't be more than that many instances.
Now, when you register your table view cells (see above) you should probably subclass UITableViewCell and then either set your layout constraints in code (maybe override init(style:reuseIdentifier:)) or you can create a .nib file and use that. Then you can set up the constraints in Xcode through the graphical UI.
Let me know if you have any questions.
I found a good function to make cell dynamic size
func dynamicHeight(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
Call this function in heightForRowAtIndexPath
I hope it works.
Try this:-
First add this two functions in your class
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Second in order to make the UITableViewAutomaticDimension work make sure you have added all the left, right, bottom, and top constraints relative to cell container view.

Resources