I want to create a round UIView (myView) inside a UITableViewCell
Prior to iOS 10 I was using func awakeFromNib() to do so like that:
class MyCell: UITableViewCell {
#IBOutlet var myView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
print(myView.bounds.height)
myView.layer.cornerRadius = myView.bounds.height/2
myView.layer.masksToBounds = true
}
}
myView height and with are set to 30 via an AutoLayout constraint in my storyboard.
But print(myView.bounds.height) show 1000.0 in the console.
So the corner radius instruction set a radius to 500 and myView disappear completely.
Is the UITableViewCell life cycle change in iOS 10 ?
Is the function where the view.bounds are set change in iOS 10 ?
How can do a set a cornerRadius to half my view height in iOS 10 ?
Here is a minimal projet to reproduce the issue
To have a result close to what I want I need to add in my UITableViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
But I'm quite sure this is not optimal ...
I had the same problem and found two ways to fix it. Both enforce the cell to layout subviews
First solution:
override func awakeFromNib() {
super.awakeFromNib()
self.setNeedsLayout()
self.layoutIfNeeded()
}
Second way:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell
}
I had exactly the same issue, with the same size of 1000.0 when asking for the bounds of a subview.
As the frame or bounds values are not reliable anymore I had to declare an IBOutlet for the height constraint defined in my storyboard and use it to set the corner radius of my subview.
class MyCell: UITableViewCell {
#IBOutlet var myView: UIView!
#IBOutlet var myViewHeightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
print(myView.bounds.height)
myView.layer.cornerRadius = myViewHeightConstraint.constant / 2
myView.layer.masksToBounds = true
}
}
This will enable you to remove the reloadData call which is really not optimal as you said.
My observation is that iOS 10 / TVOS 10 is not laying out auto layout based views before calling awakeFromNib, but is laying out views using autoresizing masks before calling the same method.
Removing auto layout constraints and using the new Autoresizing settings in storyboards solved it for me.
To me this makes Autolayout less reliable than autoresizing masks. I also 'feel' a performance improvement after switching out of Autolayout. I cannot say for sure though as I am on TVOS and the Core Animation instrument doesn't work for TVOS.
Related
I have a UITableView that looks like the following:
When a user changes the iOS text size settings, the layout changes.
Layout gets changed where the detail label gets pushed under the title, and the disclosure indicator gets very large. This happens with standard cell layout and custom cell layout with my own UILabel subviews.
I am not ready to support dynamic type, so is there a way to not allow iOS to change the layout of my UITableViewCells?
Many things have been made for iOS to assist with the Dynamic Type implementation in the table view cells ⟹ automatic cell sizing for instance.
I reproduced the initial problem in a blank project as follows:
With no extra code and only due to the iOS native implementation, notice that:
There's a reordering of the labels when the first accessibility step is reached.
The system accessory element size changes according to the Dynamic Type using.
There's an automatic row height resizing.
[...] is there a way to not allow iOS to change the layout of my UITableViewCells?
A solution may lead to the creation of your own custom table view cell as follows:
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var myTitle: UILabel!
#IBOutlet weak var myDetail: UILabel!
static let reuseIdentifier = "myReuseIdentifier"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpElementsAndConstraints()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpElementsAndConstraints()
}
private func setUpElementsAndConstraints() {
//Custom your labels and their constraints in here.
//For the demo, everything is hard coded within the Interface Builder.
}
}
Add your labels with the property adjustsFontForContentSizeCategory to false in order to keep their initial font size.
Create your own 'chevron image' that won't be resized as the system one:
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> MyTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyTableViewCell.reuseIdentifier,
for: indexPath) as! MyTableViewCell
let accessoryImageFrame = CGRect(x: 0.0, y: 0.0,
width: 40.0, height: 40.0)
let accessoryImageView = UIImageView(frame: accessoryImageFrame)
accessoryImageView.image = UIImage(named: "MyChevron")
cell.accessoryView = accessoryImageView
return cell
}
Define a static row height in the table view:
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat { return 50.0 }
Don't forget to set up your constraints that may be updated according to the text sizes settings thanks to the traitCollectiondidChange method... even if you don't want to use the Dynamic Type feature yet. 😉
Finally, I get this result that never changes whatever the text sizes in the settings: 🥳
Following this rationale, your UITableViewCell layout doesn't change when iOS larger text is used. 👍 (I know, my custom chevron isn't really nice 😁)
I need to support UITableViewAutomaticDimension (for dynamic height) with variations in the constraints: some need to be active, some not.
I setup the storyboard with aConstraint not installed, and bConstraint installed. I activate/deactivate them on need in tableView(_:cellForRowAt:).
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.estimatedRowHeight = 10
tableView.rowHeight = UITableViewAutomaticDimension
}
}
}
class MyTableViewCell: UITableViewCell {
#IBOutlet var aConstraint: NSLayoutConstraint!
#IBOutlet var bConstraint: NSLayoutConstraint!
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as! MyTableViewCell
cell.contentView.translatesAutoresizingMaskIntoConstraints = false
if indexPath.row % 2 == 0 {
NSLayoutConstraint.deactivate([cell.aConstraint])
NSLayoutConstraint.activate([cell.bConstraint])
} else {
NSLayoutConstraint.deactivate([cell.bConstraint])
NSLayoutConstraint.activate([cell.aConstraint])
}
return cell
}
}
Issue
The initial visible layout is ignoring all those activations/deactivations, and all the cells are identical to the original storyboard state.
You will notice that the correct constraints are only applied after scrolling.
Attempts
I did try without success some cell.setNeedsUpdateConstraints(); cell.updateConstraintsIfNeeded(); cell.layoutIfNeeded(); ...
Sample project shared on https://github.com/Coeur/dynamic-cell-height
Setup storyboard with both 'aConstraint' and 'bConstraint' installed, but put a lower priority on 'aConstraint' to remove warnings and it works :)
A couple of problems setting up constraints in your code.
1.
UILabel intrinsicContentSize will participate auto layout.
Auto layout system will create a width and height constraints based on intrinsicContentSize.
You explicitly set a height constraint of the label in xib file, causing an ambiguity, then added a vertical centre Y constraint to cover the problem.
2
If you want to try active or deactivate constraints, you may want to do that with a simple UIView.
3
If you want to do various height rows, take advantage of intrinsicContentSize, and set preferredWidth of UILabel.
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.. :)
I've scoured through all of the questions here regarding a similar issue and I still seem to have this problem no matter what I do. I'm on iOS 8.3.
You can find the full code at: https://github.com/DanielRakh/CellHeightTest.git
I have a cell that dynamically resizes based on the content of a UILabel that varies from single to multi-line text. Everything seems to be fine initially but when I start scrolling fast and quickly switch directions
I get a warning in the debugger:
2015-03-10 02:02:00.630 CellHeight[21115:3711275] Warning once only:
Detected a case where constraints ambiguously suggest a height of zero
for a tableview cell's content view. We're considering the collapse
unintentional and using standard height instead.
Here's how it looks in the simulator. I've marked the discrepancies in red:
Here's the simple setup in IB where I have a UILabel pinned top(11), bottom(11), leading(8), and trailing(8) with a custom cell at 45pts:
Here is my code. The cell is a custom subclass thats pretty much empty except for the IBOutlet for the label:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var arr = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for _ in 1...10 {
arr.append("This is an amount of text.")
arr.append("This is an even larget amount of text. This should move to other lines of the label and mace the cell grow.")
arr.append("This is an even larger amount of text. That should move to other lines of the label and make the cell grow. Crossing fingers. This is an even larger amount of text. That should move to other lines of the label and make the cell grow. Crossing fingers.")
}
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 45.0
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TstTableViewCell
cell.tstLabel.text = arr[indexPath.row]
return cell
}
}
EDIT: I've found that if I created the cells programmatically and set the constraints programmatically everything seems to work as intended. Weird. This seems to be an IB + AL issue.
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.