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

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
}
}

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

Set height for UIImageView in UITableViewCell using height / width values

I need to render an image within a UITableViewCell.
The API returns the original height and width of the image, so I believe I should be able to calculate the ratio. However I am unsure how to lay this out using autolayout.
final class ContentArticleImage: UITableViewCell {
var ratio: CGFloat? { // 1000 / 600 (width / height)
didSet {
guard let ratio = ratio else { return }
contentImageView.heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1 / ratio).isActive = true
}
}
private lazy var contentImageView = configure(UIImageView(frame: .zero), using: {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = .darkGray
$0.contentMode = .scaleAspectFit
})
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureUI()
}
required init?(coder: NSCoder) {
return nil
}
}
private extension ContentArticleImage {
func configureUI() {
addSubview(contentImageView)
NSLayoutConstraint.activate([
contentImageView.topAnchor.constraint(equalTo: topAnchor, constant: 12),
contentImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
contentImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
contentImageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12)
])
}
}
I tried something like the above, but I can an autolayout error
(
"<NSLayoutConstraint:0x600002885b30 V:|-(12)-[UIImageView:0x7ffe3551e170] (active, names: '|':OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage' )>",
"<NSLayoutConstraint:0x600002885c70 UIImageView:0x7ffe3551e170.bottom == OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.bottom - 12 (active)>",
"<NSLayoutConstraint:0x600002885ea0 UIImageView:0x7ffe3551e170.height == 0.548298*OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.width (active)>",
"<NSLayoutConstraint:0x6000028860d0 'UIView-Encapsulated-Layout-Height' OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.height == 251 (active)>",
"<NSLayoutConstraint:0x600002886080 'UIView-Encapsulated-Layout-Width' OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.width == 414 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600002885c70 UIImageView:0x7ffe3551e170.bottom == OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.bottom - 12 (active)>
Using the original height / width how should I calculate the height of an image in my cell?
EDIT
My cell is rendered using the following method:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CONTENT_CELL", for: indexPath) as! ContentArticleImage
cell.ratio = asset.width / asset.height
cell.selectionStyle = .none
return cell
}
What you need to do is deactivate your height constraint each time the cell is reused. Also, you need to set the height constraint equal to a multiple of the contentImageView width constraint, not the cell's width constraint. So your code should look something like this:
final class ContentArticleImage: UITableViewCell {
var heightConstraint: NSLayoutConstraint?
var ratio: CGFloat? { // 1000 / 600 (width / height)
didSet {
guard let ratio = ratio else { return }
heightConstraint?.isActive = false
heightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: 1 / ratio)
heightConstraint?.isActive = true
heightConstraint?.priority = .defaultHigh
}
}
lazy var contentImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.backgroundColor = .darkGray
imageView.contentMode = .scaleAspectFit
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configureUI()
}
}
Note that I'm setting the priority to .defaultHight just to silence a layout warning in the console.

Custom UITableViewCell - can't seem to add subview

I've created a custom UITableViewCell class as shown below (programmatically - no use of storyboards for this):
import UIKit
class MainGroupCell: UITableViewCell {
var groupLabel : UILabel {
let label = UILabel()
label.textColor = .black
label.text = "Test Group"
label.font = UIFont(name: "candara", size: 20)
return label
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(groupLabel)
groupLabel.snp.makeConstraints({make in
make.center.equalTo(self.contentView)
})
}
required init?(coder aDecoder: NSCoder){
fatalError("init(coder:) has not been implemented")
}
}
And for some reason, I'm hitting the error that contentView and groupLabel are not in the same view hierarchy, but they are - I've added groupLabel as a subview to contentView as you can see. Any reason for hitting this error? I gave it a shot with regular Atuolayout API as well, instead of SnapKit, and no such luck. Feel like this might be a small mistake I'm missing. I've also attempted the equalToSuperview constraint, rather than what I have shown above, but as expected it also throws the same error - groupLabel's superview returns nil.
Error:
Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x280870b80
"UILabel:0x105e79fa0'Test Group'.centerX"> and <NSLayoutXAxisAnchor:0x280870a00
"UITableViewCellContentView:0x105fa16a0.centerX"> because they have no common ancestor.
Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
Try this ,
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
addSubview(groupLabel)
groupLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
groupLabel.leadingAnchor.constraint(equalTo: leadingAnchor,constant: 16),
groupLabel.topAnchor.constraint(equalTo: topAnchor, constant: 16),
groupLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
groupLabel.bottomAnchor.constraint(equalTo: bottomAnchor,constant: -16),
])
}
change your grouplabel like this
let groupLabel : UILabel = {
let label = UILabel()
label.textColor = .black
label.text = "Test Group"
label.font = UIFont(name: "candara", size: 20)
return label
}()
Change to
make.center.equalTo(self.contentView.snp.center)
or
make.center.equalToSuperview()
Instead of
make.center.equalTo(self.contentView)

Constraints in UITableViewCell not being calculated

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.

TextView not resizing properly

I have a tableview cell with a textview inside it:
class DetailsCell: UITableViewCell, UITextViewDelegate {
let detailsTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont(name: "AvenirNext-Medium", size: 24)
tv.isScrollEnabled = false
tv.textColor = .white
tv.backgroundColor = .clear
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
func textViewDidChange(_ textView: UITextView) {
let addTodoViewController = AddTodoViewController()
addTodoViewController.begindEndUpdate()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: "DetailsCell")
addSubview(detailsTextView)
detailsTextView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
detailsTextView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
detailsTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
detailsTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
backgroundColor = .black
textLabel?.isHidden = true
detailsTextView.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I load the app and the textview already has a text, assigned programmatically, the cell properly get's the right height and the textview is properly resized, but when I try to change the text of the textview, the cell and the textview don't resize. Can someone help me?
override func viewWillAppear(_ animated: Bool) {
toDoTextField.becomeFirstResponder()
addTodoTableView.estimatedRowHeight = 60
addTodoTableView.rowHeight = UITableViewAutomaticDimension
}
Textview with not initial text Image
Textview with initial text Image
UITextView does not resize itself when its content changes. You have to write some code to resize your textView and then accordingly adjust the height of your cell. Probably this can help you resizing textview to its content
or you can use some third party library instead of default UITextView like this one GrowingTextView

Resources