My app currently loads like this. As you can see this is not ideal b/c the cell does not fill the entire cell. Also if you notice on the very left of every cell, the white separator line stops before the end of the cell.
I'm using a nib file DayofWeekSpendingTableViewCell.xib to customize my tableview cell.
Dimensions of UILabel dayOfWeek in nib file
Dimensions of UILabel totalAmountSpent in nib file
I have a UITableViewController SummaryTableViewController where I load the nib file. In the method tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) I've attempted to set the frame of the two labels so they would take up the width of the view, but that doesn't help b/c my app still loads like this.
class SummaryTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
dayOfWeek = [ .Mon, .Tue, .Wed, .Thu, .Fri, .Sat, .Sun]
totalSpentPerDay = [0, 7.27, 0, 0, 39, 0, 0]
// Create a nib for reusing
let nib = UINib(nibName: "DayofWeekSpendingTableViewCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "nibCell")
self.tableView.separatorColor = UIColor.whiteColor()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell = tableView.dequeueReusableCellWithIdentifier("nibCell", forIndexPath: indexPath) as! DayOfWeekTableViewCell
let day = dayOfWeek[indexPath.row]
let height = CGFloat(55)
let dayOfWeekWidth = CGFloat(80)
cell.dayOfWeek.text = day.rawValue.uppercaseString
cell.dayOfWeek.frame = CGRectMake(0, 0, dayOfWeekWidth, height)
cell.dayOfWeek.backgroundColor = colorOfDay[day]
cell.totalAmountSpent.text = "$\(totalSpentPerDay[indexPath.row])"
cell.totalAmountSpent.frame = CGRectMake(cell.dayOfWeek.frame.maxX + 1, 0, view.bounds.width - dayOfWeekWidth, height)
cell.totalAmountSpent.backgroundColor = colorOfDay[day]
return cell
}
}
If anyone could tell me how I could make the custom UITableViewCell nib file to fit the view I would be very grateful!
So, a couple of things I see. Firstly, it looks like your cell nib could use some AutoLayout constraints. Your nib is probably something like a 320px width. At run time, your cell's content view is actually stretching out to fill the new width of a larger device, but the green views that you placed are just staying put in their 320px configuration. You could test this by changing the color of the content view and seeing that color appear in the simulator. I sorta reproduced your cell here:
The pink view has a fixed width and is placed up against the top, left, and bottom of the content view. The blue view is 4 points to the right of the pink view to give that white margin in the middle. It is placed up against the top, right, and bottom of the content view. So as the cell's content view resizes, the AutoLayout constraints will stretch the blue view such that its right edge stays flush against the right edge of the content view.
For the edge insets, firstly set the edge insets on the table and on each cell. You can do that in the storyboard/nib or like this in code:
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "yubnub", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "yub")
tableView.separatorInset = UIEdgeInsets.zero
tableView.layoutMargins = UIEdgeInsets.zero
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return tableView.dequeueReusableCellWithIdentifier("yub", forIndexPath: indexPath)
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.layoutMargins = UIEdgeInsets.zero
}
}
Apply following solutions,
1) First give full autoresizing constraints to your tableview from the size inspector in storyboard.
2) Now move to the your nib file and select tableViewcell and give full constraints to it.
3) Now select left side label and give it left & up constraints to it and give right, up and middle constraints to right side label.
Now don't forget to implement heightForRowatIndexPath delegate method in your view controller and mention same height of your nib file which is in your custom xib.
I Had the same mistake, and it was because in storyboard the content of my tableView was Static Cell. I change it to Dynamic Prototype and it solved the problem.
Related
I have two TableViews, when I designed the original I designed it using the prototype cell in the storyboard, to make it reusable I tried to pull it out into a .xib and load that instead. When it is loaded from cellID.xib however it loses all the constraints at runtime, and everything is layered on top of each other.
TableViewController
let cellIdentifier = "cellID"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 300
}
Prototype cell in Storyboard (Used to work)
Cell when copy pasted to XIB
Constraints
XIB View Hierarchy
The Problem
In your question title, you asked how to solve an issue where "Swift TableViewCell xib doesn't have constraints." Additionally in the bounty, you specify that the answer should be:
An adequate answer showing why constraints are lost when moved to a
xib, when they exist in the xib, and how to address this problem.
From my understanding, your question has two parts:
Issue where a custom TableViewCell xib does not have constraints
Why constraints are lost when copy/pasted from Storyboard prototype to a xib
For issue #1, I used almost all of the same constraints as you had displayed in your screenshot in a custom cell xib and it worked. I will explain in more detail below.
For issue #2, I found that constraints are not lost. You may need to share your project so that others can replicate the issue you are having. I copy/pasted a Storyboard prototype cell with similar constraints over to a xib and I did not have any issues with constraints. So I can confirm that the copy/paste feature does work.
Solution Demo
I created the following demo to test your issue. Refer to the screenshot below. The tableView contains six cells. The 1st and 2nd are the same and have reuse identifier MyCell. The 3rd and 4th are the same and have reuse identifier MyCopyPasteCell. The 5th and 6th are the same and have reuse identifier MyPrototypeCell.
MyCell exists in a xib and uses nearly all the same constraints that you showed in your screenshot. As you can see, there are no constraints issues. MyCopyPasteCell uses similar constraints and was copy/pasted from a Storyboard prototype over to a xib. MyPrototypeCell is the original prototype that was copied. These last four cells look exactly the same even though the prototype was copied over to the xib. The constraints carried over from prototype to xib without an issue.
Constraints
In the code below, I list all the constraints that you showed in your screenshot for your ContentView. (Note that I also implemented the height=20, aspect=1:1, and height=75 constraints, although they are not listed below.) Two constraints are commented out since I did not use them. I also added one more constraint to replace another unused one.
// bottomMargin = Profile Image View.bottom + 188.5
bottomMargin = Profile Image.bottom + 17
Profile Image View.top = Username Label.top
Profile Image View.leading = leadingMargin + 2
Profile Image View.top = topMargin + 20
Username Label.leading = Content Text View.leading
// Username Label.top = topMargin + 20
Username Label.trailing = Content Text View.trailing
Username Label.leading = Profile Image View.trailing + 15
trailingMargin = Username Label.trailing + 10
Content Text View.top = Username Label.bottom + 5
Content Text View.leading = leadingMargin + 92
bottomMargin = Content Text View.bottom + 20
The first commented constraint // bottomMargin = Profile Image View.bottom + 188.5 did not make sense as it would separate the bottom of the image from the bottom of the cell by 188.5. This also did not match your Prototype cell in Storyboard (Used to work) screenshot at all. I replaced it with bottomMargin = Profile Image.bottom + 17, which just replaces the 188.5 with 17.
The second commented constraint // Username Label.top = topMargin + 20 (which separates username and topMargin by 20) can technically work. However, its functionality is redundant to Profile Image.top = topMargin + 20 (which separates Profile Image and topMargin by 20) and Profile Image View.top = Username Label.top (which sets the Profile Image and Username to the same top separation i.e. by 20).
MyTableViewController
The following is my view controller. Basically I create three sections with two cells/rows per section. MyCell and MyCopyPasteTableViewCell are from a xib and are registered in the viewDidLoad(). MyPrototypeCell is from the Storyboard and is not registered in viewDidLoad().
// MyTableViewController.swift
import UIKit
class MyTableViewController: UITableViewController {
let myCell = "MyCell"
let myCopyPasteCell = "MyCopyPasteTableViewCell"
let myPrototypeCell = "MyPrototypeCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: myCell, bundle: nil), forCellReuseIdentifier: myCell)
tableView.register(UINib(nibName: myCopyPasteCell, bundle: nil), forCellReuseIdentifier: myCopyPasteCell)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 300
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: myCell, for: indexPath)
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: myCopyPasteCell, for: indexPath)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: myPrototypeCell, for: indexPath)
return cell
}
}
}
Github link
https://github.com/starkindustries/TableViewCellConstraintsTest
Problem and Solution
You may have disabled the Autolayout for your XIB to turn it ON go to File Inspector from Top Right Menu
You Can Follow the Simple Steps to Create Cell from Xib 😊
1. Create UITableCell with .xib ( ✅ also create Xib file)
2. Give the cell identifier
3. Layout Constraints
🔸 ImageView give contarints from Top , Left and Bottom
height constant and Aspect ratio [i have set Border to show the frame]
🔸 UIlable for the title lable give constraints from three sides
from Top , Left and Right NumberOflines=0 (🚫 Don't give height constant -
⚠️ if gives warning go ahead and update the constraints)
🔸 UIlable for the Descriptions lable give constraints from all four sides also NumberOflines=0
(⚠️ This will give you an error go to suggestion and change the priority of UIlable constraints)
see the screenshot below
changing priority
than again update contraints
4. Code in UIViewController
#IBOutlet var tableView: UITableView!
var cellIdentifier="cell"
var cellXibName = "MyTableCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: cellXibName, bundle: nil), forCellReuseIdentifier: cellIdentifier)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 320
}
func numberOfSections(in tableView1: UITableView) -> Int {
return 3
}
func tableView(_ tableView1: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell :MyTableCell = tableView1.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! MyTableCell
return cell
}
And Finally the Output is perfect as you want
OUTPUT
I hope this will work for you...... please let me know if this worked for you or not.
Solution :
I was achive solution to your problem. hope it might be help to you
I use uiview to clips all cell elements.
And Cell Design
Code :
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 100
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.register(UINib(nibName: "TestCell", bundle: Bundle.main), forCellReuseIdentifier: "TestCell")
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController : UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as! TestCell
cell.selectionStyle = .none
return cell
}
}
Result :
It seems that the cell height is the problem, right? Because of that the constraints can't do their work correctly.
You need this override func when you work with XIB files:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 300
}
However your constraints must be wrong or let us say they are not perfect. I know they are working with the prototype cells in storyboard but that don't make them 100% perfect working right?
Your UIImageView has no height and width. You should try to set that with some constraints. (or you set the width and use the Aspect Ratio constraint)
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
I have a subclass of UITableViewCell and a subclass of UITableViewController. I'm popualating the tableview with my custom cells.
I created a UITableViewCell in my storyboard, made its class my custom UITableViewCell Swift file, and gave it an identifier. Then I create cells in the controller with:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myIdentifier", forIndexPath: indexPath indexPath)
return cell
}
In my custom cell (on the storyboard) I have a UIImageView with the following constraints:
Image View Constraints
The constant for the top space constraint is 0, which I thought means the ImageView top would be flush with the cell top.
However, the images in my cell are pushed down a bit:
UITableView with custom cell
The white is just the table section header, that's working perfectly. However, you can see the orange gap that appears above the image (that orange is the background color of the cell), and I don't want that there.
I tried setting the image frame in the custom cell's awakeFromNib:
#IBOutlet weak var img: UIImageView!
override func awakeFromNib() {
img.frame = CGRectMake(0, 0, img.frame.width, img.frame.height)
}
But this had no effect, so I tried setting the frame in layoutSubviews:
override func layoutSubviews() {
img.frame = CGRectMake(0, 0, img.frame.width, img.frame.height)
}
This worked, but only after scrolling down and back up on the TableView. The first two cells have the orange gap, but if you scroll to the third one and back up, then the gap is gone.
Does anyone know why this is happening? How can I make the imageview be positioned at (0, 0) right away instead of only after scrolling down and back up?
When I try to reproduce your situation and drag the imageView to align to the top of the cell the Xcode suggested constraint is -8, and it works fine like that but if i set it to 0 I get the background color like your problem, try setting the top constraint to -8 or whatever Xcode is suggesting.
I'm not very familiar with AutoLayout so I can't clarify why Xcode wants to set it to -8
Try to set the height of tableViewCell through the following code:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UIScreen.mainScreen().bounds.width
}
I have a Table View called todoTableView with cells that created by the user.
Each cell has Text View.
I want to change the height of the cell by the content of the Text View.
This is what I tried:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath:indexPath) as! TableViewCell
cell.bounds.size.height = cell.textView.bounds.size.height
return cell
}
Bound Your textview with cell from all sides using marginal constraints.(Leading, Trailing, Top and Bottom constraints)
Disable textView Scrolling
In viewDidLoad() add the following.
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
This will make your cell size according to your textview content size.
Have a look at result :
You don't need to write heightForRowAtIndexPath.
Irfan's answer didn't work for me.
It works after I go to Size Inspector and check "Automatic" for "Row Height".
You should also implement heightForRowAtIndexPath:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return // Calculate your custom row height here.
}
I have a UITableView with custom UITableViewCells, each has a UIButton inside. I'm setting buttons' titles from an array, so the size of the buttons change according to the title. I need to return correct height based on the inner button's size in heightForRowAtIndexPath event.
Since I'm using auto layout, I've created an outlet for the button's height constraint and I'm updating it in the cell's layoutSubviews() event like this:
class CustomCell: UITableViewCell {
/* ... */
override func layoutSubviews() {
super.layoutSubviews()
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
}
}
Then I return the height based on the button height and top-bottom margins like so:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds))
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell.myButton!.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (cell.topMarginConstraint!.constant * 2) /* top-bottom margins */ + 1 /* separator height */
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
return cell
}
On the first launch, there seems to be no problem. However, after I begin scrolling, then the height of some rows seem to be mistaken. When I get back to the top, I see that previous cell heights get to be broken as well.
When I googled for similar problems, issue seems to be about reusable cells, though I was unable to find another way to calculate the height. What can be done to reuse cells correctly or getting the correct height, perhaps by another method?
More info and source code:
Constraints set by IB like this:
Here's the cells on the first launch:
After some scrolling:
Full code of the project can be found on Github.
According to this
Configure tableView as
func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
}
Call it on your viewDidLoad method
Than configure your uibutton height constraint to be greater then or equal.
Override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat where you can place your estimation height code
First off, it's better if you perform constraint updates in func updateConstraints() method of UIView. So instead of
override func layoutSubviews() {
super.layoutSubviews()
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
}
I would do
override func updateConstraints() {
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
super.updateConstraints()
}
Note that you should call the super implementation at the end, not at the start. Then you would call cell.setNeedsUpdateConstraints() to trigger a constraint update pass.
Also you should never directly manipulate the cell bounds the way you are doing in heightForRowAtIndePath: method, and even if you are completely sure that manipulating directly is what you want, you should manipulate cell.contentView's bounds, not the cell's bounds. If you are looking to adjust the cell height dynamically with respect to the dimensions of the content, you should use self sizing cells. If you need to support iOS 7, then this answer tells you how to achieve that behaviour with autolayout only (without touching the bounds etc).
To reiterate the answer, you should do:
func viewDidLoad() {
self.dummyCell = CustomCell.init()
// additional setup
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
self.dummyCell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
self.dummyCell.layoutIfNeeded() // or self.dummyCell.setNeedsUpdateConstraints() if and only if the button text is changing in the cell
return self.dummyCell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
}
Please know that the answer I linked to outlines a strategy to get the cell height via autolayout, so only writing the code changes I proposed won't be enough unless you set your constraints in a way that makes this solution work. Please refer to that answer for more information.
Hope it helps!
First of all, remove the height constraint of button and bind it to top and bottom with cell.
Then, in your cell' height, calculate height of the text based on the width and font of button. This will make the cell's height dynamic and you wont need height constraint anymore.
Refer the link below to get the height of text:
Adjust UILabel height to text
Hope it helps. If you need help further or understanding anything, let me know.. :)