Swift UITableViewCell Subview Layout Updating Delayed - ios

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
}

Related

Update constraint inside layoutSubviews() of UITableViewCell

I need to update a constraint's value inside a UITableViewCell, which should trigger resizing of the cell. I need to know the correct frame before I can update the constraint's value, however having my code inside layoutSubviews() does not resize the cell. The subview which has the constraint attached to it grows, but cell stays the same initial height, cutting off the view.
My setup for the purpose of demonstration is very simple, I have a single UIView pinned to the edges of the cell's contentView, and a height constraint added to it. I expect the cell to resize when the height constraint of the view is changed.
override func layoutSubviews() {
super.layoutSubviews()
heightConstraint.constant = 500
}
Any ideas on why this is not working?
There are a couple quirks with changing cell height in layoutSubviews().
Typically, when a cell modifies its internal layout (constraints, etc), the cell must tell the table view to redraw itself.
In your case, the cell's layoutSubviews() is being called after the table view has determined the cell's height.
Couple ways around this, if you really do need to do that in layoutSubviews():
1 - call relaodData() in viewDidAppear()
2 - force the cell layout inside cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
// set your cell properties here as usual - labels, images, etc...
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell
}

Dynamic UITableViewCell not resizing according to the content

Senario A:
If I set the label content in cellForRowAtIndexPath, the cell correctly get resized.
Senario B:
If I change the text content in custom action in cell, the cell sized does not get changed.(I do call setNeedsLayout + layoutIfNeeded)
How to fix this?
EDIT:
1) I have set,
myTableView.estimatedRowHeight = 71.0
myTableView.rowHeight = UITableViewAutomaticDimension
2) I have correctly added auto layout constraints.
I was running into this issue, and my problem was that I was constraining the content to self (the UITableViewCell) and not to self.contentView (the contentView OF the cell). Hope this helps someone else who has built their cells all in code!
In my case, the cell's custom size was enabled:
After you change the text of the cell, just reload that particular cell or simply call mainTableView.reloadData().
To reload that cell-
//indexPath is indexPath of cell you just changed label of
mainTableView.reloadRows(at: indexPath, with: .automatic)
In my case, in the same cell I had an imageView in the top left corner with a "center vertically in container" constraint, and a "top space container" constraint.
Obviously to satisfy this two constraint the cell must have an height equal to:
(height of the imageView / 2) + (length of the top space container constraint).
This height is not enough to fit the label text, so my label had only 1 line visible.
After I have deleted the imageView top constraint all went to the right place, in my case i wanted the image to be centered, if the image had to stay in the top left corner I had to take off the "center vertically in container" constraint.
I hope this can help someone.
First of all, I don't specifically know what was your action on UITableViewCell. So, I assume I do that in UITableViewCell selection.
The below answer only work on iOS 9 and above
But, for some reason, it failed to do it in iOS 8 until it scroll. So, I will update the answer for iOS 8.
I have seen you have used UITableView's estimatedRowHeight and rowHeight at your project. So,
Please check the following
Make sure UITableView's estimatedRowHeight and rowHeight include inside viewDidLoad()
Make sure your UILabel lines set to 0
Make sure there is no constraints about height for your UILabel and let the constraints be like that :
If there are other component also included, make sure only bottom and top space constraints included or top space to container margin and bottom space to container margin.
Every time that you want to update the cell, you have to reload tableView no matter what your current situation will be.
So, don't say anything yet before you try this sample project, #Rikh answer still work. May be you are going in wrong direction. Here's the solution. Please do as I said steps by steps and let me know if that didn't work out. You might need to share your sample project which is causing.
Sample Demo - DynamicCellDemo
UPDATE for iOS 8 : update the following code for iOS 8 users
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 9, *) {
// do nothing
} else {
tblDynamic.reloadData()
}
}
what you can do is set the AutoLayout constraints for the label in present in the cell from all the sides that is from Top, Bottom, Leading and Trailing. Then add the following UITableViewDelegate method to your class.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 500
}
This will do the job. As now content in table view cell automatically adjusts the height of the cell.
Try this if it works:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Add the following in your viewDidLoad()
-(void)viewDidLoad{
[super viewDidLoad];
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
}

How to resize custom UITableViewCell nib file to fit tableView?

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.

Settings UITableViewCell Heights With Multiline UIButtons in Auto Layout

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.. :)

Custom UITableViewCell does not size properly when Accessory is added

I'm having trouble getting a custom UITableViewCell to size properly when an Accessory is set. Why does the Accessory blow the cell sizing up on the initial display? When I scroll, with the accessory set, the sizing corrects itself - however, the initial view is not sizing correctly.
I've watched the WWDC14 (what's new in tableview) video several times and I've read many stackoverflow questions and tried many solutions. I think I have most of the problem solved - cells do resize for dynamic text - but I'm stumped on this strange initial behavior. I am running XCode 6.1.1 deploying to iOS 8.1.
I'm using a storyboard. I have a UITableViewController and custom UITableViewCell. I define constraints in the storyboard and there are no constraint warnings and I see no constraint messages in the console at runtime.
In my UITableViewController viewDidLoad()
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
with override
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) as TableViewCell
cell.configure(titles[indexPath.row])
return cell
}
This is my entire custom UITableViewCell
class TableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configure(title: NSString) {
titleLabel.text = title
titleLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.preferredMaxLayoutWidth = frame.width
}
}
This is what it looks like when Accessory None is set on the cell
This is what it looks like when I set Accessory Disclosure Indicator on the cell
Again, when I scroll with the accessory set the sizing corrects itself. I've tried adding a vertical constraint to the cell both in storyboard and also by adding all constraints programmatically with no success. Thank you for any thoughts.
I've seen this problem too with storyboard designed self-sizing cells. Try adding self.layoutIfNeeded() to an override of didMoveToSuperview in your cell class. I don't know if this is a bug, or we're just missing something that we're supposed to be doing.

Resources