31

I'm trying to implement a UITableView that will behave similarly to the timeline of a twitter client. Right now I'm simply attempting to get two labels inside a UITableViewCell. As recommended by this Stack Overflow answer, I'm using a different reuseIdentifier for each layouts. My layouts are simple, consisting of either a single label or two labels. Eventually I will be adjusting the height of the UITableViewCells but first I need to get the cells populated with content.

I can get the labels so show up if I set their frame with initWithFrame:, however the constraints aren't being implemented.

  • Question: What is preventing the labels and constraints from appearing? I'm clearly missing something in my implementation of the UITableViewCell but I have no idea what it is.

  • Secondary question: Am I registering the UITableViewCell class correctly for each reuseIdentifier in viewDidLoad?

This might come across as being difficult but Interface Builder confuses me, I would like to accomplish this all in code.

Here is the code for the custom UITableViewCell named TVTCell.h:

static NSString * const kCellIDTitle = @"CellWithTitle";
static NSString * const kCellIDTitleMain = @"CellWithTitleMain";

@interface TVTCell : UITableViewCell
{
    NSString *reuseID;
}

@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *mainLabel;

@end

And TVTCell.m:

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        reuseID = reuseIdentifier;

        nameLabel = [[UILabel alloc] init];
        [nameLabel setTextColor:[UIColor blackColor]];
        [nameLabel setBackgroundColor:[UIColor colorWithHue:32 saturation:100 brightness:63 alpha:1]];
        [nameLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:18.0f]];
        [nameLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.contentView addSubview:nameLabel];

        mainLabel = [[UILabel alloc] init];
        [mainLabel setTextColor:[UIColor blackColor]];
        [mainLabel setBackgroundColor:[UIColor colorWithHue:66 saturation:100 brightness:63 alpha:1]];
        [mainLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:18.0f]];
        [mainLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.contentView addSubview:mainLabel];

        [self.contentView setTranslatesAutoresizingMaskIntoConstraints:NO];

    }
    return self;
}


- (void)updateConstraints
{
    [super updateConstraints];

    NSDictionary *views = NSDictionaryOfVariableBindings(nameLabel, mainLabel);
    if (reuseID == kCellIDTitle) {
        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[nameLabel]|"
                                                options: NSLayoutFormatAlignAllCenterX
                                                metrics:nil
                                                  views:views];
        [self.contentView addConstraints:constraints];
        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameLabel]|"
                                                              options: NSLayoutFormatAlignAllCenterX
                                                              metrics:nil
                                                                views:views];
        [self.contentView addConstraints:constraints];
    }
    if (reuseID == kCellIDTitleMain) {
        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[nameLabel]|"
                                                                       options: NSLayoutFormatAlignAllCenterX
                                                                       metrics:nil
                                                                         views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mainLabel]|"
                                                                       options: NSLayoutFormatAlignAllCenterX
                                                                       metrics:nil
                                                                         views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameLabel][mainLabel]|"
                                                              options: NSLayoutFormatAlignAllLeft
                                                              metrics:nil
                                                                views:views];
        [self.contentView addConstraints:constraints];

        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:nameLabel
                                     attribute:NSLayoutAttributeHeight
                                     relatedBy:NSLayoutRelationEqual
                                        toItem:nil
                                     attribute:NSLayoutAttributeNotAnAttribute
                                    multiplier:0.0
                                      constant:44.0]];
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:nameLabel
                                                                     attribute:NSLayoutAttributeWidth
                                                                     relatedBy:NSLayoutRelationEqual
                                                                        toItem:self.contentView
                                                                     attribute:NSLayoutAttributeNotAnAttribute
                                                                    multiplier:0.0
                                                                      constant:1]];
    }
}

Sorry, ton of code. Here's my UITableView's tableView:cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == 0 || indexPath.row == 2 || indexPath.row == 3) {
        TVTCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIDTitle forIndexPath:indexPath];

        [[cell nameLabel] setText:[nameArray objectAtIndex:indexPath.row]];

        return cell;
    } else if (indexPath.row == 1 || indexPath.row == 4 || indexPath.row == 5) {
        TVTCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIDTitleMain forIndexPath:indexPath];

        [[cell nameLabel] setText:[nameArray objectAtIndex:indexPath.row]];
        [[cell mainLabel] setText:[dataArray objectAtIndex:indexPath.row]];

        return cell;
    } else
    {
        UITableViewCell *badCell = [[UITableViewCell alloc] init];
        NSLog(@"Warning! returning a cell that shouldnt be here");
        badCell.textLabel.text = @"Warning!";
        return badCell;
    }
}

And lastly, the UITableView's viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[self tableView] registerClass:[TVTCell class] forCellReuseIdentifier:kCellIDTitle];
    [[self tableView] registerClass:[TVTCell class] forCellReuseIdentifier:kCellIDTitleMain];
}
Community
  • 1
  • 1
Shawn Throop
  • 1,281
  • 1
  • 13
  • 28

2 Answers2

32

