1

I have looked everywhere, although, I am still having trouble finding a solution to this problem. I am creating a todo app in Xcode, and I am having trouble getting my cells to auto-size when I edit the labels text to be longer (multiple lines). Here's what I have done so far:

  • Set label's Lines to 0
  • Set label's Line Break to Word Wrap
  • Added these constraints to the label inside of the cell
  • Added the following code inside of HomeTableViewController.swift:

    override func viewWillAppear(_ animated: Bool) {
       tableView.rowHeight = UITableViewAutomaticDimension
       tableView.estimatedRowHeight = 85.0
    }
    

Still, I'm having this problem... (notice line 1)

This is becoming really frustrating, and I would appreciate it if anyone could help me out!

Entire HomeViewController.swift code:

//
//  HomeTableViewController.swift
//  Do
//
//  Created by Justin Owens on 12/16/17.
//  Copyright © 2017 Justin Owens. All rights reserved.
//

import UIKit

class HomeTableViewController: UITableViewController, AddItemTableViewControllerDelegate {

    // MARK:

    func addItemTableViewControllerCanceled(_ controller: AddItemTableViewController) {
        navigationController?.popViewController(animated: true)
    }

    func addItemTableViewController(_ controller: AddItemTableViewController, didFinishEditing item: DoItem) {
        if let index = items.index(of: item)
        {
            let indexPath = IndexPath(row: index, section: 0)

            if let cell = tableView.cellForRow(at: indexPath)
            {
                configureLabels(for: cell, with: item)
            }
        }

        navigationController?.popViewController(animated: true)
    }

    func addItemTableViewController(_ controller: AddItemTableViewController, didFinishAdding item: DoItem) {
        let newRowIndex = items.count
        let indexPath = IndexPath(row: newRowIndex, section: 0)
        let indexPaths = [indexPath]

        items.append(item)
        tableView.insertRows(at: indexPaths, with: .automatic)

        navigationController?.popViewController(animated: true)
    }

    // MARK:

    var groups = [""]

    // MARK:

    var items: [DoItem]

    required init?(coder aDecoder: NSCoder)
    {
        items = [DoItem]()

        let row0Item = DoItem()
        row0Item.text = "NSA interview"
        row0Item.checked = false
        items.append(row0Item)

        let row1Item = DoItem()
        row1Item.text = "Work on app"
        row1Item.checked = false
        items.append(row1Item)

        let row2Item = DoItem()
        row2Item.text = "Do homework"
        row2Item.checked = false
        items.append(row2Item)

        let row3Item = DoItem()
        row3Item.text = "Catch up on Shameless"
        row3Item.checked = false
        items.append(row3Item)

        super.init(coder: aDecoder)
    }

    // MARK:

    override func viewDidLoad()
    {
        super.viewDidLoad()

        navigationController?.navigationBar.prefersLargeTitles = true
    }

    override func viewWillAppear(_ animated: Bool)
    {
        super.viewWillAppear(animated)

        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 85.0
    }

    // MARK:

    override func numberOfSections(in tableView: UITableView) -> Int {
        return groups.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DoItemIdentifier", for: indexPath)
        let item = items[indexPath.row]

        configureLabels(for: cell, with: item)
        configureCheckmark(for: cell, with: item)

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        if let cell = tableView.cellForRow(at: indexPath)
        {
            let item = items[indexPath.row]

            item.toggleCheck()
            configureCheckmark(for: cell, with: item)
        }

        tableView.deselectRow(at: indexPath, animated: true)
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
    {
        let indexPaths = [indexPath]

        items.remove(at: indexPath.row)
        tableView.deleteRows(at: indexPaths, with: .automatic)
    }

    override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "Section \(section)"
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "AddItem"
        {
            let controller = segue.destination as! AddItemTableViewController
            controller.delegate = self
        }
        else if segue.identifier == "EditItem"
        {
            let controller = segue.destination as! AddItemTableViewController
            controller.delegate = self

            if let indexPath = tableView.indexPath(for: sender as! UITableViewCell)
            {
                controller.itemToEdit = items[indexPath.row]
            }
        }
    }

    // MARK:

    func configureLabels(for cell: UITableViewCell, with item: DoItem)
    {
        let mainLabel = cell.viewWithTag(1000) as! UILabel

        mainLabel.text = item.text
    }

    func configureCheckmark(for cell: UITableViewCell, with item: DoItem)
    {
        let checkmark = cell.viewWithTag(1001)

        if item.checked
        {
            checkmark?.isHidden = false
        }
        else
        {
            checkmark?.isHidden = true
        }
    }

    // MARK:

    @IBAction func addButtonAction(_ sender: Any)
    {
        let newRowIndex = items.count
        let item = DoItem()
        let indexPath = IndexPath(row: newRowIndex, section: 0)
        let indexPaths = [indexPath]

        item.text = ""
        item.checked = false

        items.append(item)
        tableView.insertRows(at: indexPaths, with: .automatic)
    }
}

P.S. I attempted to add in tableView.reloadData() whenever the user presses the Done button after editing a label on the AddItemViewController screen; needless to say, this didn't change anything.

mugx
  • 9,869
  • 3
  • 43
  • 55

2 Answers2

2

You miss bottom constraint. The contentView of the cell does not know how to stretch, by constraining the bottom of the label to the bottom of the cell you are telling the cell to resize itself based on the label size.

See my other answer for more details.

