Stackview Show hide constraints not making self sizing possible - ios

I've made UI for cell in cell class below:
final class OptionTVCell: UITableViewCell {
fileprivate static let id = String(describing: OptionTVCell.self)
private var defaultTintColor: UIColor {
let color = AppConfiguration.sharedAppConfiguration.appTextColor
return Utility.hexStringToUIColor(hex: color ?? "ffffff")
}
// MARK: - Subviews
lazy private(set) var optionImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 24).isActive = true
imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
let widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
widthConstraint.priority = UILayoutPriority(998)
widthConstraint.isActive = true
imageView.contentMode = .scaleAspectFit
imageView.tintColor = defaultTintColor
return imageView
}()
lazy private(set) var optionNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
label.textColor = defaultTintColor
label.numberOfLines = 0
let fontSize: CGFloat = Constants.shared.IPHONE ? 14 : 20
label.font = UIFont(name: Utility.getFontName(), size: fontSize)
return label
}()
lazy private(set) var separatorLineView: UIView = {
let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false
let heightConstraint = separator.heightAnchor.constraint(equalToConstant: 1)
heightConstraint.priority = UILayoutPriority(999)
heightConstraint.isActive = true
separator.backgroundColor = defaultTintColor.withAlphaComponent(0.5)
return separator
}()
lazy private(set) var separatorContainerStackView: UIStackView = {
let verticalStackView = UIStackView()
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.axis = .vertical
verticalStackView.spacing = 10
verticalStackView.distribution = .fillProportionally
verticalStackView.alignment = .fill
return verticalStackView
}()
lazy private(set) var iconContainerStackView: UIStackView = {
let horizontalStackView = UIStackView()
horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
horizontalStackView.axis = .horizontal
horizontalStackView.spacing = 16
horizontalStackView.distribution = .fill
horizontalStackView.alignment = .center
return horizontalStackView
}()
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Life Cycle
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
super.setHighlighted(highlighted, animated: animated)
let color = highlighted
? Utility.hexStringToUIColor(hex: AppConfiguration.sharedAppConfiguration.primaryHoverColor ?? "ffffff")
: defaultTintColor
optionImageView.tintColor = color
optionNameLabel.textColor = color
}
// MARK: - Setup
private func setupView() {
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
setupIconStackView()
setupSeparatorStackView()
}
private func setupSeparatorStackView() {
contentView.addSubview(separatorContainerStackView)
NSLayoutConstraint.activate([
separatorContainerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
separatorContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
separatorContainerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
separatorContainerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16)
])
separatorContainerStackView.addArrangedSubview(iconContainerStackView)
separatorContainerStackView.addArrangedSubview(separatorLineView)
}
private func setupIconStackView() {
iconContainerStackView.addArrangedSubview(optionImageView)
iconContainerStackView.addArrangedSubview(optionNameLabel)
let labelWidth = optionNameLabel.widthAnchor.constraint(greaterThanOrEqualTo: iconContainerStackView.widthAnchor, constant: -40)
labelWidth.isActive = true
}
// MARK: - Configure
}
Below is the tableView implementation for cell:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat(cellHeight)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: OptionTVCell.id) as? OptionTVCell,
indexPath.row <= optionsArray.count-1,
let navItem = optionsArray[indexPath.row] as? OptionItem
else {
return UITableViewCell()
}
cell.separatorLineView.isHidden = navItem.hasSeparator == true ? false : true
if let imageName = navItem.pageIcon, !imageName.isEmpty {
if let optionImage = UIImage(named: imageName)?.withRenderingMode(.alwaysTemplate) {
cell.optionImageView.image = optionImage
cell.optionImageView.isHidden = false
} else {
let listImageStringURL = imageName.appending("?w=\(Utility.sharedUtility.getImageSizeAsPerScreenResolution(size: cell.optionImageView.frame.size.width)))&h=\(Utility.sharedUtility.getImageSizeAsPerScreenResolution(size: cell.optionImageView.frame.size.height))")
if let imageURL = URL(string: listImageStringURL) {
cell.optionImageView.af.setImage(
withURL: imageURL,
placeholderImage: nil,
filter: nil,
imageTransition: .crossDissolve(0.2)
)
}
}
} else {
cell.optionImageView.isHidden = true
}
cell.optionNameLabel.text = navItem.title?.uppercased()
return cell
}
As you can see the separator view and icon can be hidden or shown and modifies cell height accordingly. The issue is the self sizing does not work on initial dequeue but after scrolling it fixes itself.
Here is the runtime error thrown from autolayout:
1.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600002525810 'fittingSizeHTarget' UIStackView:0x7fafd1e8c330.width == 0 (active)>",
"<NSLayoutConstraint:0x600002524f50 'UISV-canvas-connection' UIStackView:0x7fafd1e8c330.leading == UIImageView:0x7fafd1e8c4c0.leading (active)>",
"<NSLayoutConstraint:0x600002524fa0 'UISV-canvas-connection' H:[UILabel:0x7fafd1e8aa30]-(0)-| (active, names: '|':UIStackView:0x7fafd1e8c330 )>",
"<NSLayoutConstraint:0x600002524ff0 'UISV-spacing' H:[UIImageView:0x7fafd1e8c4c0]-(16)-[UILabel:0x7fafd1e8aa30] (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600002524ff0 'UISV-spacing' H:[UIImageView:0x7fafd1e8c4c0]-(16)-[UILabel:0x7fafd1e8aa30] (active)>
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600002500410 V:|-(8)-[UIStackView:0x7fafd1f47c90] (active, names: '|':UITableViewCellContentView:0x7fafd1f0cf90 )>",
"<NSLayoutConstraint:0x600002500820 UIStackView:0x7fafd1f47c90.bottom == UITableViewCellContentView:0x7fafd1f0cf90.bottom - 8 (active)>",
"<NSLayoutConstraint:0x600002500550 UIImageView:0x7fafd1f569f0.height == 24 (active)>",
"<NSLayoutConstraint:0x600002502120 'UISV-canvas-connection' V:[_UILayoutSpacer:0x60000399e2b0'UISV-alignment-spanner']-(0)-| (active, names: '|':UIStackView:0x7fafd1f5a1e0 )>",
"<NSLayoutConstraint:0x600002500370 'UISV-canvas-connection' UIStackView:0x7fafd1f5a1e0.centerY == UIImageView:0x7fafd1f569f0.centerY (active)>",
"<NSLayoutConstraint:0x600002500000 'UISV-canvas-connection' UIStackView:0x7fafd1f47c90.top == UIStackView:0x7fafd1f5a1e0.top (active)>",
"<NSLayoutConstraint:0x6000025d2260 'UISV-canvas-connection' V:[UIView:0x7fafd1f48db0]-(0)-| (active, names: '|':UIStackView:0x7fafd1f47c90 )>",
"<NSLayoutConstraint:0x6000025b2490 'UISV-spacing' V:[UIStackView:0x7fafd1f5a1e0]-(10)-[UIView:0x7fafd1f48db0] (active)>",
"<NSLayoutConstraint:0x600002502210 'UISV-spanning-boundary' _UILayoutSpacer:0x60000399e2b0'UISV-alignment-spanner'.bottom >= UIImageView:0x7fafd1f569f0.bottom (active)>",
"<NSLayoutConstraint:0x6000025f2cb0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fafd1f0cf90.height == 43.5 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600002500550 UIImageView:0x7fafd1f569f0.height == 24 (active)>
What I did:
I have added a few constraints and reduced priority as they were giving some errors when made hidden but they had fixed height/width. But errors are still thrown and self sizing still not working properly.

