如何使用动态单元格高度和自动布局以编程方式创建一个非常基本的 UITableView?

Posted

技术标签:

【中文标题】如何使用动态单元格高度和自动布局以编程方式创建一个非常基本的 UITableView?【英文标题】:How to create a very basic UITableView programmatically with dynamic cell height and auto layout? 【发布时间】:2014-10-14 07:07:53 【问题描述】:

请注意:我知道有很多类似的例子。我正在寻找所需的最少设置的最基本的解决方案。

我试图自己创建一个,但我知道我还差得很远..

    #import "ViewController.h"

    @interface ViewController () <UITableViewDataSource, UITableViewDelegate>

    @property(strong, nonatomic) UITableView *tableView;
    @property(strong, nonatomic) NSMutableArray *dataArray;
    @property (strong, nonatomic) UITableViewCell *customCell;

    @end

    @implementation ViewController

    - (void)viewDidLoad 
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) style:UITableViewStylePlain];
        [self.tableView setDataSource:self];
        [self.tableView setDelegate:self];
        [self.tableView setShowsVerticalScrollIndicator:NO];
        self.tableView.translatesAutoresizingMaskIntoConstraints = NO;
        self.tableView.rowHeight = UITableViewAutomaticDimension;
        [self.view addSubview:self.tableView];

        self.dataArray = [@[@"For the past 33 years, I have looked in the mirror every morning and asked myself: 'If today were the last day of my life, would I want to do what I am about to do today?' And whenever the answer has been 'No' for too many days in a row, I know I need to change something. -Steve Jobs",
                         @"Be a yardstick of quality. Some people aren't used to an environment where excellence is expected. - Steve Jobs",
                         @"Innovation distinguishes between a leader and a follower. -Steve Jobs"] mutableCopy];


    

    - (void)didReceiveMemoryWarning 
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 

        return [self.dataArray count];
    

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
        NSString *cellIdentifier = @"CustomCell";
        UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];

        if (cell == nil)
        
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
        

        cell.backgroundColor = [UIColor colorWithRed:249.0/255 green:237.0/255 blue:224.0/255 alpha:1.0];

        int dataIndex = (int) indexPath.row % [self.dataArray count];
        cell.textLabel.text = self.dataArray[dataIndex];
        cell.textLabel.numberOfLines = 0;
        cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;

        NSDictionary *views = @@"label":cell.textLabel;
        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label]|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views];
        [cell.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]|"
                                                              options: 0
                                                              metrics:nil
                                                                views:views];
        [cell.contentView addConstraints:constraints];

        return cell;

    

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 
        // Calculate a height based on a cell
        if(!self.customCell) 
            self.customCell = [self.tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
        

        // Configure the cell
        int dataIndex = (int) indexPath.row % [self.dataArray count];
        self.customCell.textLabel.text = self.dataArray[dataIndex];

        // auto layout

        NSDictionary *views = @@"label":self.customCell.textLabel;
        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label]|"
                                                                       options:0
                                                                       metrics:nil
                                                                         views:views];
        [self.customCell.contentView addConstraints:constraints];

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

        // Layout the cell

        [self.customCell layoutIfNeeded];

        // Get the height for the cell

        CGFloat height = [self.customCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

        // Padding of 1 point (cell separator)
        CGFloat separatorHeight = 1;

        return height + separatorHeight;
    

    - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath 

        return 140;

    


    @end

【问题讨论】:

检查这个 -> appcoda.com/self-sizing-cells 但是那个是用故事板完成的,不是吗?我正在寻找一个完全以编程方式。 【参考方案1】:

所以在经历了很多例子之后,我终于明白了哪里出了问题。它是其中一个缺失的行会弄乱整个代码的事情之一。对我来说是,

cell.bodyLabel.preferredMaxLayoutWidth = tableView.bounds.size.width;

我们需要在 heightForRowAtIndexPath 方法中添加,否则会出现约束错误。

感谢其中一位 cmet 给出了惊人的答案 here ,我得到了解决方案。尽管在答案中给出的示例代码中,上面的代码行在函数 cellForRowAtIndexPath 而不是 heightForRowAtIndexPath 中。但是我的代码不能这样运行。

所以,基本代码是,

// RJCell.h

#import <UIKit/UIKit.h>

@interface RJTableViewCell : UITableViewCell

@property (strong, nonatomic) UILabel *titleLabel;
@property (strong, nonatomic) UILabel *bodyLabel;

@end

// RJCell.m

#import "RJTableViewCell.h"

@interface RJTableViewCell ()

@property (nonatomic, assign) BOOL didSetupConstraints;

@end

@implementation RJTableViewCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) 
        // Initialization code

        self.titleLabel = [[UILabel alloc] init];
        [self.titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.titleLabel setLineBreakMode:NSLineBreakByTruncatingTail];
        [self.titleLabel setNumberOfLines:1];
        [self.titleLabel setTextAlignment:NSTextAlignmentLeft];
        [self.titleLabel setTextColor:[UIColor blackColor]];
        [self.titleLabel setBackgroundColor:[UIColor clearColor]];
        [self.contentView addSubview:self.titleLabel];

        // Add this label to the button
        self.bodyLabel = [[UILabel alloc] init];
        [self.bodyLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
        [self.bodyLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
        [self.bodyLabel setLineBreakMode:NSLineBreakByTruncatingTail];
        [self.bodyLabel setNumberOfLines:0];
        [self.bodyLabel setTextAlignment:NSTextAlignmentLeft];
        [self.bodyLabel setTextColor:[UIColor darkGrayColor]];
        [self.bodyLabel setBackgroundColor:[UIColor clearColor]];
        [self.contentView addSubview:self.bodyLabel];
    

    return self;


- (void)updateConstraints 
    [super updateConstraints];

    if (self.didSetupConstraints) return;

    // Get the views dictionary
    NSDictionary *viewsDictionary =
        @
            @"titleLabel" : self.titleLabel,
            @"bodyLabel" : self.bodyLabel
        ;

    NSString *format;
    NSArray *constraintsArray;

    //Create the constraints using the visual language format
    format = @"V:|-10-[titleLabel]-10-[bodyLabel]-10-|";
    constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:viewsDictionary];
    [self.contentView addConstraints:constraintsArray];

    format = @"|-10-[titleLabel]-10-|";
    constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:viewsDictionary];
    [self.contentView addConstraints:constraintsArray];

    format = @"|-10-[bodyLabel]-10-|";
    constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:viewsDictionary];
    [self.contentView addConstraints:constraintsArray];

    self.didSetupConstraints = YES;