I can see some other bad smells in your code.

First of all, change:

tableView.insertRows(at: indexPaths, with: .automatic)

to:

tableView.beginUpdates()
tableView.insertRows(at: indexPaths, with: .automatic)
tableView.endUpdates()

(and same applies to removing rows).

Then move:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 85.0

to viewDidLoad - it's enough to configure it once at the beginning.

Also drop the label width constraint - you already set it to be constrained to leading and trailing anchors (also make sure you did not set the height constraint).

Finally, use @IBOutlets instead of using tags to identify views created in storyboards (I am referring to your handling of the cell configuration).

Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
  • I expanded my label to reach the top and bottom of the cell and set new constraints. This unfortunately did not solve the problem. – Justin Owens Jan 19 '18 at 00:48
  • @JustinOwens show us where you set the text of the label.. and update the question with new constraints.. also, show the whole code of the HomeTableViewController that has anything to do with the tableView – Milan Nosáľ Jan 19 '18 at 00:55
  • 1
    @JustinOwens there are some issues in your code that I mentioned in the updated answer. However, I don't think any of those would cause your issue (but still, it's better to deal with those issues to prevent other bugs. Now from your code it's hard to guess what's the problem. Can you show us the cell prototype from storyboards along with all the constraints that are defined in the cell? Also, can you make sure that the label with tag 1000 is really the one that you set up constraints for? – Milan Nosáľ Jan 19 '18 at 01:23
0

I've created a basic application with a UITableView whose cells can display multiple lines of text.

Apologies in advance as my example app is written in Objective-C.

The table view controller (TACTableViewController) is created in the app delegate.

//
//  TACAppDelegate.m
//  BasicTable
//
//  Created by Steve Developer on 24/01/18.
//  Copyright (c) 2018 The App Company. All rights reserved.
//

#import "TACAppDelegate.h"
#import "TACTableViewController.h"

@implementation TACAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.

    // Create table view controller
    TACTableViewController *tableViewController = [[TACTableViewController alloc] init];

    // Place table view controller's table view in the window hierarchy
    self.window.rootViewController = tableViewController;

    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
...

The table view controller (TACTableViewController) is a subclass of UITableViewController.

The init and initWithStyle methods in the table view controller are the initialisers and are hopefully self explanatory.

NB 1: For this example app I have hard coded the value returned from method numberOfRowsInSection.

NB 2: In order to customise the behaviour of the cells in the table view, I created a XIB file (TACTableViewCell.xib) and a subclass of UITableViewCell (TACTableViewCell).

NB 3: The viewDidLoad method is overridden only so the cells of the table view can be reused, which in this case is pointless as there are only two cells.

//
//  TACTableViewController.m
//  BasicTable
//
//  Created by Steve Developer on 24/01/18.
//  Copyright (c) 2018 The App Company. All rights reserved.
//

#import "TACTableViewController.h"
#import "TACTableViewCell.h"

@implementation TACTableViewController

- (instancetype)init
{
    // Call the superclass's designated initializer
    self = [super initWithStyle:UITableViewStyleGrouped];

    return self;
}

- (instancetype)initWithStyle:(UITableViewStyle)style
{
    self = [self init];
    return self;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 2;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Get a new or recycled cell
    TACTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TACTableViewCell" forIndexPath:indexPath];

    // You would normally retrieve data using your model class at this point

    NSString *string = @"The quick brown fox jumped over the lazy dog.";
    cell.label.text = string;

    return cell;
}

// Required to override this method in order to recycle table view cells
- (void)viewDidLoad
{
    [super viewDidLoad];

    // Load the NIB file
    UINib *nib = [UINib nibWithNibName:@"TACTableViewCell" bundle:nil];

    // Register this NIB, which contains the cell
    [self.tableView registerNib:nib forCellReuseIdentifier:@"TACTableViewCell"];
}

@end

TACTableViewCell is a subclass of UITableViewCell and its header and implementation files are almost empty apart from the IBOutlet, named label, in the header. This outlet (will) point to the UILabel in TACTableViewCell.xib.

The header file:

//
//  TACTableViewCell.h
//  BasicTable
//
//  Created by Steve Developer on 24/01/18.
//  Copyright (c) 2018 The App Company. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface TACTableViewCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UILabel *label;

@end

The implemenation file:

//
//  TACTableViewCell.m
//  BasicTable
//
//  Created by Steve Developer on 24/01/18.
//  Copyright (c) 2018 The App Company. All rights reserved.
//

#import "TACTableViewCell.h"

@implementation TACTableViewCell

@end

To create the TACTableViewCell.xib file do the following:

  1. Add a new empty User Interface file to your project, name it TACTableViewCell.xib.

  2. Open the XIB file and add a Table View Cell to the canvas.

  3. Add a Label to the Table View Cell (I stretched the label to almost the width of the Table View Cell).

Label

  1. Specify the spacing to the label's nearest neighbour by adding four constraints.

Spacing to its nearest neighbour

The newly added constraints are displayed in the dock to the left of the canvas.

enter image description here

  1. Change the Lines attribute for the Label from 1 to 0.

enter image description here

  1. Connect the IBOutlet, named label, in the TACTableViewCell class with the Label in Interface Builder, by clicking the circle in the margin next to the line shown below and dragging to the Label in the canvas.

enter image description here

At this point the app should build and run successfully and display something similar to that below.

enter image description here

Steve
  • 696
  • 7
  • 15