You should be able to get rid of your layout errors / warnings with a single change:
lazy private(set) var separatorContainerStackView: UIStackView = {
let verticalStackView = UIStackView()
verticalStackView.translatesAutoresizingMaskIntoConstraints = false
verticalStackView.axis = .vertical
verticalStackView.spacing = 10
// use .fill NOT .fillProportionally
verticalStackView.distribution = .fill // .fillProportionally
verticalStackView.alignment = .fill
return verticalStackView
}()
A tip: when working with UIStackView, forget about the .fillProportionally distribution setting. There are very specific layouts where that is appropriate, but you are unlikely to encounter them. And, as you see, it can cause problems when used incorrectly.
As side notes:
lazy private(set) var optionImageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.heightAnchor.constraint(equalToConstant: 24).isActive = true
// you can use this
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
// none of this is needed
//imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
//imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
//let widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
//widthConstraint.priority = UILayoutPriority(998)
//widthConstraint.isActive = true
imageView.contentMode = .scaleAspectFit
imageView.tintColor = defaultTintColor
return imageView
}()
and:
lazy private(set) var optionNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
// these two are not needed
//label.setContentHuggingPriority(.defaultLow, for: .horizontal)
//label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
label.textColor = defaultTintColor
label.numberOfLines = 0
let fontSize: CGFloat = Constants.shared.IPHONE ? 14 : 20
label.font = UIFont(name: Utility.getFontName(), size: fontSize)
return label
}()

