rowHeight of cell not adjusting to content increase - ios

I use below code to add constraints programatically, no story board used
cell.descriptionDetail.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
cell.descriptionDetail.topAnchor.constraint(greaterThanOrEqualTo: cell.contentView.topAnchor, constant: 20).isActive = true
cell.descriptionDetail.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -20).isActive = true
cell.descriptionDetail.bottomAnchor.constraint(greaterThanOrEqualTo: cell.contentView.bottomAnchor, constant: 10).isActive = true
Now i use this in viewDidLaod
detailTableView.rowHeight = UITableView.automaticDimension
detailTableView.rowHeight = 40
even if i remove height constraints on all cell the row height does. not adjust, if i remove
detailTableView.rowHeight = 40
and add detailTableView.estimatedRowHeight = 40, i get error, currently this is what happens , content overlapping cells
UPDATE MY ENTIRE CODE OF FILE WHERE THE CELL IS BEING CREATED AND CONSTRAINED
let cell = detailTableView.dequeueReusableCell(withIdentifier: String(describing: TextOnlyCell.self), for: indexPath) as! TextOnlyCell
view.addSubview(cell.descriptionDetail)
view.addSubview(cell)
cell.descriptionDetail.numberOfLines = 0
cell.descriptionDetail.lineBreakMode = .byWordWrapping
cell.descriptionDetail.translatesAutoresizingMaskIntoConstraints = false
cell.descriptionDetail.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
cell.descriptionDetail.heightAnchor.constraint(greaterThanOrEqualToConstant: 20).isActive = true
///Constraints
cell.descriptionDetail.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 15).isActive = true
cell.descriptionDetail.topAnchor.constraint(greaterThanOrEqualTo: cell.topAnchor, constant: 10).isActive = true
cell.descriptionDetail.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -5).isActive = true
cell.descriptionDetail.bottomAnchor.constraint(greaterThanOrEqualTo: cell.bottomAnchor, constant: 10).isActive = true
cell.descriptionDetail.text = restaurant.description
return cell

Add this method in your class... and be sure that your constraints are attached with top and bottom ... to help automaticDimension to calculate height
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}

Related

Swift View is not hiding subViews correctly

