大概你第一眼看来,动态调整高度是一件不容易的事情,而且打算解决它的第一个想法往往是不正确的。在这篇文章中我将展示如何使图表单元格的高度能根据里面文本内容来动态改变,同时又不必子类化UITableViewCell。你当然可以通过子类化它来实现,但是这样做会使得代码复杂因为设置高度是在图表本身的实例上而不是对单元格操作。下面你将会看到这其实是一件轻而易举的事情。对于图表来说能够动态调整高度是件很有意义的事情,我首先想到的需要这个功能的是当显示一列长度会变化的文本列表时,如果文本内容较少,它或许能够适合正常的单元格label,但是如果文本变长,就不得不重新设置单元格大小以便于显示全部的文本内容。我总结了重新设置单元格大小的主要步骤如下:

1 创建并添加一个UILabel作为单元格cell的子视图;
2 在UITableView的委托方法: (CGFloat)tableView:(UITableView*)tableViewheightForRowAtIndexPath: (NSIndexPath *) indexPath中计算高度
3 在UITableView的委托方法: (UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath: (NSIndexPath *) indexPath中计算UILabel的框大小。

下面我要详细介绍这些步骤,首先看一下程序输出截图:

在普通的图表中,你可以简单地用下面的方法设置单元格内label的文本内容:

[[cell textLabel] setText:@"Text for the current cell here."];

也许你认为这样做就可以完全控制UILabel了,但是我发现我的任何要改变UILabel框大小的尝试都失败了,因此这并不是实现动态调整大小的一个好的候选方案。

我们需要设计一个UILabel然后把它添加到单元格的内容视图中。要实现它需要调用-cellForRowAtIndexPath,大致内容如下所示:

  1. - (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. {
  3. UITableViewCell *cell;
  4. UILabel *label = nil;
  5. cell = [tv dequeueReusableCellWithIdentifier:@"Cell"];
  6. if (cell == nil)
  7. {
  8. cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"] autorelease];
  9. label = [[UILabel alloc] initWithFrame:CGRectZero];
  10. [label setLineBreakMode:UILineBreakModeWordWrap];
  11. [label setMinimumFontSize:FONT_SIZE];
  12. [label setNumberOfLines:0];
  13. [label setFont:[UIFont systemFontOfSize:FONT_SIZE]];
  14. [label setTag:1];
  15. [[cell contentView] addSubview:label];
  16. }
  17. }

这并不是完整的代码因为我们仅仅在创建单元格的时候初始化它的label,这段代码对应调用-dequeueReusableCellWithIdentifier之后的判断模块if(cell == nil)。
在这里我想强调两点:第一个,我们可以注意到label有一个标签与其对应,因为调用了-setTag:1。当cell不等于nil时这个标签可以用到。第二点,我们通过调用[[cell contentView] addSubview:label]来将label添加到单元格的内容视图中,这个只是在label初始化的时候用到。每调用这个函数都会添加label到子视图序列中。下面我们会将这段代码补充完整,但之前先让我们看一下如何设置cell的高度。

计算cell的高度

在一个复杂的cell中,计算高度可能比较困难,但是你只需要关心那些高度会变化的部件就可以了。在我的例子中,唯一需要处理的就是添加到单元格中的label。我们根据文本的大小来计算cell 的高度,而文本的大小取决于文本的长度和文本字体。NSString类提供了函数-sizeWithFont来方便我们获取cell 的大小。下面的代码介绍了函数-heightForRowAtIndexPath:

  1. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
  2. {
  3. NSString *text = [items objectAtIndex:[indexPath row]];
  4. CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
  5. CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
  6. CGFloat height = MAX(size.height, 44.0f);
  7. return height + (CELL_CONTENT_MARGIN * 2);
  8. }

你会注意到我们用到了几个常量来计算cell 的大小,它们的定义如下所示:

#define FONT_SIZE 14.0f
#define CELL_CONTENT_WIDTH 320.0f
#define CELL_CONTENT_MARGIN 10.0f

常量CELL_CONTENT_WIDTH是整个cell的宽度。CELL_CONTENT_MARGIN是我们定义的页边空白,FONT_SIZE是我们采用文本的字体大小。

首先我们要创建一个内容宽度的约束条件。CGSizeMake的第一个参量是总共的内容宽度减去两个页边空白。因为左边和右边各有一个页边空白。第二个参数是我们提供的最大数值。这个约束条件在后面的函数-sizeWithFont中将会用到。在-sizeWithFont中我们设置为UILineBreakModeWordWrap来获取在允许自动换行的情况和上面提到的约束条件下正确的大小。最后我们使用MAX宏设置cell的高度,并且保证cell 的高度不会小于44个像素,因为它返回size.height和44两个数中的最大值。最后,我们将上下的页边空白考虑进去得到最后的结果。

为了使得读者形象化的了解页边空白,下面一个截图可以看出有一个边界环绕着label。调用[[label layer] setBorderWidth:2.0f]可以显示该边界从而方便我们看到页边空白。

计算并设置UILabel框大小

在前面我们用来计算高度的方法也是我们用来设置UILabel框大小的方法。下面将-cellForRowAtIndexPath代码补充完整:

  1. - (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. {
  3. UITableViewCell *cell;
  4. UILabel *label = nil;
  5. cell = [tv dequeueReusableCellWithIdentifier:@"Cell"];
  6. if (cell == nil)
  7. {
  8. cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"] autorelease];
  9. label = [[UILabel alloc] initWithFrame:CGRectZero];
  10. [label setLineBreakMode:UILineBreakModeWordWrap];
  11. [label setMinimumFontSize:FONT_SIZE];
  12. [label setNumberOfLines:0];
  13. [label setFont:[UIFont systemFontOfSize:FONT_SIZE]];
  14. [label setTag:1];
  15. [[label layer] setBorderWidth:2.0f];
  16. [[cell contentView] addSubview:label];
  17. }
  18. NSString *text = [items objectAtIndex:[indexPath row]];
  19. CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
  20. CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
  21. if (!label)
  22. label = (UILabel*)[cell viewWithTag:1];
  23. [label setText:text];
  24. [label setFrame:CGRectMake(CELL_CONTENT_MARGIN, CELL_CONTENT_MARGIN, CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), MAX(size.height, 44.0f))];
  25. return cell;
  26. }

要注意if(cell == nil)模块是初始化代码,只在cell创建的时候运行一次。该模块外部代码每次都会执行只要在每次数据更新或者窗口拖拽之后调用了-cellForRowAtIndexPath。

也就是说,每次都需要设置label中文本内容以及设置label外框大小。注意如果label处于未初始化状态,我们需要通过调用[cell viewWithTag:1]来获取UILabel的句柄。这段代码跟前面计算高度的代码基本相同。

总结

动态计算单元格cell的高度真的并不困难。如果你有一个很复杂的cell,你只需要根据内容宽度和特定文本字体的大小来确定cell的高度。如果你不清楚你的外框显示在什么地方,只需要通过调用[[view layer] setBorderWidth:2.0f]来使外框显示即可。这会有助于你了解绘图过程以及更快地在更深的层次理解绘图显示的问题。

演示工程文件:DynamicHeights Demo Project

作者:Matt Long
原文链接:http://www.cimgf.com/2009/09/23/uitableviewcell-dynamic-height/
原文:
UITableViewCell Dynamic Height
At first glance setting a height dynamically for table view cells seems a little daunting and the first most obvious answers that come to mind are not necessarily correct. In this post I will show you how to set your table view cell heights dynamically based upon the text content without subclassing UITableViewCell. You can subclass it, however, doing so does not make the code much cleaner as setting the height is done in your delegate for the table view itself rather than the cell anyhow. Read on to see what you need to know to make dynamic cell height sizing a breeze. There are probably numerous reasons why you might want dynamic heights for your table view cells, but the one I’ve run into most is the need to resize because I am displaying lists of text objects with varying lengths. When the text is short, it might fit in the normal cell label, however, if the text gets longer, you will want to resize the cell so that you can display the complete content. I’ve distilled the process of resizing table cells to a few rules of thumb. Here they are:

  • Create, configure, and add a UILabel as a subview of the contentView in the cell.
  • Calculate the height in the UITableView delegate method, – (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
  • Calculate the frame for the UILabel in the UITableView delegate method, – (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

I am going to cover each of these rules in detail, but take a look at the output of the example project in the screenshot.

In simpler table view based applications, you can simply set the text of the table view cell’s text label like this:

[[cell textLabel] setText:@"Text for the current cell here."];

Doing so might make you think that you can manipulate the UILabel that the cell uses, however, I’ve found my attempts to change the UILabel’s frame get ignored completely, so it is not a good candidate for use with our dynamic resizing code.

Instead what we need to do is programatically create a UILabel and add it to the cell’s content view. Do this in the call to -cellForRowAtIndexPath. Use something like the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
UILabel *label = nil;
 
cell = [tv dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"] autorelease];
 
label = [[UILabel alloc] initWithFrame:CGRectZero];
[label setLineBreakMode:UILineBreakModeWordWrap];
[label setMinimumFontSize:FONT_SIZE];
[label setNumberOfLines:0];
[label setFont:[UIFont systemFontOfSize:FONT_SIZE]];
[label setTag:1];
 
[[cell contentView] addSubview:label];
}
}

This is not the completed code as you’ll notice that we have initialized the label only when the cell needs created for the first time, that is if (cell == nil) after a call to -dequeueReusableCellWithIdentifier. There are two points I want to address in relation to this. First, notice the label has a tag associated with it after a call to -setTag:1. This will be used in the case where the cell is not equal to nil after a call to -dequeueReusableCellWithIdentifier. In that case we will need to get a handle to the label by calling [cell viewWithTag:1] which will return the view that we associated with that tag. Second, notice that we have added our label to the cell’s content view with a call to [[cell contentView] addSubview:label]. This is done when the label is initialized and should only be done once. Adding it each time this method is called will add the label again to the subviews array. We will come back to this code to finish it in a minute, but first let’s take a look at how we can set the height for our cell now that our label has been added.

Calculate the Cell Height

In a complex cell, your calculations could get a bit challenging, however, you only need to worry with the items that will change in height. In our example, the only item you need to deal with is the label that we added. We calculate the height of the cell by determining the size of the text based on the length of the text and the font we intend to use. The NSString class provides a method called -sizeWithFont that enables us to obtain this size. The following code show how we implement our call to -heightForRowAtIndexPath:

1
2
3
4
5
6
7
8
9
10
11
12
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
NSString *text = [items objectAtIndex:[indexPath row]];
 
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
 
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
 
CGFloat height = MAX(size.height, 44.0f);
 
return height + (CELL_CONTENT_MARGIN * 2);
}

You will notice that we have several constants we are using to calculate the size of our cell. These are defined as follows:

#define FONT_SIZE 14.0f
#define CELL_CONTENT_WIDTH 320.0f
#define CELL_CONTENT_MARGIN 10.0f

The constant CELL_CONTENT_WIDTH is the width of the entire cell. CELL_CONTENT_MARGIN is the margin we want to use all the way around the cell as the content inset, and of course FONT_SIZE is the size of the font we want to use for the label text.

The first place we use these is to create a constraint with the content width. Notice that CGSizeMake takes as its first parameter the total content width minus the margin times 2. This subtracts the margin from the left and the margin from the right from the total width to have the actual width of the label. The second parameter is just a maximum number we provide. The call to -sizeWithFont will set this to the actual height in the next line. This call to -sizeWithFont calculates the size according to the constant UILineBreakModeWordWrap which causes it to return the correct size for word wrap–which is why the width is important to get right. Next we set our height for the cell using a call to the MAX macro. This will ensure that our cell height will never be shorter than the default 44 pixels as MAX returns the larger of the two variables. Finally, we add our margin height back into the height for both top and bottom (hence x 2) and then return the result.

To help visualize how the margin is working, take a look at the following screenshot to see what each label looks like with a border around it. Turning this on with a call to [[label layer] setBorderWidth:2.0f] on the UILabel we added in the previous section makes it clear where the margins are.

Calculate the UILabel Frame and Set It

The same calculation we used to determine the height in the previous section is the code we use to set the frame for the UILabel we added in the beginning. To complete this tutorial we will finish out our implementation of -cellForRowAtIndexPath with the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
UILabel *label = nil;
 
cell = [tv dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"] autorelease];
 
label = [[UILabel alloc] initWithFrame:CGRectZero];
[label setLineBreakMode:UILineBreakModeWordWrap];
[label setMinimumFontSize:FONT_SIZE];
[label setNumberOfLines:0];
[label setFont:[UIFont systemFontOfSize:FONT_SIZE]];
[label setTag:1];
 
[[label layer] setBorderWidth:2.0f];
 
[[cell contentView] addSubview:label];
 
}
NSString *text = [items objectAtIndex:[indexPath row]];
 
CGSize constraint = CGSizeMake(CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), 20000.0f);
 
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
 
if (!label)
label = (UILabel*)[cell viewWithTag:1];
 
[label setText:text];
[label setFrame:CGRectMake(CELL_CONTENT_MARGIN, CELL_CONTENT_MARGIN, CELL_CONTENT_WIDTH - (CELL_CONTENT_MARGIN * 2), MAX(size.height, 44.0f))];
 
return cell;
}