@end

// RJTableViewController.h

#import <UIKit/UIKit.h>

@interface RJTableViewController : UITableViewController

@end

// RJTableViewController.m

#import "RJTableViewController.h"
#import "RJTableViewCell.h"

static NSString *CellIdentifier = @"CellIdentifier";

@interface RJTableViewController ()

@property(strong,nonatomic) NSMutableArray *titleArray;
@property(strong,nonatomic) NSMutableArray *bodyArray;

@end

@implementation RJTableViewController

- (id)initWithStyle:(UITableViewStyle)style

    self = [super initWithStyle:style];
    if (self) 
        // Custom initialization
        self.title = @"Table View Controller";
    
    return self;


- (void)viewDidLoad

    [super viewDidLoad];

    [self.tableView registerClass:[RJTableViewCell class] forCellReuseIdentifier:CellIdentifier];
    self.titleArray = [[UIFont familyNames] mutableCopy];
    for(int i = 0; i < 100; i++) 
        [self.titleArray addObjectsFromArray:[UIFont familyNames]];
    
    self.bodyArray = [@[@"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",@"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",@"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",@"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."] mutableCopy];


- (void)contentSizeCategoryChanged:(NSNotification *)notification

    [self.tableView reloadData];


#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

    // Return the number of sections.
    return 1;


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

    // Return the number of rows in the section.
    return [self.titleArray count];


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath


    RJTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    int dataIndex = (int) indexPath.row % [self.bodyArray count];
    cell.titleLabel.text =  self.titleArray[indexPath.row];
    cell.bodyLabel.text = self.bodyArray[dataIndex];

    // Make sure the constraints have been added to this cell, since it may have just been created from scratch
    [cell setNeedsUpdateConstraints];

    return cell;


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath


    RJTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    int dataIndex = (int) indexPath.row % [self.bodyArray count];
    cell.titleLabel.text =  self.titleArray[indexPath.row];
    cell.bodyLabel.text = self.bodyArray[dataIndex];

    cell.bodyLabel.preferredMaxLayoutWidth = tableView.bounds.size.width - (20.0 * 2.0f);

    [cell setNeedsUpdateConstraints];
    [cell updateConstraintsIfNeeded];
    [cell.contentView setNeedsLayout];

    [cell.contentView layoutIfNeeded];

    CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    return height;


- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

    return 200.0f;


@end

基本上,棘手的部分在两个地方,首先是设置约束。二、实现heightForRowAtIndexPath和cellForRowAtIndexPath方法。

【讨论】:

您没有在解决方案代码中实现数据源和委托协议的原因是什么? 抱歉回复晚了,但我想我已经实现了所有最低限度的必要代码。数据源有 cellForRowAtIndexPath。当然我没有采取任何单独的数据源委托,但那是因为我想保持简单,并使用最少的必要代码..

以上是关于如何使用动态单元格高度和自动布局以编程方式创建一个非常基本的 UITableView?的主要内容,如果未能解决你的问题,请参考以下文章

多个自定义视图的自动布局问题

使用锚以编程方式设置 UITableViewCell 的布局

UICollectionViewCell 以编程方式自动布局

如何使用自动布局动态调整具有固定宽度的collectionview单元格高度?

使用自动布局自定义集合视图单元格的动态高度

自动布局以动态调整 UILabel 大小不起作用