UPDATE AT THE BOTTOM
Inside my ViewController I have a TableView with CustomCells. The content being presented in those cells is depending on the userInput. I think the best way to explain the problem is to actually show it:
1. adding cells to the tableView:
looking as expected
2. Problem: after dismissing the ViewController and going back to it:
showing views that should actually be hidden in the first cell
By the way, when I open the View-Hirarchy in the Debugger, it is being displayed correctly !
Here is also another video for a better understanding: video
In this case I didn't add an image, but when going back to the viewController it still shows the imageContainerView(shadow) and also the content for the first cell.
Code:
My code is quite complex and messy so I you can follow me here:
setupViews in CustomCell:
I don't think that this is quite helpful, but I also don't think that the setup is the issue here.
func setupViews(){
contentView.addSubview(checkButton)
contentView.addSubview(mainStackView)
// main StackView
mainStackView.addArrangedSubview(label)
mainStackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
mainStackView.leadingAnchor.constraint(equalTo: checkButton.trailingAnchor, constant: 15).isActive = true
mainStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -30).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
//constrain wish label
labelHeightConatraint = label.heightAnchor.constraint(equalToConstant: 50)
labelHeightConatraint.priority = .defaultHigh
labelHeightConatraint.isActive = true
// constrain checkButton
checkButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 30).isActive = true
checkButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
checkButton.widthAnchor.constraint(equalToConstant: 40).isActive = true
checkButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
mainStackView.addArrangedSubview(secondaryStackView)
secondaryStackViewHeightConstraint = secondaryStackView.heightAnchor.constraint(equalToConstant: 90)
secondaryStackViewHeightConstraint.priority = .defaultHigh
secondaryStackViewHeightConstraint.isActive = true
secondaryStackView.addArrangedSubview(imageContainerView)
imageContainerWidthConstraint = imageContainerView.widthAnchor.constraint(equalToConstant: 90)
imageContainerWidthConstraint.priority = .defaultHigh
imageContainerWidthConstraint.isActive = true
imageContainerView.addSubview(shadowLayer)
shadowLayer.widthAnchor.constraint(equalToConstant: 80).isActive = true
shadowLayer.heightAnchor.constraint(equalToConstant: 80).isActive = true
shadowLayer.topAnchor.constraint(equalTo: imageContainerView.topAnchor).isActive = true
shadowLayer.trailingAnchor.constraint(equalTo: imageContainerView.trailingAnchor, constant: -10).isActive = true
imageContainerView.addSubview(wishImage)
wishImage.widthAnchor.constraint(equalToConstant: 80).isActive = true
wishImage.heightAnchor.constraint(equalToConstant: 80).isActive = true
wishImage.topAnchor.constraint(equalTo: imageContainerView.topAnchor).isActive = true
wishImage.trailingAnchor.constraint(equalTo: imageContainerView.trailingAnchor, constant: -10).isActive = true
secondaryStackView.addArrangedSubview(thirdHelperView)
thirdHelperView.addSubview(thirdStackView)
thirdHelperViewHeightConstraint = thirdHelperView.heightAnchor.constraint(equalToConstant: 90)
thirdHelperViewHeightConstraint.priority = .defaultHigh
thirdHelperViewHeightConstraint.isActive = true
thirdStackView.addArrangedSubview(priceView)
priceView.heightAnchor.constraint(equalToConstant: 30).isActive = true
priceView.addSubview(priceImage)
priceView.addSubview(priceLabel)
thirdStackView.addArrangedSubview(linkView)
linkView.heightAnchor.constraint(equalToConstant: 30).isActive = true
linkView.addSubview(linkImage)
linkView.addSubview(linkTextView)
thirdStackView.addArrangedSubview(noteView)
noteView.heightAnchor.constraint(equalToConstant: 30).isActive = true
noteView.addSubview(noteImage)
noteView.addSubview(noteLabel)
priceImage.topAnchor.constraint(equalTo: priceView.topAnchor).isActive = true
priceImage.leadingAnchor.constraint(equalTo: thirdStackView.leadingAnchor).isActive = true
priceImage.heightAnchor.constraint(equalToConstant: 20).isActive = true
priceImage.widthAnchor.constraint(equalToConstant: 20).isActive = true
priceLabel.topAnchor.constraint(equalTo: priceView.topAnchor).isActive = true
priceLabel.leadingAnchor.constraint(equalTo: priceImage.trailingAnchor, constant: 10).isActive = true
priceLabel.trailingAnchor.constraint(equalTo: priceView.trailingAnchor, constant: -10).isActive = true
linkImage.topAnchor.constraint(equalTo: linkView.topAnchor).isActive = true
linkImage.leadingAnchor.constraint(equalTo: thirdStackView.leadingAnchor).isActive = true
linkImage.heightAnchor.constraint(equalToConstant: 20).isActive = true
linkImage.widthAnchor.constraint(equalToConstant: 20).isActive = true
linkTextView.topAnchor.constraint(equalTo: linkView.topAnchor).isActive = true
linkTextView.leadingAnchor.constraint(equalTo: linkImage.trailingAnchor, constant: 10).isActive = true
linkTextView.trailingAnchor.constraint(equalTo: linkView.trailingAnchor, constant: -10).isActive = true
noteImage.topAnchor.constraint(equalTo: noteView.topAnchor).isActive = true
noteImage.leadingAnchor.constraint(equalTo: thirdStackView.leadingAnchor).isActive = true
noteImage.heightAnchor.constraint(equalToConstant: 20).isActive = true
noteImage.widthAnchor.constraint(equalToConstant: 20).isActive = true
noteLabel.topAnchor.constraint(equalTo: noteView.topAnchor).isActive = true
noteLabel.leadingAnchor.constraint(equalTo: noteImage.trailingAnchor, constant: 10).isActive = true
noteLabel.trailingAnchor.constraint(equalTo: noteView.trailingAnchor, constant: -10).isActive = true
}
more importantly: cellForRowAt, where I actually hide/show the different views depending on the content. As you can see I actually call .isHidden on the ImageContainerView, priceView, linkView & noteView , if there content is empty ( which it is in the first cell )
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: WhishCell.reuseID, for: indexPath) as! WhishCell
cell.label.text = ""
cell.linkTextView.text = ""
cell.priceLabel.text = ""
cell.noteLabel.text = ""
cell.wishImage.image = UIImage()
let currentWish = self.wishData[indexPath.row]
cell.label.text = currentWish.name
cell.linkTextView.hyperLink(originalText: "Link öffnen", hyperLink: "Link öffnen", urlString: currentWish.link)
cell.priceLabel.text = currentWish.price
cell.noteLabel.text = currentWish.note
cell.wishImage.image = currentWish.image
cell.setupSuccessAnimation()
cell.noteView.isHidden = false
cell.priceView.isHidden = false
cell.linkView.isHidden = false
cell.imageContainerView.isHidden = false
cell.secondaryStackViewHeightConstraint.constant = 0
cell.thirdHelperViewHeightConstraint.constant = 0
if currentWish.image == nil || !currentWish.image!.hasContent {
cell.imageContainerView.isHidden = true
print("but its truue: \(cell.imageContainerView.isHidden)")
if currentWish.price != "" {
cell.thirdHelperViewHeightConstraint.constant += 30
cell.secondaryStackViewHeightConstraint.constant += 30
}
if currentWish.link != "" {
cell.thirdHelperViewHeightConstraint.constant += 30
cell.secondaryStackViewHeightConstraint.constant += 30
}
if currentWish.note != "" {
cell.thirdHelperViewHeightConstraint.constant += 30
cell.secondaryStackViewHeightConstraint.constant += 30
}
} else {
cell.secondaryStackViewHeightConstraint.constant = 90
cell.thirdHelperViewHeightConstraint.constant = 90
}
if currentWish.price == "" {
cell.priceView.isHidden = true
}
if currentWish.link == "" {
cell.linkView.isHidden = true
}
if currentWish.note == "" {
cell.noteView.isHidden = true
}
return cell
}
I have no idea why this happens. I don't think something is wrong with the setup as it works if I actually add the cells. It is simply not hiding the views when it actually should. The cellheight is also working as expected. Just the damn hiding...
I know this is a lot but I hope my problem is clear. If you need any thing more just let me know!
Update:
I added two print-statements inside cellForRowAt and is actually printing this:
print("but its truue: \(cell.imageContainerView.isHidden)")
print("but its truue: \(cell.shadowLayer.isHidden)")
but its truue: true
but its truue: false
so it is hiding the imageConatinerView correctly but not shadowLayer even though shadowLayer is a subView of imageContainerView??! Im stuck here...
You can use UIView debugging method to debug your UI.
There are four useful methods to debug:
hasAmbiguousLayout:
exerciseAmbiguityInLayout:
exerciseAmbiguityInLayout:
_autolayoutTrace:
And then you can use Xcode UI Debug to review your UI