Just remember that anything done within the if (cell == nil) block is initialization code and should only be done when the cell is first created. Anything done outside of the block will be used every time the -cellForRowAtIndexPath is called, which is any time the data gets reloaded or the view gets scrolled.

That being said, you will see that the only thing we do every time it gets called is setting the text of the current item and setting the label’s frame for the current item (lines 32 and 33). Notice that we got a handle to our UILabel by calling [cell viewWithTag:1] (lines 29 and 30) in the case where the label is nil in subsequent/non-initialization calls to this method. You will notice that our frame calculation code is exactly the same as what we used in the previous section to determine the row height.

Conclusion

Calculating dynamic cell heights is really not too hard. If you have a very complex cell, just remember that all you really need to calculate is the height based up a width that shouldn’t change and the size of the text of a certain font (unless of course you support both portrait and landscape modes–which makes things a little more challenging. I will, however, leave this as an exercise for the reader). If you find yourself wondering where your actual frame is displaying for a given view, just turn on the view border by calling [[view layer] setBorderWidth:2.0f]. This will help you see what is going on and give you the ability get to the bottom of your display problems quicker. Until next time.
DynamicHeights Demo Project

 

iOS学习之路十三(动态调整UITableViewCell的高度)的更多相关文章

  1. 动态调整UITableViewCell高度的实现方法

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPa ...

  2. IOS开发---菜鸟学习之路--(二十二)-近期感想以及我的IOS学习之路

    在不知不觉当中已经写了21篇内容 其实一开始是没有想些什么东西的 只是买了Air后 感觉用着挺舒服的,每天可以躺在床上,就一台笔记本,不用网线,不用电源,不用鼠标,不用键盘,干干脆脆的就一台笔记本. ...

  3. iOS-动态调整UITableViewCell的高度

    OS-动态调整UITableViewCell的高度iOS开发文档, by 友盟翻译组 stefaliu. 大概你第一眼看来,动态调整高度是一件不容易的事情,而且打算解决它的第一个想法往往是不正确的.在 ...

  4. 浅谈iOS学习之路(转)

    转眼学习iOS已经快两年的时间了,这个路上有挫折也有喜悦,一步步走过来发现这个过程是我这一辈子的财富,我以前的老大总是对我说,年轻就是最大的资本(本人91年),现在才算是慢慢的体会到,反观自己走过的这 ...

  5. iOS开发——Autolayout下动态调整单元格高度

    情景描述: 有时候我们希望更新某一个单元格的数据,通常的做法是使用reloadData方法更新整个单元格.但是对一些情况是不适用的或者说实现起来比较麻烦.比如说这种简单的"点开"一 ...

  6. 浅谈iOS学习之路

    转眼学习iOS已经快两年的时间了,这个路上有挫折也有喜悦,一步步走过来发现这个过程是我这一辈子的财富,我以前的老大总是对我说,年轻就是最大的资本(本人91年),现在才算是慢慢的体会到,反观自己走过的这 ...

  7. 让tableView的高度等于contentSize的高度、动态调整tableView的高度、tableView的高度自适应布局

    文章概要: 1.简介下,tableView中的内容如何高度自适应的布局 2.如何做到让tableView的高度动态调整 还是看图作文吧- 首先,tableView的高度就是用户能够看见里面更大世界的那 ...

  8. IOS学习之路七(通过xib自定义UITableViewCell)

    一.新建iOS Application工程,选择Single View Application,不要选中Use Storyboard.假设指定的是product name是:UITableViewCe ...

  9. IOS学习之路二十三(EGOImageLoading异步加载图片开源框架使用)

    EGOImageLoading 是一个用的比较多的异步加载图片的第三方类库,简化开发过程,我们直接传入图片的url,这个类库就会自动帮我们异步加载和缓存工作:当从网上获取图片时,如果网速慢图片短时间内 ...