Related

iOS - Take all the available space in a horizontal UIStackView

I have UITableViewCell that contains a horizontal UIStackView. The UIStackView contains four views in the following order.
UIImageView UILabel UILabel UIImageView
There are 16 points spacing after the arrangedSubViews. I want that the second UILabel takes all the available space. If there is not enough space, it text should wrap.
I have used the following codes. It works almost. The problem is that even though there is enough space between the UILabel's as you see in the screenshot attached, the second UILabel breaks. But I want it to break only if there is not enough space.
class TableViewCell: UITableViewCell {
static let identifier = "TableViewCell"
private let leadingImageView: UIImageView = {
let view = UIImageView(image: UIImage(systemName: "calendar"))
view.setConstraints(heightConstant: 25, widthConstant: 25)
view.tintColor = .text
return view
}()
private let leadingLabel: UILabel = {
let label = UILabel()
label.textColor = .label
label.text = "Start Date"
label.numberOfLines = 0
label.sizeToFit()
return label
}()
let trailingLabel: UILabel = {
let label = UILabel()
label.text = "Friday, 17 July 2020"
label.textColor = .label
label.numberOfLines = 0
label.textAlignment = .right
return label
}()
let trailingImageView: UIImageView = {
let configuration = UIImage.SymbolConfiguration(pointSize: 12, weight: .light)
let image = UIImage(systemName: "arrowtriangle.down", withConfiguration: configuration)
let view = UIImageView(image: image)
return view
}()
private let superStackView: UIStackView = {
let view = UIStackView()
view.distribution = .fillProportionally
view.alignment = .center
view.spacing = 16
return view
}()
private let containerView: UIView = {
let view = UIView()
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension TableViewCell {
private func setUpSubviews(){
containerView.addSubview(trailingImageView)
trailingImageView.alignCenter(centerXAnchor: containerView.centerXAnchor,
centerYAnchor: containerView.centerYAnchor)
superStackView.addArrangedSubview(leadingImageView)
superStackView.addArrangedSubview(leadingLabel)
superStackView.addArrangedSubview(trailingLabel)
superStackView.addArrangedSubview(containerView)
let constant = CGFloat(16)
self.addSubview(superStackView)
self.contentView.addSubview(superStackView)
superStackView.setConstraints(topAnchor: contentView.topAnchor, leadingAnchor: contentView.leadingAnchor,
bottomAnchor: contentView.bottomAnchor, trailingAnchor: contentView.trailingAnchor,
topConstant: constant, leadingConstant: constant, bottomConstant: constant, trailingConstant: constant)
self.contentView.updateConstraints()
}
}
How can I fix this issue so that the cell looks like the cells in the following image?
You're close...
First, setting a stack view's Distribution to Fill Proportionally is the most misunderstood distribution option, and you don't want to use it here.
Second, it helps greatly when designing to use contrasting backgrounds to make it easy to see what your frames are doing.
Here is your code, modified to get to what appears to be your goal. I don't have your "constraint helpers" so I changed it to standard constraint format. I also added a few comments for some clarification:
class TableViewCell: UITableViewCell {
static let identifier = "TableViewCell"
private let leadingImageView: UIImageView = {
let view = UIImageView(image: UIImage(systemName: "calendar"))
view.translatesAutoresizingMaskIntoConstraints = false
//view.setConstraints(heightConstant: 25, widthConstant: 25)
view.widthAnchor.constraint(equalToConstant: 25).isActive = true
view.heightAnchor.constraint(equalToConstant: 25).isActive = true
view.tintColor = .blue // .text
return view
}()
private let leadingLabel: UILabel = {
let label = UILabel()
label.textColor = .label
label.text = "Start Date"
// only single line for "leading label"
label.numberOfLines = 1
// content hugging
label.setContentHuggingPriority(.required, for: .horizontal)
label.backgroundColor = .cyan
return label
}()
let trailingLabel: UILabel = {
let label = UILabel()
label.text = "Friday, 17 July 2020"
label.textColor = .label
label.numberOfLines = 0
label.textAlignment = .right
label.backgroundColor = .green
return label
}()
let trailingImageView: UIImageView = {
let configuration = UIImage.SymbolConfiguration(pointSize: 12, weight: .light)
let image = UIImage(systemName: "arrowtriangle.down", withConfiguration: configuration)
let view = UIImageView(image: image)
return view
}()
private let superStackView: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
// do NOT use .fillProportionally
view.distribution = .fill
view.alignment = .center
view.spacing = 16
return view
}()
private let containerView: UIView = {
let view = UIView()
// not needed
//view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
//view.setContentHuggingPriority(.defaultHigh, for: .vertical)
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpSubviews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension TableViewCell {
private func setUpSubviews(){
containerView.addSubview(trailingImageView)
//trailingImageView.alignCenter(centerXAnchor: containerView.centerXAnchor,
// centerYAnchor: containerView.centerYAnchor)
superStackView.addArrangedSubview(leadingImageView)
superStackView.addArrangedSubview(leadingLabel)
superStackView.addArrangedSubview(trailingLabel)
superStackView.addArrangedSubview(containerView)
let constant = CGFloat(16)
self.addSubview(superStackView)
self.contentView.addSubview(superStackView)
//superStackView.setConstraints(topAnchor: contentView.topAnchor, leadingAnchor: contentView.leadingAnchor,
// bottomAnchor: contentView.bottomAnchor, trailingAnchor: contentView.trailingAnchor,
// topConstant: constant, leadingConstant: constant, bottomConstant: constant, trailingConstant: constant)
NSLayoutConstraint.activate([
trailingImageView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
trailingImageView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
superStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: constant),
superStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: constant),
superStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -constant),
superStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -constant),
])
// not needed
//self.contentView.updateConstraints()
}
}
And, an example view controller:
class MahanTableViewController: UITableViewController {
var myData: [String] = [
"Saturday, 18 July 2020",
"Sunday, 19 July 2020",
"Wednesday, 22 July 2020",
"Saturday, 26 September 2020",
"Sunday, 27 September 2020",
"Wednesday, 30 September 2020",
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as! TableViewCell
cell.trailingLabel.text = myData[indexPath.row]
return cell
}
}
Producing this output:
Try to Return 1
let trailingLabel: UILabel = {
let label = UILabel()
label.text = "Friday, 17 July 2020"
label.textColor = .label
label.numberOfLines = 1
label.textAlignment = .right
return label
}()

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.

