如何使用AutoLayout编程方式创建自定义的UITableViewCell

31

我正在尝试实现一个UITableView,其行为类似于Twitter客户端的时间线。目前,我只是试图在UITableViewCell中放置两个标签。根据这个Stack Overflow答案的建议,我为每个布局使用不同的reuseIdentifier。我的布局很简单,只包含一个标签或两个标签。最终,我将调整UITableViewCells的高度,但首先我需要让单元格填充内容。

如果我使用initWithFrame:设置标签的框架,我可以使标签显示出来,但约束没有被实现。

  • 问题:是什么阻止了标签和约束的出现?显然,在我的UITableViewCell实现中,我错过了某些东西,但我不知道是什么。

  • 次要问题:我在viewDidLoad中为每个reuseIdentifier正确注册UITableViewCell类吗?

这可能听起来有点困难,但Interface Builder让我感到困惑,我想在代码中完成所有操作。

这是名为TVTCell.h的自定义UITableViewCell的代码:

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

而 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]];
    }
}

抱歉,代码太多了。这是我 UITableViewtableView: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;
    }
}

最后,UITableView的viewDidLoad方法:

- (void)viewDidLoad
{
    [super viewDidLoad];

    [[self tableView] registerClass:[TVTCell class] forCellReuseIdentifier:kCellIDTitle];
    [[self tableView] registerClass:[TVTCell class] forCellReuseIdentifier:kCellIDTitleMain];
}

我也感到困惑。谢谢你的问题。 - stackFish
2个回答

32

你的代码有几个问题。首先,如果你进行一些日志记录,我认为你会发现updateConstraints从未被调用。我建议将所有代码放在init方法中。此外,你的约束条件也有几个问题。你设置高度为44的约束不是必要的,因为你已经让标签固定在了单元格的顶部和底部。我不知道你最后一个约束想做什么,它看起来好像会让nameLabel只有1个点宽。另外,你不应该将content视图的translatesAutoresizingMaskIntoConstraints设置为NO,这会导致奇怪的效果。所以这是我认为你需要的代码:

- (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;
}

非常好,感谢您纠正我的代码。单标签单元格的高度适应了我的默认单元格高度88.0,双标签单元格则平均分配空间。在我之前提到的Stack Overflow答案中,开发人员说:“通过确保每个子视图在垂直维度上的内容压缩阻力和内容吸附约束没有被您添加的更高优先级的约束覆盖,让这些子视图的内在内容大小驱动表视图单元格的内容视图的高度。” 我该如何在哪里实现它? - Shawn Throop
@ShawnThroop,我不确定那会如何运作——那个帖子中使用的方式不同于我通常使用的方式。你的单元格需要有不同的高度吗?如果是这样的话,这个高度只会取决于标签的数量,还是其中一些标签将具有可变数量的行? - rdelmar
最终,我希望一个单元格的高度主要取决于一个标签的行数。我需要更多地了解内容压缩阻力和内容吸附。参考苹果文档...顺便问一下,你通常是怎么做的?我猜这比我尝试实现的要容易。 - Shawn Throop
@ShawnThroop,我通常使用sizeWithFont:constrainedToSize:lineBreakMode:(现已弃用,替换为boundingRectWithSize:options:attributes:context:)在heightForRowAtIndexPath中计算单元格的高度。由于标签固定在单元格的顶部和底部,标签会随着单元格的扩展而扩展。 - rdelmar
1
@ShawnThroop 压缩抗性/紧密约束是自动创建的低优先级约束,尝试保持视图在其内在内容大小。这样做的想法是通过不覆盖它们来让它们产生影响,而是通过明确添加需要的优先级约束(将大小固定在某些静态值/等)来覆盖它们。因此,您不必做任何事情就可以让它们工作-只需连接视图的边缘,使它们全部链接起来即可。只有当您过度约束并无意中覆盖这些隐式约束时,才会导致自己遭受损失。 - smileyborg
显示剩余2条评论

-1

您可以使用自动布局在Swift 4中以编程方式创建UITableViewCell,如下所示。它并不完全解决您在问题中指定的问题,而是更通用的实现方法,即如何在Swift中使用自动布局以编程方式创建Tableview单元格:

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
  }  
  }

定义模型:

struct CellModel {
  let labelString : String
 }

定义自定义单元格:

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")
}
}

这是上述程序的输出。

这是实际项目,你可以查看。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接