Swift Programmatic Constraint not working

I am trying to create a uiview that has a segment control inside. I want to be able to add this uiview to my viewcontroller's view. the segment control should be right on top of my tableview. but everytime i setup the constraints i keep getting this error
"Thread 1: Exception: "Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x282ee24c0 "UIView:0x119d3a610.bottom"> and <NSLayoutYAxisAnchor:0x282ee2500 "UITableView:0x11a014a00.top"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal."" I tried working around by adding the subview first and what not but it's not working. here's my code if anyone can help me.
func configureTableView(){
setupSegmentControl()
view.addSubview(tableView)
setTableViewDelegates()
tableView.rowHeight = 50
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.tableView.topAnchor.constraint(equalToSystemSpacingBelow: self.view.topAnchor, multiplier: 20).isActive = true
tableView.register(UINib(nibName: "CustomCellNSB2", bundle: nil), forCellReuseIdentifier: "CustomCellNSB2")
}
func setTableViewDelegates(){
tableView.delegate = self
tableView.dataSource = self
}
func setupSegmentControl(){
var headerView = UIView()
var importanceSegmentControl = CustomSegmentControl()
headerView.addSubview(importanceSegmentControl)
self.view.addSubview(headerView)
importanceSegmentControl.addTarget(self, action: #selector(indexChanged(control:)),for: UIControl.Event.valueChanged)
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
headerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
headerView.bottomAnchor.constraint(equalTo: self.tableView.topAnchor, constant: 20).isActive = true
headerView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 10).isActive = true
importanceSegmentControl.translatesAutoresizingMaskIntoConstraints = false
importanceSegmentControl.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 20).isActive = true
importanceSegmentControl.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -20).isActive = true
importanceSegmentControl.bottomAnchor.constraint(equalTo: headerView.topAnchor, constant: 20).isActive = true
importanceSegmentControl.topAnchor.constraint(equalTo: headerView.topAnchor, constant: 10).isActive = true
}
The tableView and importanceSegmentControl doesn't have any common ancestor at the time of adding the constraint to the importanceSegmentControl. So to fix the issue just switch the order of execution:
func configureTableView(){
view.addSubview(tableView)
setupSegmentControl()
//...
}

