Constraints in UITableViewCell not being calculated - ios

I have the following cell with constraints being setup programmatically:
class RadioButtonCell: UITableViewCell {
static let identifier = "RadioButtonCell"
let radioButton = RadioButton()
let labelTitle = UILabel()
private var didUpdateConstraints = false
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupSubViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSubViews() {
radioButton.translatesAutoresizingMaskIntoConstraints = false
labelTitle.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(radioButton)
contentView.addSubview(labelTitle)
}
override func updateConstraints() {
super.updateConstraints()
if !didUpdateConstraints {
radioButton.anchor(leading: contentView.leadingAnchor, padding: UIEdgeInsets(top: 0, left: Constants.UI.defaultMarginX2, bottom: 0, right: 0))
radioButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
labelTitle.anchor(leading: radioButton.trailingAnchor, padding: UIEdgeInsets(top: 0, left: Constants.UI.defaultMarginX2, bottom: 0, right: 0))
labelTitle.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
didUpdateConstraints = true
}
}
}
The anchor method is just a helper method to add constraints. The constraints are setup correctly (no issues with Autolayout).
Then in the cellForRowAtIndexPath method I create the cell like so:
guard let cell = tableView.dequeueReusableCell(withIdentifier: RadioButtonCell.identifier, for: indexPath) as? RadioButtonCell else { return UITableViewCell() }
radtioButtonController.addButton(cell.radioButton)
cell.labelTitle.text = "test"
return cell
This creates me the following layout in the tableView (which is obviously wrong):
If I move the setup of the constraints to the setupSubViews() method, the layout is correct:
class RadioButtonCell: UITableViewCell {
static let identifier = "RadioButtonCell"
let radioButton = RadioButton()
let labelTitle = UILabel()
private var didUpdateConstraints = false
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupSubViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupSubViews() {
radioButton.translatesAutoresizingMaskIntoConstraints = false
labelTitle.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(radioButton)
contentView.addSubview(labelTitle)
radioButton.anchor(leading: contentView.leadingAnchor, padding: UIEdgeInsets(top: 0, left: Constants.UI.defaultMarginX2, bottom: 0, right: 0))
radioButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
labelTitle.anchor(leading: radioButton.trailingAnchor, padding: UIEdgeInsets(top: 0, left: Constants.UI.defaultMarginX2, bottom: 0, right: 0))
labelTitle.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
}
Why is this like that? I thought we should setup constraint in the updateConstraints method...
Thank you for an answer :)
Edit I found out, that it works with updateConstraints when I call cell.updateConstraintsIfNeeded() or cell.setNeedsUpdateConstraints() in cellForRowAtIndexPath. Why do we need to tell the cell to calculate the constraints again? We do not need to do that when adding the constraints with IB...

Well, in your first example you haven't just added the constraints to your UI elements in the init. You just set up your sub views.
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupSubViews()
// No explicit constraint setup happened
}
To setup your constraints you've overridden the override func updateConstraints() { ... } method. Let's take a look at the official documentation from Apple.
In short, your override will be in effect when you notify the system that you need constraints' update. As a result you need to explicitly inform the system by invoking setNeedsUpdateConstraints() or updateConstraintsIfNeeded().
Let's look at your second example. You embedded the constraints setup inside your private func setupSubViews(){ ... }. So at the time this function gets called, your constraints are ready to be applied. No system call needed.

Related

Custom UITableViewCell with title - constraints

