Shin622
Shin622

Reputation: 645

UITableViewCell height with dynamic subview swift

I have a UITableView that UITableViewCell has a dynamic UILabel which will be added based on data i get from the database.

I'm using something like

let testing = ["A", "B", "C", "D"] // This array is dynamic data
self.dictionaryTableView.estimatedRowHeight = 44
self.dictionaryTableView.rowHeight = UITableViewAutomaticDimension

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "vocabCell", for: indexPath)
    var y = 0
    for var i in 0..<self.testing.count {
        let lbl = UILabel(frame: CGRect(x: 0, y: y, width: 50, height: 25))
        lbl.text = self.testing[i]
        lbl.numberOfLines = 0
        cell.addSubview(lbl)
        y += 20       
    }
    return cell
}

But UITableViewCell height does not stretch automatically to display all content cell. Please help Here is the result enter image description here

EDIT I added constraint for the uilabel in UITableView cellForRowAtIndexPath, the constraints are working (I knew it when I expend the cell height), but cell height not automaticly strech out

var bottomAnchor: NSLayoutYAxisAnchor = NSLayoutYAxisAnchor()
for var i in 0..<self.testing.count {
    let lbl = UILabel()
    lbl.text = self.testing[i]
    lbl.numberOfLines = 0
    lbl.translatesAutoresizingMaskIntoConstraints = false

    cell.addSubview(lbl)
    lbl.leftAnchor.constraint(equalTo: cell.leftAnchor, constant: 16).isActive = true
    lbl.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true
    lbl.heightAnchor.constraint(equalToConstant: 25).isActive = true

    if i == 0 {
        lbl.topAnchor.constraint(equalTo: cell.topAnchor).isActive = true
    } else {
        lbl.topAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }
    bottomAnchor = lbl.bottomAnchor
}
return cell

Many thanks

Upvotes: 0

Views: 1862

Answers (4)

Shin622
Shin622

Reputation: 645

I found the answers here

I added a constraint to my UITableViewCell bottom with the following code and it's working, but I don't know what exactly this line doing (I don't know the parameters too)

Could anyone help me explain this code

let bottomSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: lbl, attribute: NSLayoutAttribute.bottomMargin, relatedBy: NSLayoutRelation.equal, toItem: cell.contentView, attribute: NSLayoutAttribute.bottomMargin, multiplier: 1, constant: -8)
cell.contentView.addConstraint(bottomSpaceConstraint)

Upvotes: 0

Mischa
Mischa

Reputation: 17241

There are two things that you need to fix in your implementation:

  1. Create the layout of your cell before dequeueing it in tableView(_:, cellForRowAt:).

    For efficiency reasons, table views reuse the same cells over and over again when the user scrolls. That's why you use this weird "dequeuing" function rather than simply instantiating a new cell.

    let cell = tableView.dequeueReusableCell(withIdentifier: "vocabCell", for: indexPath)
    

    will always try to return a used cell that just scrolled out of the view. Only if there are no recycled cells available (for example when dequeueing the first couple of cells) the table view will create new instances of a cell.

    The recycling of cells is the very reason why you should never create your cell's layout inside tableView(_:, cellForRowAt:), for example by adding a label. When the cell is being reused, another label will be added on top of the label that you added before and when it's being reused a second time, you'll end up with three overlapping labels etc.

    The same applies to constraints. When you add constraints to a cell in tableView(_:, cellForRowAt:) without removing existing ones, more and more constraints will be added while the user is scrolling, most likely resulting in serious constraint conflicts.

    Instead, setup your cell's layout before dequeueing it. There are several ways to achieve this:

    • If you use a UITableViewController inside a storyboard, you can create dynamic prototypes and lay out the cells directly in the storyboard. You could, for example, drag a label to a prototype cell there and create an outlet for it in a custom UITableViewCell subclass.

    • You can create a XIB file for your cell, open it in Interface Builder and create your layout there. Again, you need to create a UITableViewCell subclass with the appropriate outlets and associate it with your XIB file.

    • You can create a UITableViewCell subclass and set up your layout purely in code, for example inside the cell's initializer.

  2. You need to use Auto Layout.

    If you create your layout in Interface Builder, you just need to add the necessary constraints and you're good to go. If you create your layout in code, you need to set the translatesAutoresizingMaskIntoConstraints property to false for all views that you wish to constrain, for example:

    lbl.translatesAutoresizingMaskIntoConstraints = false
    

    In your particular layout, you need to constrain the label at the left, right, top and bottom with the corresponding edges of your cell's contentView.

    If you don't know how to do that, please read Apple's Auto Layout Guide. (It's usually a better idea to do this in Interface Builder rather than in code.)

    A very detailed description of how to use "Auto Layout in UITableView for dynamic cell layouts & variable row heights" can be found here.

Upvotes: 2

muazhud
muazhud

Reputation: 954

You can use UITableView section to render the data instead of adding the view programmatically. Here's the example:

class ViewController: UITableViewController {

    private var dataSectionOne = ["Data 1", "Data 2"]
    // This can be your dynamic data. 
    // Once the data changed, called tableView.reloadData() to update the view.
    private var dataSectionTwo = ["A", "B", "C"] 

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 44
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 2
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            return dataSectionOne.count
        } else if section == 1 {
            return dataSectionTwo.count
        }
        return 0
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        if indexPath.section == 0 {
            cell.textLabel?.text = dataSectionOne[indexPath.row]
        } else if indexPath.section == 1 {
            cell.textLabel?.text = dataSectionTwo[indexPath.row]
        }
        return cell
    }

}

The results:

Demo

Upvotes: 1

BuLB JoBs
BuLB JoBs

Reputation: 861

used this code.

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
   return UITableViewAutomaticDimension
}

Upvotes: 1

Related Questions