TableView not appearing after setting the constraints programmatically

I have a UITableView in my storyboard. I am trying to setup constraints for it in my view controller. After running the application it's not showing up at all. it's only working when i don't run the constraints. here are my constraints.
func tableviewsConstraints(){
homeTableView.translatesAutoresizingMaskIntoConstraints = false
homeTableView.layer.cornerRadius = 10
homeTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
homeTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
homeTableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50).isActive = true
homeTableView.heightAnchor.constraint(equalToConstant: homeTableView.rowHeight * 3).isActive = true
self.view.addSubview(homeTableView)
}
override func viewDidLoad() {
super.viewDidLoad()
tableviewsConstraints()
Add the homeTableView as the view's subview before adding the constraints to it. And use a constant value when setting the height of tableView, i.e.
func tableviewsConstraints(){
self.view.addSubview(homeTableView) //here....
homeTableView.layer.cornerRadius = 10
homeTableView.translatesAutoresizingMaskIntoConstraints = false
homeTableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20).isActive = true
homeTableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20).isActive = true
homeTableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 50).isActive = true
let rowHeight: CGFloat = 100.0 //here....
homeTableView.heightAnchor.constraint(equalToConstant: rowHeight * 3).isActive = true
}
Change the rowHeight value as per your requirement.
Don't you have the outlet of the table view if you have it in the storyboard? Then why you need to subview? If you're creating table view programmatically then follow this.

Async height change for UITableViewCell

