Search code examples
swiftuitableviewuitableviewautomaticdimension

Dynamic Height in TableViewCell Not Working


I've spent over six hours researching and unsuccessfully attempting to create dynamic cells today. I've found a handful of great tutorials on it too. I'm doing something wrong and am hoping someone can tell me what.

My app: The table starts empty. Clicking an add button presents a modal VC, which has two text input fields (title/desc). That data is passed back to the table. An unwind method in the main VC reloads the table data.

Problem: If the description part of the cell is longer than one line, it just gets cut off.

Here is the code in my main view controller, which houses the table:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

//***** ----- ***** ------ ***** ----- ***** ----- *****
//Initial Setup
//***** ----- ***** ------ ***** ----- ***** ----- *****

@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

}

override func viewDidAppear(animated: Bool) {
    tableView.reloadData()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

//Automatic table reload upon unwind
@IBAction func unwindToMain(segue: UIStoryboardSegue) {
    //RW's code for dynamic cell height
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 80.0

    tableView.reloadData()
}

//***** ----- ***** ------ ***** ----- ***** ----- *****
//Functions
//***** ----- ***** ------ ***** ----- ***** ----- *****

//Swipe to delete task cell
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if(editingStyle == UITableViewCellEditingStyle.Delete) {
        taskMgr.tasks.removeAtIndex(indexPath.row)
        tableView.reloadData()
    }
}



//***** ----- ***** ------ ***** ----- ***** ----- *****
//Table View & Cell Setup
//***** ----- ***** ------ ***** ----- ***** ----- *****

//Tells the table how many rows it should render
//*Looks to the taskMgr to count tasks, creates equal # of rows
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return taskMgr.tasks.count
}

//Creates the individual cells. If the above function returns 3, this runs 3 times
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "BasicCell")

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 80.0

    cell.textLabel?.text = taskMgr.tasks[indexPath.row].name
    cell.detailTextLabel?.text = taskMgr.tasks[indexPath.row].desc

    return cell
}

}

Code in my modal/editor view:

class EditorView: UIViewController, UITextFieldDelegate {

@IBOutlet var txtTask: UITextField!
@IBOutlet var txtDesc: UITextView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
}



//***** ----- ***** ------ ***** ----- ***** ----- *****
//Keyboard Functionality
//***** ----- ***** ------ ***** ----- ***** ----- *****

//Dismisses keyboard upon tapping the return key
func textFieldShouldReturn(textField: UITextField) -> Bool{
    textField.resignFirstResponder()
    return true
}

//Dismisses keyboard upon touch outside text boxes
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    self.view.endEditing(true)
}

//***** ----- ***** ------ ***** ----- ***** ----- *****
//Code to run upon Editor being dismissed
//***** ----- ***** ------ ***** ----- ***** ----- *****

//Actions performed upon tapping the 'Finish' button
@IBAction func dismissEditorView(sender: AnyObject) {

    //Calls a previously written function to append textfield input to the table array
    taskMgr.addTask(txtTask.text, desc: txtDesc.text)

    //Dismisses the EditorView
    dismissViewControllerAnimated(true, completion: nil)

}
}

My task manager file housing a function to append new items to table:

import UIKit

//This has global scope!!
var taskMgr: TaskManager = TaskManager()

struct task {
    var name = "Name TBD"
    var desc = "Desc TBD"
}

class TaskManager: NSObject {

    var tasks = [task]()

    func addTask(name: String, desc: String){
        tasks.append(task(name: name, desc: desc))    
    }
}

Tutorials I've been referencing:

http://natashatherobot.com/ios-8-self-sizing-table-view-cells-with-dynamic-type/

http://www.raywenderlich.com/87975/dynamic-table-view-cell-height-ios-8-swift

http://coding.tabasoft.it/ios/ios8-self-sizing-uitableview-cells/

http://useyourloaf.com/blog/2014/08/07/self-sizing-table-view-cells.html

What I think I am doing right:

-Table added to view, prototype cell added to table

-Two labels added to the cell. Auto layout added. Top label constraints are to container (leading, trailing, and top). Bottom constraint is to second label. Bottom label has top to second label, then container for leading, trailing and bottom.

-Top label has 751 priority level for compression resistance and content hugging. Bottom label has 750 priority for all four.

-Both labels have preferred width set to automatic. Explicit is unchecked


Solution

  • Solved my problem. Basically the prototype cell's title and description label weren't linked to the code with IBOutlets. My app worked because when I ran it and added a table item it just used the default title and subtitle. However, those did not have auto layout constraints or lines set to 0, so the height behaved statically.

    I added a custom class file and set the cell's custom class as the new class, and then linked IBOutlet's to this file. So that file looked like this:

    import UIKit
    
    class CustomTableViewCell: UITableViewCell {
    
        @IBOutlet var nameLabel:UILabel!
        @IBOutlet var descLabel:UILabel!
    
        override func awakeFromNib() {
            super.awakeFromNib()
            // Initialization code
        }
    
        override func setSelected(selected: Bool, animated: Bool) {
            super.setSelected(selected, animated: animated)
    
            // Configure the view for the selected state
        }
    
    }
    

    Then I just slightly changed my main ViewController code (shown in my above question. Here's where the change was:

    //Creates the individual cells. If the above function returns 3, this runs 3 times
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
        let cellIdentifier = "BasicCell"
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CustomTableViewCell
    
        cell.nameLabel.text = taskMgr.tasks[indexPath.row].name
        cell.descLabel.text = taskMgr.tasks[indexPath.row].desc
    

    And that was it. Now it works perfectly, each time I add an item, everything sizes properly.