I have a custom UITableViewCell so that
class CustomCell: UITableViewCell {
private var someCustomView = UIView()
init(style: UITableViewCell.CellStyle, reuseIdentifier: String?, text: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupContraints()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setupContraints() {
contentView.addSubview(someCustomView)
self.someCustomView.frame.size = CGSize(width: 100, height: 100)
NSLayoutConstraint.activate([
self.someCustomView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
self.someCustomView.topAnchor.constraint(equalTo: contentView.topAnchor)
])
setNeedsLayout()
layoutIfNeeded()
}
The issue I am having is around the NSLayoutConstraint - Ignore the specifics of this as I haven't yet setup the constraints I need, however the view only setting up the frame and not constraints. Any help? Thank you!
You need to set translatesAutoresizingMaskIntoConstraints to false before adding constraints as follows:
someCustomView.translatesAutoresizingMaskIntoConstraints = false

Swift 4: Could not load NIB in bundle: 'NSBundle' for custom UITableViewCell (no IB)

I have built my own UITableViewCell (entirely by code, no interface builder) and I'm getting the fallowing exception:
[...] NSInternalInconsistencyException', reason: 'Could not load NIB in
bundle: 'NSBundle [...]
Im fairly certain I don't need to use awakeFromNib() (see code) but this is the only way I got it working when passing it as "non-reusable"
// this works
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath [...] {
return myCustomCell()
// this fails
tableView.register(MyCustomCell.self, forCellReuseIdentifier: "foo")
//throws immediately after this line: "[...] Could not load NIB in bundle"
My custom cell:
class TextValueTableViewCell: UITableViewCell {
let textField: UITextField = UITextField(
frame: CGRect(x: 30, y: 5, width: 350, height: 25)
)
let label = UILabel(frame: CGRect(x: 30, y: 5, width: 350, height: 25))
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.awakeFromNib();
}
override func awakeFromNib() {
super.awakeFromNib()
self.label.text = "oh"
self.contentView.addSubview(self.label)
self.contentView.addSubview(self.textField)
self.textField.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -25).isActive = true
self.textField.translatesAutoresizingMaskIntoConstraints = false;
self.textField.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor, constant: 0).isActive = true
self.textField.widthAnchor.constraint(equalTo: self.label.widthAnchor, constant: 0).isActive = true
self.textField.textAlignment = .right;
self.textField.placeholder = "Account Name"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
}
As I said, I have not used the interface builder (and I'm not planing on it) so I'm not sure why it would try to load something from the bundle. Specifically, wouldn't I have to register it as a NIB then?
tableView.register(<#T##nib: UINib?##UINib?#>, forCellReuseIdentifier: <#T##String#>)
class TextValueTableViewCell: UITableViewCell {
let textField: UITextField = UITextField(
frame: CGRect(x: 30, y: 5, width: 350, height: 25)
)
let label = UILabel(frame: CGRect(x: 30, y: 5, width: 350, height: 25))
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setupCell()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupCell()
}
func setupCell() {
label.text = "oh"
contentView.addSubview(label)
contentView.addSubview(textField)
textField.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -25).isActive = true
textField.translatesAutoresizingMaskIntoConstraints = false;
textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0).isActive = true
textField.widthAnchor.constraint(equalTo: label.widthAnchor, constant: 0).isActive = true
textField.textAlignment = .right;
textField.placeholder = "Account Name"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
For example If your using a UIViewController class name as TextPicker
Then u need to use like Bellow Code
let bundle = Bundle(for: TextPicker.self)
super.init(nibName: "TextPicker" , bundle: bundle)
If You are using TableView with Xib in other project as Framework
tableView.register(UINib.init(nibName: "TextPickerCell", bundle: bundle), forCellReuseIdentifier: "TextPickerCell")
let bundle = Bundle(for: TextPicker.self)

Unable to satisfy Bottom Constraint of UILabel to ContentView of Dynamic UITableViewCell

All implementations done programmatically(No Storyboards!).
I have created dynamic tableView with UITableView.automaticDimension with all custom Cells. All Cells are working fine and this one too but this one is generating Constraint warnings in debug.
Though, the layout is perfect and displaying as it should.
It has just one label and 3 CAShapeLayers. Below is the implementation Code:
//BadCustomTableViewCell
override func layoutSubviews() {
super.layoutSubviews()
setUpViews()
setUpConstraints()
}
let badLabel:UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 40, weight: UIFont.Weight.light)
label.text = "899"
label.textColor = .black
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setUpViews() {
contentView.addSubview(badLabel)
}
func setUpConstraints() {
badLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
badLabel.layoutIfNeeded()
let safeMargin:CGFloat = badLabel.frame.size.width - 15
badLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: safeMargin).isActive = true
badLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -safeMargin).isActive = true
}
Everything is fine according to me, but I don't what's breaking the constraints!
The log shows this -
(
"<NSLayoutConstraint:0x282729720 V:|-(77.6667)-[UILabel:0x103179a60'65.89 %'] (active, names: '|':UITableViewCellContentView:0x10317a150 )>",
"<NSLayoutConstraint:0x282729950 UILabel:0x103179a60'65.89 %'.bottom == UITableViewCellContentView:0x10317a150.bottom - 77.6667 (active)>",
"<NSLayoutConstraint:0x2827297c0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x10317a150.height == 48.6667 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x282729950 UILabel:0x103179a60'65.89 %'.bottom == UITableViewCellContentView:0x10317a150.bottom - 77.6667 (active)>
Any Idea what I might be missing here?
Lower the bottom constraint
let con = badLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -safeMargin)
con.priority = UILayoutPriority(999)
con.isActive = true
and add the setup & constraints inside
override init(style: UITableViewCell.CellStyle,reuseIdentifier: String?)
as
override func layoutSubviews()
is called multiple times
`
Although it is quite late, I'm posting this answer anyway. Most of the times, you don't need to re-setup your constraints and re-add your subviews to your super/parent view. So your setUpViews() and setUpConstraints() should be indeed inside override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) like so:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
setUpConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Also, you don't need (most of the times, again), at least for me, to setup priorities to your constraints. As much as possible, solve the constraints issues/warnings without lowering priorities.
badLabel.layoutIfNeeded() isn't needed too, unless you're actually modifying some constants of your constraints or re-making your constraint. Well it makes sense to use such line if you REALLY want to setup everything in your layoutSubviews()
This whole class similar to your cell worked for me:
class BadCustomTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
setUpConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let badLabel:UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 40, weight: UIFont.Weight.light)
label.text = "899"
label.textColor = .black
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setUpViews() {
contentView.addSubview(badLabel)
}
func setUpConstraints() {
badLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
let safeMargin:CGFloat = badLabel.frame.size.width - 15
badLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: -safeMargin).isActive = true
badLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: safeMargin).isActive = true
}
}

Must call a designated initializer of the superclass 'UITableViewCell'

let bubbleView : UIView = {
let view = UIView()
view.backgroundColor = blueColor
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 16
view.layer.masksToBounds = true
return view
}()
let messageImageView : UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.cornerRadius = 16
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFill
return imageView
}()
init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// getting error in like "super.init(frame: frame)" as Must call a designated initializer of the superclass 'UITableViewCell'please help me in sorting this problem thanks in advance...
I guess the code that you provided is from UITableViewCell type class. So in the initializer you should call designed initializer for this class. Not from UIView
The designated initializer for UITableViewCell class is
init(style: UITableViewCellStyle, reuseIdentifier: String?)
So in you class you should override this initializers:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
From the docs for init(style: UITableViewCellStyle, reuseIdentifier: String?):
This method is the designated initializer for the class.
The super initializer you're calling is for UIView, not UITableViewCell.

Custom cell with dynamic subviews stack

I have a tableView populated by an array of 'Post'. On this tableView I register a class 'PostCell'. This PostCell isn't supported by any nib since I want to dynamically compose it with subviews.
Suppose two subviews, a top yellowView and a bottom redView, each subclass of UIView, are stacked on a PostCell.
My issue is that the below code returns this:
ViewController
override func viewDidLoad(){
super.viewDidLoad()
tableView.delegate = self
tableView.datasource = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
tableView.registerClass(PostCell.self, forCellReuseIdentifier: "PostCell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let post = posts[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell") as! PostCell
cell.configureForData(post)
return cell
}
PostCell
class PostCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fun configureForData(post: Post){
let yellowView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 40))
yellowView.backgroundColor = UIColor.yellowColor()
yellowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(yellowView)
let redView: UIView = UIView(frame: CGRect(x: 0, y: 40, width: contentView.frame.width, height: 40))
redView.backgroundColor = UIColor.redColor()
redView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(redView)
}
override func layoutSubviews() {
super.layoutSubviews()
let height = NSLayoutConstraint(item: contentView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 80)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(height)
}
}
EDIT 1: This fixes the layout
class PostCell: UITableViewCell {
let yellowView = UIView()
let redView = UIView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?){
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fun configureForData(post: Post){
yellowView.backgroundColor = UIColor.yellowColor()
yellowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(yellowView)
redView.backgroundColor = UIColor.redColor()
redView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(redView)
layoutIfNeeded
}
override func layoutSubviews() {
super.layoutSubviews()
let height = NSLayoutConstraint(item: contentView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 80)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(height)
yellowView.frame = UIView(frame: CGRect(x: 0, y: 0, width: contentView.frame.width, height: 40))
redView.frame = UIView(frame: CGRect(x: 0, y: 40, width: contentView.frame.width, height: 40))
}
Try to use cell.layoutIfNeeded() after cell.configureForData(post) in your tableView:cellForRowAtIndexPath function. But I think it's better to calculate cell height and return it in tableView:heightForRowAtIndexPath function.
Also you have problem with cell width:
It's because you got contentView.frame.width before id correctly laid out. You need to update frames in each layoutSubviews call or use constraints.

Resources