In one of my projects, I need to change the height of UIImageView in UITableViewCell according to image size, but the problem is that sometimes I have to do this after the cell is already shown.
So, my current solution works like a charm if I know all the image sizes beforehand, but if I'm trying to calculate this with some delay – it's completely broken (especially with scrolling but it's broken even without it).
I made the example project to illustrate this. There is no async downloading, but I'm trying to dynamically change the height of UIImageView after some delay (1s). The height depends on UIImageView, so every next UIImageView should be slightly higher (10 pixels) than previous one. Also, I have a UILabel, constrained to UIImageView.
It looks like that (UIImageViews are the red ones)
If I'm trying to do this async, it looks like this, all the UILabels are really broken here.
and this is one after the scroll (async too):
What am I doing wrong here? I've read several threads about dynamic heights, but none of the solutions worked for me yet.
My code is fairly simple:
func addTableView() {
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .black
tableView.register(DynamicCell.self, forCellReuseIdentifier: "dynamicCell")
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
tableView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamicCell", for: indexPath) as! DynamicCell
cell.message = messageArray[indexPath.row]
cell.backgroundColor = .clear
cell.selectionStyle = .none
cell.buildCell()
return cell
}
DynamicCell.swift (delegate is doing nothing right now):
var backView: UIView!
var label: UILabel!
var picView: UIImageView!
var message: DMessage?
var picViewHeight: NSLayoutConstraint!
var delegate: RefreshCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backView = UIView()
backView.translatesAutoresizingMaskIntoConstraints = false
backView.backgroundColor = .white
backView.clipsToBounds = true
backView.layer.cornerRadius = 8.0
self.addSubview(backView)
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .left
label.textColor = .black
label.numberOfLines = 0
backView.addSubview(label)
picView = UIImageView()
picView.translatesAutoresizingMaskIntoConstraints = false
picView.clipsToBounds = true
picView.backgroundColor = .red
backView.addSubview(picView)
addMainConstraints()
}
func addMainConstraints() {
backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -32).isActive = true
backView.topAnchor.constraint(equalTo: self.topAnchor, constant: 4).isActive = true
backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4).isActive = true
picView.topAnchor.constraint(equalTo: backView.topAnchor, constant: 0).isActive = true
picView.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 0).isActive = true
picView.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: 0).isActive = true
label.topAnchor.constraint(equalTo: picView.bottomAnchor, constant: 0).isActive = true
label.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 8).isActive = true
label.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -8).isActive = true
label.bottomAnchor.constraint(equalTo: backView.bottomAnchor, constant: -4).isActive = true
picViewHeight = NSLayoutConstraint(item: picView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100)
picViewHeight.priority = UILayoutPriority(999)
picViewHeight.isActive = true
}
override func prepareForReuse() {
picViewHeight.constant = 0
//picViewHeight.constant = 0
}
func buildCell() {
guard let message = message else {return}
label.attributedText = NSAttributedString(string: message.text)
changeHeightWithDelay()
//changeHeightWithoutDelay()
}
func changeHeightWithoutDelay() {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
func changeHeightWithDelay() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let nh = self.message?.imageHeight {
self.picViewHeight.constant = nh
self.delegate?.refreshCell(cell: self)
}
}
}
putting this as an answer.
one thing I noticed, when you are playing around with cell, it's always better to use the contentView instead of directly using self. ie self.contentView.addSubview(). what does refreshcell function do? have you tried marking it as needsSetDisplay so in the next draw cycle it will be updated? have you tried calling layoutIfNeeded?
To explain a bit further, your view has already been 'rendered' the moment you want to change the height/width of your view you need to inform it that there's an update. this happens when you mark the view as setNeedsDisplay and in the next render cycle it will be updated
more info on apple's documentation here
You can use this method or the setNeedsDisplay(_:) to notify the system that your view’s contents need to be redrawn. This method makes a note of the request and returns immediately. The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.

Swift Autolayout - Height Anchor not being applied