随机推荐

  1. 【SSH三框架】Struts2第一章的基础:第一次写Struts2规划

    今年八月,当已经SSH三架完成学业.然后,他感动Android开展.三个框架已经很长的时间做无用的东西.所以,如果你想花三四天的时间来复习一下,写在博客. 附带SSH整个jar包网盘下载:http:/ ...

  2. 你知道OneNote的OCR功能吗?office lens为其增大威力,中文也识别

    原文:[原创]你知道OneNote的OCR功能吗?office lens为其增大威力,中文也识别 OneNote提供了强大的从图片中取出文字的功能,大家只要装上了桌面版OneNote(本人用的2013 ...

  3. 通过SMTP协议来发送邮件

    简单邮件传输协议 (Simple Mail Transfer Protocol, SMTP) 是事实上的在Internet传输email的标准. SMTP是一个相对简单的基于文本的协议.在其之上指定了 ...

  4. html转换为纯文本,支持撇号

    /// <summary> /// html转换为纯文本 /// </summary> /// <param name="source">< ...

  5. linux中如何用root去修改其他用户的密码

    linux中如何用root去修改其他用户的密码 昨天linux实验课,我有很多自己想摸索的东西.今天周五,本是下午一二节是编译的实验,可强烈的欲望让我今早就来实验室了,摸索吧,碰到了这个问题....  ...

  6. RabbitMQ基本管理(上)

    1.1.2  显示RabbitMQ进程 查看RabbitMQ进程信息,输入以下命令: <span style="font-size:18px;"><strong& ...

  7. 《java系统性能优化》--2.高速缓存

    上一节.简介了怎样发现性能瓶颈.从这节開始.我会和大家分享我在项目中做的一些性能调优工作.这个系列没有什么顺序可言,认为什么重要.就说说什么. 这节.我们聊缓存. 最開始接触缓存这个词,是学习硬件知识 ...

  8. each与list的用法

    each与list的用法(PHP学习) 1.each的用法 先看API array each ( array &$array ) api里是这么描述的:each — 返回数组中当前的键/值对并 ...

  9. HEAP CORRUPTION DETECTED

    发生主要是由于这个问题给写入超出预分配的空间,注意检查越界情况 版权声明:本文博客原创文章,博客,未经同意,不得转载.

  10. NHibernate-Generator主键生成方式

    NHibernate之Generator主键生成方式   (1) assigned主键由外部程序负责生成,无需NHibernate参与. (2) hilo通过hi/lo 算法实现的主键生成机制,需要额 ...