Search code examples
iosswiftuitableviewuitableviewautomaticdimension

UITableViewAutomaticDimension works not as expected. Swift


After reading Ray Wenderlich guide for "Self-sizing Table View Cells" as well as this question and answers to it, I've decided to ask all of you for a help.

Have a programmically created cell:

import UIKit

class NotesCell: UITableViewCell {
lazy private var cellCaption: UILabel = {
    let label = UILabel()

    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
    label.numberOfLines = 0
    label.lineBreakMode = .byWordWrapping

    return label
}()

func configure(with note: NotesModel) {
    cellCaption.text = note.name

    contentView.addSubview(cellCaption)
}

override func layoutSubviews() {
    super.layoutSubviews()

    NSLayoutConstraint.activate([
        cellCaption.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
        cellCaption.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
        cellCaption.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
//            cellCaption.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
cellCaption.bottomAnchor.constraint(greaterThanOrEqualTo: contentView.bottomAnchor, constant: -8)
            ])

//        cellCaption.sizeToFit()
//        cellCaption.layoutIfNeeded()
}
}

The table view controller uses UITableViewAutomaticDimension in the delegate methods:

extension NotesTableViewController {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

}

As a result, the longest caption is indicated fully, but the cell anyway has the same height as all other.

enter image description here

Some update!

I've already tried to put into viewDidLoad() following code:

tableView.rowHeight = 44
tableView.estimatedRowHeight = UITableViewAutomaticDimension

with enabling delegate methods and disabling them as well. The result is the same :(


Solution

  • You're doing a number of things wrong, but the main point is your use of greaterThanOrEqualTo:.

    Instead, it should be:

    cellCaption.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
    

    Also, your current code is adding a new label as a subview every time you set the text. Cells are reused, so you only want to add the label when the cell is created.

    Next, the correct properties for the table are:

        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 44
    

    Put those two lines in viewDidLoad() of your table view controller, and do not implement heightForRowAt or estimatedHeightForRowAt functions. You can delete your extension entirely.

    And finally, you only need to set the constraints once. Definitely NOT in layoutSubviews().

    Here's a full example:

    //
    //  NotesTableViewController.swift
    //
    //  Created by Don Mag on 8/29/18.
    //
    
    import UIKit
    
    class NotesModel: NSObject {
        var name: String = ""
    }
    
    class NotesCell: UITableViewCell {
        lazy private var cellCaption: UILabel = {
            let label = UILabel()
    
            label.translatesAutoresizingMaskIntoConstraints = false
            label.font = UIFont.systemFont(ofSize: 20, weight: UIFont.Weight.medium)
            label.numberOfLines = 0
            label.lineBreakMode = .byWordWrapping
    
            return label
        }()
    
        func configure(with note: NotesModel) {
            cellCaption.text = note.name
        }
    
        override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            commonInit()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            commonInit()
        }
    
        func commonInit() -> Void {
    
            contentView.addSubview(cellCaption)
    
            NSLayoutConstraint.activate([
                cellCaption.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
                cellCaption.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
                cellCaption.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
                cellCaption.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
                ])
    
        }
    
    }
    
    class NotesTableViewController: UITableViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tableView.rowHeight = UITableViewAutomaticDimension
            tableView.estimatedRowHeight = 44
        }
    
        // MARK: - Table view data source
    
        override func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 8
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "NotesCell", for: indexPath) as! NotesCell
    
            let m = NotesModel()
    
            if indexPath.row == 3 {
                m.name = "This is a very long caption. It will demonstrate how the cell height is auto-sized when the text is long enough to wrap to multiple lines."
            } else {
                m.name = "Caption \(indexPath.row)"
            }
    
            cell.configure(with: m)
    
            return cell
        }
    
    }
    

    Result:

    enter image description here