I am working on a "Profile View Controller" which is inside a TableViewCell and table cell dimension is set as UITableView.automaticDimension:
The problem is that I would like to set the background image view (Blue) with a variable height based on cell width and a 16:9 ratio. For some reason I am not being able to change the view height even if I set the constant.
Here the code:
let topView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 20
view.backgroundColor = .yoofitDarkBlue
view.layer.masksToBounds = true
view.layer.shadowColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.5
view.layer.borderColor = UIColor.yoofitDarkBlue.cgColor
view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
view.layer.shadowRadius = 12.0
view.layer.shadowOpacity = 0.7
return view
}()
let userImage : UIImageView = {
let i = UIImageView()
i.translatesAutoresizingMaskIntoConstraints = false
i.image = UIImage(named: "user2")
i.layer.masksToBounds = true
i.layer.cornerRadius = 75
i.layer.borderWidth = 1
i.layer.borderColor = UIColor.white.cgColor
return i
}()
var userNameLabel:UILabel = {
let l = UILabel()
l.translatesAutoresizingMaskIntoConstraints = false
l.font = UIFont(name: "AvenirNext-DemiBold", size: 20)
l.textAlignment = .center
l.textColor = .yoofitBlack
return l
}()
func setupViews() {
self.backgroundColor = .clear
self.selectionStyle = .none
addSubview(topView)
let width = self.frame.width
print(width) // print 320
topView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
topView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
topView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
topView.heightAnchor.constraint(equalToConstant: width/16*9).isActive = true // this doesn't work even if I set another number like 600
topView.layoutIfNeeded()
addSubview(userImage)
userImage.centerYAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true
userImage.centerXAnchor.constraint(equalTo: topView.centerXAnchor).isActive = true
userImage.widthAnchor.constraint(equalToConstant: 150).isActive = true
userImage.heightAnchor.constraint(equalToConstant: 150).isActive = true
addSubview(userNameLabel)
userNameLabel.topAnchor.constraint(equalTo: userImage.bottomAnchor, constant: 10).isActive = true
userNameLabel.centerXAnchor.constraint(equalTo: userImage.centerXAnchor).isActive = true
userNameLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
userNameLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
userNameLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -20).isActive = true
}
PLEASE NOTE THERE ARE 2 answers to this question. The latter is definitely the correct one but the first maybe may help others.
So this is how I fixed it but I hope to get better answers. This answer helped me to go to the right path (iOS: How to Align The Center of a View With The Bottom of Another View With AutoLayout)
I have added a containing view which is twice as the required size of my top view (the blue one).
I centered the picture on the containing view.
self.conteiningView.backgroundColor = .red // I made this red so you can visually see how the views are related to each other
let width = self.frame.width
self.addSubview(conteiningView)
conteiningView.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
conteiningView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
conteiningView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
conteiningView.heightAnchor.constraint(equalToConstant: (width/16*9)*2).isActive = true // twice of the blue view
conteiningView.layoutIfNeeded()
self.conteiningView.addSubview(topView)
topView.topAnchor.constraint(equalTo: conteiningView.topAnchor).isActive = true
topView.leftAnchor.constraint(equalTo: conteiningView.leftAnchor).isActive = true
topView.rightAnchor.constraint(equalTo: conteiningView.rightAnchor).isActive = true
topView.heightAnchor.constraint(equalToConstant: width/16*9).isActive = true // 16:9 ration
conteiningView.addSubview(userImage)
userImage.centerYAnchor.constraint(equalTo: conteiningView.centerYAnchor).isActive = true
userImage.centerXAnchor.constraint(equalTo: conteiningView.centerXAnchor).isActive = true
userImage.widthAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.heightAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.layer.cornerRadius = width/4 // to mantain nice proportions
conteiningView.addSubview(userNameLabel)
userNameLabel.topAnchor.constraint(equalTo: userImage.bottomAnchor, constant: 10).isActive = true
userNameLabel.centerXAnchor.constraint(equalTo: userImage.centerXAnchor).isActive = true
userNameLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
userNameLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true
With red background:
With clear background:
UPDATE: The answer above works but I have actually found the problem:
Even if I had set:
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 300
For some (silly me) I had also implemented:
tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
This, of course, does not be implemented for the automatic dimension to work.
removing that allowed to use the following code to achieve the same result but without setting a predefined height:
self.addSubview(conteiningView)
conteiningView.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
conteiningView.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
conteiningView.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
conteiningView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
conteiningView.layoutIfNeeded()
self.conteiningView.addSubview(topView)
topView.topAnchor.constraint(equalTo: conteiningView.topAnchor).isActive = true
topView.leftAnchor.constraint(equalTo: conteiningView.leftAnchor).isActive = true
topView.rightAnchor.constraint(equalTo: conteiningView.rightAnchor).isActive = true
topView.heightAnchor.constraint(equalToConstant: width/16*9).isActive = true
conteiningView.addSubview(userImage)
userImage.centerYAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true // the vertical center is now set nicely at bottom edge of my top view.
userImage.centerXAnchor.constraint(equalTo: topView.centerXAnchor).isActive = true
userImage.widthAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.heightAnchor.constraint(equalToConstant: width/2).isActive = true
userImage.layer.cornerRadius = width/4
conteiningView.addSubview(userNameLabel)
userNameLabel.topAnchor.constraint(equalTo: userImage.bottomAnchor, constant: 2).isActive = true
userNameLabel.centerXAnchor.constraint(equalTo: userImage.centerXAnchor).isActive = true
userNameLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
userNameLabel.heightAnchor.constraint(equalToConstant: 35).isActive = true

Resources