There are several things wrong with your code. First, I think you'll find, if you do some logging, that updateConstraints is never called. I would put all the code in the init method. Also, there are several things wrong in your constraints. The constraint where you set the height to 44 is not needed since you already have the labels pinned to the to and bottom of the cell. I don't know what you're trying to do with that last one, it looks like that would make the nameLabel 1 point wide. Also, you shouldn't set the translatesAutoresizingMaskIntoConstraints to NO for the content view, that causes weird effects. So this is the code I think you want:

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        reuseID = reuseIdentifier;

        nameLabel = [[UILabel alloc] init];
        [nameLabel setTextColor:[UIColor blackColor]];
        [nameLabel setBackgroundColor:[UIColor colorWithHue:32 saturation:100 brightness:63 alpha:1]];
        [nameLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:18.0f]];
        [nameLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.contentView addSubview:nameLabel];

        mainLabel = [[UILabel alloc] init];
        [mainLabel setTextColor:[UIColor blackColor]];
        [mainLabel setBackgroundColor:[UIColor colorWithHue:66 saturation:100 brightness:63 alpha:1]];
        [mainLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:18.0f]];
        [mainLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.contentView addSubview:mainLabel];

        NSDictionary *views = NSDictionaryOfVariableBindings(nameLabel, mainLabel);
        if (reuseID == kCellIDTitle) {
            NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[nameLabel]|"
                                                                           options: 0
                                                                           metrics:nil
                                                                             views:views];
            [self.contentView addConstraints:constraints];
            constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameLabel]|"
                                                                  options: 0
                                                                  metrics:nil
                                                                    views:views];
            [self.contentView addConstraints:constraints];
        }
        if (reuseID == kCellIDTitleMain) {
            NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[nameLabel]|"
                                                                           options:0
                                                                           metrics:nil
                                                                             views:views];
            [self.contentView addConstraints:constraints];

            constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mainLabel]|"
                                                                  options: 0
                                                                  metrics:nil
                                                                    views:views];
            [self.contentView addConstraints:constraints];

            constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[nameLabel][mainLabel(==nameLabel)]|"
                                                                  options: 0
                                                                  metrics:nil
                                                                    views:views];
            [self.contentView addConstraints:constraints];

        }
    }
    return self;
}
rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • Wonderful, thank you for correcting my code. Cells are displaying nicely, single labeled cells scale to fit my default cell height of 88.0 and double labeled cells share the space evenly. In the Stack Overflow answer I mentioned earlier the developer says: _"Let the intrinsic content size of these subviews drive the height of the table view cell's content view by making sure the content compression resistance and content hugging constraints in the vertical dimension for each subview are not being overridden by higher-priority constraints you have added."_ How and where would I accomplish that? – Shawn Throop Sep 24 '13 at 13:55
  • @ShawnThroop, I'm not sure how that would work -- the way that poster is using is a different approach from what I usually use. Do you need to have different heights for your cells? If so, will that height only depend on the number of labels, or would some of the labels have a variable number of lines? – rdelmar Sep 24 '13 at 15:26
  • Ultimately I'm aiming for a cell with a height that is mainly be dependant on the number or lines of one label. I need to do more reading on content compression resistance and content hugging. To the Apple Docs... btw, How would you normally do it? I'm guessing it's easier that what I'm trying to implement. – Shawn Throop Sep 24 '13 at 19:56
  • @ShawnThroop, I usually calculate the height of the cell in heightForRowAtIndexPath using sizeWithFont:constrainedToSize:lineBreakMode: (now depreciated, replaced by boundingRectWithSize:options:attributes:context:). Since I have the labels pinned to the top and bottom of the cell, the label expands with the cell. – rdelmar Sep 24 '13 at 20:10
  • 1
    @ShawnThroop compression resistance/hugging constraints are auto-created low priority that try to keep the view at its intrinsic content size. The idea is to let them have an effect by not overriding them by explicitly adding Required priority constraints that fix the size at some static value/etc. So you don't have to do anything to get them to work - just connect the edges of the views together so they are all linked up. It's only if you over-constrain and unintentionally override these implicit constraints that you end up shooting yourself in the foot. – smileyborg Sep 26 '13 at 08:25
  • @rdelmar That approach works fine for simple cells. But it won't scale to more complex cell layouts (e.g. a few variable sized subviews stacked vertically) without you having to effectively solve the constraints by hand. That's obviously a mess and defeats the purpose of Auto Layout, which is why you're much better just having the constraint solver do its thing, then asking for the solution (e.g. the calculated smallest height for the cell's content view). – smileyborg Sep 26 '13 at 08:29
  • "you shouldn't set the translatesAutoresizingMaskIntoConstraints to NO for the content view, that causes weird effects"! Wow, the fact that this just silently fails is crazy. – SmileBot Apr 28 '15 at 00:57
-1

You can create UITableViewCell programatically in swift 4 using auto-layout like below. It's not exactly the solution of your above problem as you specified in question, It's more generic implementation how to create Tableview cell programatically in swift using auto-layout :

class ViewController: UITableViewController {

override func viewDidLoad() {
   super.viewDidLoad()
   tableView.register(CustomCell2.self, forCellReuseIdentifier: "cell")
}

override func numberOfSections(in tableView: UITableView) -> Int {
  return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return 2
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? CustomCell2 else { return UITableViewCell() }
  cell.model = CellModel(labelString: "set constriant by code")
  return cell
  }  
  }

Define Model :

struct CellModel {
  let labelString : String
 }

Define Custom Cell :

class CustomCell2 : UITableViewCell {
private let label : UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false // enable auto layout
    label.backgroundColor = .green // to visualize the background of label
    label.textAlignment = .center // center text alignment
    return label
}()

private func addLabel() {
    addSubview(label)
    NSLayoutConstraint.activate([
        // label width is 70% of cell width 
        label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.7),
        // label is horizontally center of cell
        label.centerXAnchor.constraint(equalTo: centerXAnchor)
    ])
}

var model : CellModel? {
    didSet {
        label.text = model?.labelString ?? ""
    }
}

// Init 
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    addLabel()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

This is the output of above program.

This is the actual project, you can check out.

Ashis Laha
  • 2,176
  • 3
  • 16
  • 17