adding subviews in uitableviewcell breaks constraints

I have an imageview with fixed height and width of 55 and a label under it but i get the following error. The constraints are not able to tell the tableview what the height should be even thought i set UITableView.automaticDimension
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x2811767b0 h=--& v=--& UILabel:0x1057292b0'steve_rodriquez'.minY == 0 (active, names: '|':UITableViewCellContentView:0x105729520 )>",
"<NSAutoresizingMaskLayoutConstraint:0x281176800 h=--& v=--& UILabel:0x1057292b0'steve_rodriquez'.height == 0 (active)>",
"<NSLayoutConstraint:0x2811765d0 UILabel:0x1057292b0'steve_rodriquez'.bottom == UITableViewCellContentView:0x105729520.bottom (active)>",
"<NSLayoutConstraint:0x2811772a0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x105729520.height == 0.333333 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x2811765d0 UILabel:0x1057292b0'steve_rodriquez'.bottom == UITableViewCellContentView:0x105729520.bottom (active)>
My tableviewcell class is this with the constraints
class ChatTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(userImageView)
self.contentView.addSubview(username)
userImageView_constraints()
username_constraints()
}
var userImageView: UIImageView = {
let imageView = UIImageView()
let imageViewHeightAndWidth: CGFloat = 55
let image = UIImage(named: "steve")
imageView.image = image
imageView.clipsToBounds = true
imageView.layer.cornerRadius = imageViewHeightAndWidth / 2
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
var username: UILabel = {
let label = UILabel()
label.text = "steve_rodriquez"
label.textAlignment = .center
label.font = UIFont(name: "Arial", size: 13)
return label
}()
func userImageView_constraints(){
userImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 20).isActive = true
userImageView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 0).isActive = true
userImageView.widthAnchor.constraint(equalToConstant: 55).isActive=true
userImageView.heightAnchor.constraint(equalToConstant: 55).isActive=true
}
func username_constraints(){
username.topAnchor.constraint(equalTo: userImageView.bottomAnchor, constant: 0).isActive = true
username.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: 0).isActive=true
username.centerXAnchor.constraint(equalTo: userImageView.centerXAnchor).isActive=true
username.widthAnchor.constraint(equalToConstant: 65).isActive=true
}
You forgot to set translatesAutoresizingMaskIntoConstraints = false on the label — as the error message quite plainly points out.

