0

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

Shin622
  • 645
  • 3
  • 7
  • 22
  • Why you are adding multiple label? I mean you can insert next line character – SPatel Oct 28 '17 at 10:52
  • @user3783161 loop inside cellForRowAt is not a good idea, You can add label , views from storyboard.Have you googled your doubt? – Tushar Sharma Oct 28 '17 at 10:54
  • https://stackoverflow.com/a/30300870/8779236 look this answer – a.afanasiev Oct 28 '17 at 10:56
  • @user3783161 Follow this question b'coz you have similar issues. https://stackoverflow.com/questions/18600047/multiple-lines-of-a-label-in-a-custom-uitableviewcell – BuLB JoBs Oct 28 '17 at 11:03
  • @SPatel : I am need multiple uilabel because each label have some special attributes (ex: display on the right side, click on it will perform a action) that can not be done in only one label. Some data has those attributes and some data is not. – Shin622 Oct 28 '17 at 12:27
  • @tushar Sharma thanks for your advice, but I have to faced to dynamic UILabel based on API return data. Do you have any ideas to achieve this problem – Shin622 Oct 28 '17 at 12:33
  • try UIstackView (vertical), and add label inside stack view – SPatel Oct 29 '17 at 02:46
  • @SPatel do you have any example, Please – Shin622 Oct 29 '17 at 07:34

4 Answers4

2

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.

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • I added the constraints (please see edited), but not working, are there something wrong – Shin622 Oct 28 '17 at 13:28
  • 1
    There is some other mistake in your code that I overlooked at first. I updated my answer and added a detailed description of how to fix this. – Mischa Oct 28 '17 at 15:26
  • Thank you for the detailed information, just have one more question. If I setup cell layout before dequeueing, How can I create dynamic UILabel cause I dont know how many label that need to use – Shin622 Oct 29 '17 at 08:17
  • You only assign the _text_ to the label inside `tableView(_:,cellForRowAt:)`. Everything else happens automatically as the label computes its _intrinsic content size_ from the text and the constraints. The only thing you need to make sure is that you set the label's `numberOfLines` to 0 (which you already did in your sample code). This should also be done during setup. – Mischa Oct 29 '17 at 11:37
1

used this code.

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
   return UITableViewAutomaticDimension
}
BuLB JoBs
  • 841
  • 4
  • 20
  • 1. Greater than or equal constraint for the label height isn't required. 2. These are not all the necessary steps. – FreeNickname Oct 28 '17 at 11:11
  • This is obsolete, as the code example in the question already sets `self.dictionaryTableView.rowHeight = UITableViewAutomaticDimension`. – Mischa Oct 28 '17 at 11:19
1

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

Community
  • 1
  • 1
muazhud
  • 914
  • 7
  • 18
  • thanks for the advice. My table view have dynamic sections and each section has it data which I want to use UILabel to achive – Shin622 Oct 28 '17 at 13:31
0

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)
Shin622
  • 645
  • 3
  • 7
  • 22