Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set the height of a cell depending on a UILabel with a typewriter effect

I have a UILabel in a cell of UITableView.

To adjust the height of the cell depending the height of the label, it's ok, it works perfectly.

But I need to add another constraints. I need to display the UILabel with a typewriter effect (letter by letter).

My extension for the effect works well:

extension UILabel{

    func setTextWithTypeAnimation(id:String, typedText: String, pauseCharacterArray: [Int:Double], characterInterval: TimeInterval = 0.06 ) {

        text = ""

        let group = DispatchGroup()
        group.enter()

        DispatchQueue.global(qos: .userInteractive).async {

            for (index, character) in typedText.characters.enumerated() {

                DispatchQueue.main.async {
                    self.text = self.text! + String(character)
                }

                Thread.sleep(forTimeInterval: characterInterval)
            }

        group.leave()
    }

    group.notify(queue: .main) {
        //do something
    }
}

I tried to call this function in my configureCell fund :

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "ParagraphTableViewCell", for: indexPath) as! ParagraphTableViewCell

    cell.delegate = self
    self.configureCell(cell: cell, atIndexPath: indexPath)
    return cell
 }


func configureCell(cell: ParagraphTableViewCell, atIndexPath indexPath: IndexPath) {

    let paragraph = paragraphArray[indexPath.row] as! Paragraph

    let pauseCharactersArray:[Int:Double] = [1:0, 6:0]
    cell.dialogueLabel.setTextWithTypeAnimation(id:"intro", typedText: "lorem ipsum", pauseCharacterArray: pauseCharactersArray)
}

But the label doesn't appear. I think it's because the height of the label in the cell is set to 0, and it's never updated.

I don't know how to adjust the height of the cell "in live" (every time a character is displayed)

EDIT

I use autolayout

EDIT 2

The typewriter effect, run in a simple UILabel without UITableView:

gif

The cell set in the xib:

xib

When I run, the UILabel doesn't appear:

run

like image 986
cmii Avatar asked Mar 30 '17 12:03

cmii


2 Answers

I was able to achieve using a different method.

  • I created a stack view to group the label and the buttons (and a sub stack view to line up your buttons).
  • On button's stack view, I pinned the height through IB.
  • On parent stack view. I pinned the stack view to cell's margin.
  • On label, I pinned both sides to fit the stack view (do not pin the bottom side, you are free to pin the top side)

Here is my IB Screen Shot to use as reference.

  • On your ViewController, set the following properties:

    yourTableView.rowHeight = UITableViewAutomaticDimension
    yourTableView.rowHeight.estimatedRowHeight = 40 // You should set an initial estimated row height here, the number 40 was chosen arbitrarily
    
  • I edited your method to add an callback.

    func setTextWithTypeAnimation(id:String, typedText: String, pauseCharacterArray: [Int:Double], characterInterval: TimeInterval = 0.06, callBackAfterCharacterInsertion:(()->())?) {
    
      text = ""
    
      let group = DispatchGroup()
      group.enter()
    
      DispatchQueue.global(qos: .userInteractive).async {
    
        for (_, character) in typedText.characters.enumerated() {
    
            DispatchQueue.main.async {
                self.text = self.text! + String(character)
                callBackAfterCharacterInsertion?()
            }
    
            Thread.sleep(forTimeInterval: characterInterval)
        }
    
        group.leave()
      }
    
      group.notify(queue: .main) {
        //do something
      }
    }
    
  • And through the callback, I called the beginUpdates() and endUpdates(), from the TableView, after each character update.

Hope this help you in any way.

like image 130
VKawai Avatar answered Sep 30 '22 20:09

VKawai


I found a solution but I don't know if it's a very clean one.

I added layoutIfNeeded() in configureCell method :

func configureCell(cell: ParagraphTableViewCell, atIndexPath indexPath: IndexPath) {

    let paragraph = paragraphArray[indexPath.row] as! Paragraph

    cell.layoutIfNeeded() 

    let pauseCharactersArray:[Int:Double] = [1:0, 6:0]
    cell.dialogueLabel.setTextWithTypeAnimation(id:"intro", typedText: "lorem ipsum", pauseCharacterArray: pauseCharactersArray)

}

And I added the heightForRowAtIndexPath method in my controller, which returns the height of my label:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

    let cell = tableView.dequeueReusableCell(withIdentifier: "ParagraphTableViewCell") as! ParagraphTableViewCell

    return cell.dialogueLabel.frame.height
}

It works, but I don't understand exactly why, because in heightForRowAtIndexPath I'm returning the height of the label and not the height of the cell.

If someone has a better solution, I'm interested.

like image 21
cmii Avatar answered Sep 30 '22 22:09

cmii