Autolayout warning tableview cell

I am building a chat layout and i have a table view and custom chat cell by adding constraint programmatically but i am getting the autolayout warning
In my ChatMessageCell i have a enum Style to determine the chat layout direction leading or trailing
I have messageLabel which has top, bottom, leading or trailing anchor to the UITableViewCelland width less than equal to 250
I have view which has leading, trailing, bottom and top anchor to the messageLabel view, setMessageLayout method active or deactive the leading or trailing anchor of the view
Autolayout Warning
(
"<NSLayoutConstraint:0x6000039a3c00 UILabel:0x7ff54cc34b20'Hello how are you'.width <= 250 (active)>",
"<NSLayoutConstraint:0x6000039a3e80 H:[UILabel:0x7ff54cc34b20'Hello how are you']-(16)-| (active, names: '|':iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading' )>",
"<NSLayoutConstraint:0x6000039a3e30 iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.leading == UILabel:0x7ff54cc34b20'Hello how are you'.leading - 16 (active)>",
"<NSLayoutConstraint:0x6000039afca0 'UIView-Encapsulated-Layout-Width' iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.width == 375 (active)>"
)
ChatMessageCell
class ChatMessageCell: UITableViewCell {
enum Style {
case leading
case trailing
}
private var style: Style!
private let messageLbl = UILabel()
private let view = UIView()
private var leadingConstraint: NSLayoutConstraint!
private var trailingConstraint: NSLayoutConstraint!
var message: ChatMessage! {
didSet {
messageLbl.text = message.message
setMessageLayout(with: message.sender == "User1" ? .leading : .trailing)
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = #colorLiteral(red: 0.9490196078, green: 0.9450980392, blue: 0.9529411765, alpha: 1)
messageLbl.translatesAutoresizingMaskIntoConstraints = false
messageLbl.font = UIFont(name: "Avenir-Book", size: 17)
messageLbl.numberOfLines = 0
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 5
addSubview(view)
addSubview(messageLbl)
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: messageLbl.topAnchor, constant: -16),
bottomAnchor.constraint(equalTo: messageLbl.bottomAnchor, constant: 16),
messageLbl.widthAnchor.constraint(lessThanOrEqualToConstant: 250)
])
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: messageLbl.topAnchor, constant: -8),
view.bottomAnchor.constraint(equalTo: messageLbl.bottomAnchor, constant: 8),
view.leadingAnchor.constraint(equalTo: messageLbl.leadingAnchor, constant: -8),
view.trailingAnchor.constraint(equalTo: messageLbl.trailingAnchor, constant: 8),
])
leadingConstraint = leadingAnchor.constraint(equalTo: messageLbl.leadingAnchor, constant: -16)
trailingConstraint = trailingAnchor.constraint(equalTo: messageLbl.trailingAnchor, constant: 16)
leadingConstraint.isActive = true
trailingConstraint.isActive = false
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setMessageLayout(with style: Style) {
messageLbl.textColor = style == .leading ? .black : .white
view.backgroundColor = style == .leading ? .white : #colorLiteral(red: 0.06274509804, green: 0.6039215686, blue: 0.3019607843, alpha: 1)
leadingConstraint.isActive = style == .leading ? true : false
trailingConstraint.isActive = style == .trailing ? true : false
}
}
My Layout
According to your Autolayout warning, messageLabel's width and leading are conflicting.
The warning is telling that both status are active
(
"<NSLayoutConstraint:0x6000039a3c00 UILabel:0x7ff54cc34b20'Hello how are you'.width <= 250 (active)>",
"<NSLayoutConstraint:0x6000039a3e80 H:[UILabel:0x7ff54cc34b20'Hello how are you']-(16)-| (active, names: '|':iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading' )>",
"<NSLayoutConstraint:0x6000039a3e30 iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.leading == UILabel:0x7ff54cc34b20'Hello how are you'.leading - 16 (active)>",
"<NSLayoutConstraint:0x6000039afca0 'UIView-Encapsulated-Layout-Width' iOSChatApp.ChatMessageCell:0x7ff54d087600'cell_leading'.width == 375 (active)>"
)
If you read warning carefully, the console may recommend you what to remove.

Prevent UIStackView from compressing UITableView

I am adding a UITableView into vertical UIStackView. That vertical UIStackView is within a UIScrollView.
However the table is not displaying unless I force an explicit Height constraint on it which I obviously don't want to do.
According to this SO question and answer UITableView not shown inside UIStackView this is because "stackview tries to compress content as much as possible"
If I add a UILabel to the StackView it is displayed fine. There is something specific about the UITableView that means it is not. I am using Xamarin and creating the UITableView in code
this.recentlyOpenedPatientsTable = new UITableView()
{
RowHeight = UITableView.AutomaticDimension,
EstimatedRowHeight = 44.0f,
AllowsMultipleSelectionDuringEditing = false,
TranslatesAutoresizingMaskIntoConstraints = false,
Editing = false,
BackgroundColor = UIColor.Clear,
TableFooterView = new UIView(),
ScrollEnabled = false,
};
The UIScrollView is pinned to the Top, Bottom, Left and Right of the View and works fine. It takes the Height I expect.
I have tried both the suggestions in this SO question and neither have worked. I find it odd that I cannot find others having this issue.
Any other suggestions?
Here is a very basic example, using a UITableView subclass to make it auto-size its height based on its content.
The red buttons (in a horizontal stack view) are the first arranged subView in the vertical stack view.
The table is next (green background for the cells' contentView, yellow background for a multi-line label).
And the last arranged subView is a cyan background UILabel:
Note that the vertical stack view is constrained 40-pts from Top, Leading and Trailing, and at least 40-pts from the Bottom. If you add enough rows to the table to exceed the available height, you'll have to scroll to see the additional rows.
//
// TableInStackViewController.swift
//
// Created by Don Mag on 6/24/19.
//
import UIKit
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
class TableInStackCell: UITableViewCell {
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.textAlignment = .left
v.numberOfLines = 0
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .green
contentView.addSubview(theLabel)
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor, constant: 0.0),
theLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: 0.0),
theLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor, constant: 0.0),
theLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor, constant: 0.0),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class TableInStackViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let theStackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 8
return v
}()
let addButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Add a Row", for: .normal)
v.backgroundColor = .red
return v
}()
let deleteButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Delete a Row", for: .normal)
v.backgroundColor = .red
return v
}()
let buttonsStack: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fillEqually
v.spacing = 20
return v
}()
let theTable: ContentSizedTableView = {
let v = ContentSizedTableView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let bottomLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
v.textAlignment = .center
v.numberOfLines = 0
v.text = "This label is the last element in the stack view."
// prevent label from being compressed when the table gets too tall
v.setContentCompressionResistancePriority(.required, for: .vertical)
return v
}()
var theTableData: [String] = [
"Content Sized Table View",
"This row shows that the cell heights will auto-size, based on the cell content (multi-line label in this case).",
"Here is the 3rd default row",
]
var minRows = 1
let reuseID = "TableInStackCell"
override func viewDidLoad() {
super.viewDidLoad()
minRows = theTableData.count
view.addSubview(theStackView)
NSLayoutConstraint.activate([
// constrain stack view 40-pts from top, leading and trailing
theStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
theStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40.0),
theStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40.0),
// constrain stack view *at least* 40-pts from bottom
theStackView.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40.0),
])
buttonsStack.addArrangedSubview(addButton)
buttonsStack.addArrangedSubview(deleteButton)
theStackView.addArrangedSubview(buttonsStack)
theStackView.addArrangedSubview(theTable)
theStackView.addArrangedSubview(bottomLabel)
theTable.delegate = self
theTable.dataSource = self
theTable.register(TableInStackCell.self, forCellReuseIdentifier: reuseID)
addButton.addTarget(self, action: #selector(addRow), for: .touchUpInside)
deleteButton.addTarget(self, action: #selector(deleteRow), for: .touchUpInside)
}
#objc func addRow() -> Void {
// add a row to our data source
let n = theTableData.count - minRows
theTableData.append("Added Row: \(n + 1)")
theTable.reloadData()
}
#objc func deleteRow() -> Void {
// delete a row from our data source (keeping the original rows intact)
let n = theTableData.count
if n > minRows {
theTableData.remove(at: n - 1)
theTable.reloadData()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return theTableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! TableInStackCell
cell.theLabel.text = theTableData[indexPath.row]
return cell
}
}

Resources