前言
最近又要做新功能了,虽然没有什么难点,只是获取后端XML数据显示到TableView,但是不是可以更简单快速的完成呢?原来Cell的动态高度一直都是通过sizeWithFont手动计算,潜意识觉得这应该不是最好的实现方式,但由于当时时间紧不允许尝试新技术,所以问题也就遗留了下来,这次又遇到了,时间充裕就解决下吧。
Autolayout是解决自适应frame问题的解决方案(iOS6.0就已经支持了,我现在才用= =#)。通过给视图元素设置合适的约束条件,内部会根据元素内容和限制条件计算出合适的尺寸显示。我们就不用自己手动写这些代码了。
文章步骤看上去有些复杂,真正做起来还是很快的,鼠标拖拽几下就能完成原来的手动计算高度部分代码,减少了很多重复性工作,也让代码看起来更加整洁。
创建Xib文件
首先将Cell做好布局,调整到满意的位置和宽度,然后开始做Autolayout设定。
Autolayout操作方式有两种,一种是选择目标后,使用右下角的工具栏;另一种是直接使用右键拖拽目标,在弹出的菜单中选择限制项。当选择的目标比较小的时候,可以打开左侧的菜单,在这里做拖拽操作一样是可以的。个人感觉后者更方便一些。
开始之前,先来介绍下使用的基本工具吧。
第一个按钮是和对齐有关的,就是控制多个元素(Lable, Button等)的统一约束。例如我们需要让标题和内容按照左,就选择标题和内容元素,选择Leading Edges设置为5即可。
第二个按钮是和元素位置固定有关的限制条件,直接看图吧:
右侧能够看到当前选择元素限制条件的列表:
这里有两个参数,“Content Hugging Priority”和“Content Compression Resistance Priority”,感觉不太好理解,栈爆上找到一篇解释,讲的挺好的:Cocoa Autolayout: content hugging vs content compression resistance priority
有时候想要一个元素的间距是一个动态值,例如距离右侧至少10pt(即>=10pt),那么可以在上图中点击右侧按钮(齿轮)进入详细设置:
第三个按钮是有关清除限制条件、根据限制更新视图大小的工具。个人比较常用的是清除限制条件,有时候设置错了很麻烦,直接清除掉重新来就行了。
上面这些就是常用到的一些限制条件了。个人觉得使用右键拖拽弹出的菜单选择更方便和直观一些,因为菜单中会根据拖拽内容动态显示可用项供我们选择,菜单如图
大致就是这些了吧……
我来谈谈自己的用法。总体上是从上到下,从左到右做约束限制。在这个例子中,就是设置标题->内容->发帖人这样的顺序。
- 设置标题的顶部和左侧距离,以及宽度(防止超出边界)。
- 设置内容的顶部(距离标题)和左侧距离,以及宽度。设置最大行数。
- 设置发帖人的顶部和左侧距离,以及高度。
- 设置发帖时间的顶部和左侧距离,距离右侧间距(防止内容过长)。
- 关键步骤,设置发帖人距离底部距离,如果不设置这个参数,那么下面代码计算的Cell高度会永远是0。
多试一试,如果有错误或者缺少限制,XCode会有提示。它报出的错误一般都是必须修正的,但它给的自动修正建议有时并不是我们想要的(正确的),想清楚再添加。
代码部分
使用了xib制作的Cell,那么在原来的项目代码中如何使用呢?看代码:
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
37
|
static
NSString
*CellIdentifier
=
@"CellIdentifier"
;
-
(
void
)
viewDidLoad
{
//注册TableView中用于复用的Cell
[
self
.
tableView
registerNib
:
[
UINib
nibWithNibName
:
@"BBSPostContentCell"
bundle
:nil
]
forCellReuseIdentifier
:CellIdentifier
]
;
//...
}
//关键方法,获取复用的Cell后模拟赋值,然后取得Cell高度
-
(
CGFloat
)
tableView
:
(
UITableView
*
)
tableView
heightForRowAtIndexPath
:
(
NSIndexPath
*
)
indexPath
{
BBSPostContentCell
*cell
=
[
tableView
dequeueReusableCellWithIdentifier
:CellIdentifier
]
;
NSDictionary
*dataSourceItem
=
[
self
.
dataSource
objectAtIndex
:indexPath
.
row
]
;
cell
.
titleLabel
.
text
=
[
dataSourceItem
valueForKey
:
@"title"
]
;
cell
.
contentLabel
.
text
=
[
dataSourceItem
valueForKey
:
@"body"
]
;
[
cell
setNeedsUpdateConstraints
]
;
[
cell
updateConstraintsIfNeeded
]
;
CGFloat
height
=
[
cell
.
contentView
systemLayoutSizeFittingSize
:UILayoutFittingCompressedSize
]
.
height
;
return
height
;
}
//在cellForRowAtIndexPath中,按照常规方法做赋值就行了
-
(
UITableViewCell
*
)
tableView
:
(
UITableView
*
)
tableView
cellForRowAtIndexPath
:
(
NSIndexPath
*
)
indexPath
{
BBSPostContentCell
*cell
=
[
tableView
dequeueReusableCellWithIdentifier
:CellIdentifier
]
;
NSDictionary
*dic
=
dataSource
[
indexPath
.
row
]
;
cell
.
titleLabel
.
text
=
dic
[
@"title"
]
;
cell
.
contentLabel
.
text
=
dic
[
@"body"
]
;
return
cell
;
}
|
2014.1.2: 在测试时发现这部分的代码还存在一些性能问题(整个表视图在更新时会卡顿),我会稍后补上。
我在使用Instruments分析发现,heightForRowAtIndexPath中调用dequeueReusableCellWithIdentifier会占用很多CPU资源,因此我试着不使用registerNib方法注册复用Cell,而在代码中手动处理,类似这样:
1
2
3
4
5
6
7
|
BBSPostContentCell
*cell
=
[
tableView
dequeueReusableCellWithIdentifier
:CellIdentifier
]
;
if
(
cell
==
nil
)
{
cell
=
[
[
NSBundle
mainBundle
]
loadNibNamed
:
@"BBSPostContentCell"
owner
:self
options
:NULL
]
[
0
]
;
NSLog
(
@"cell loadNibNamed"
)
;
}
else
{
NSLog
(
@"cell dequeueReusableCellWithIdentifier"
)
;
}
|
这时我发现这里的Cell调用dequeueReusableCellWithIdentifier方法总是返回nil,因此每次都是从xib中加载,从而耗费了大量的资源。问题的原因我还不清楚,目前我的解决方法是,单独生成一个Cell用于在heightForRowAtIndexPath方法中计算高度。
其次,在[tableView reloadData]和[tableView insertRowsAtIndexPaths]时,底层会将所有行高重新计算,这个会占用大量的时间,因此我试着对行高做了缓存,暂时解决了这个问题。
关于兼容性问题
由于Autolayout只能在iOS6.0以上版本使用,而根据友盟统计,目前6.0以下的用户大概还有8%左右(2013.12)。现在有两个办法解决:
- 哥不在乎,放弃这些用户!(好霸气=。=)把项目的部署版本修改为6.0以上即可。
- 咳…咳…这个嘛,用户还是有必要支持的………恩,那我们来说说这个怎么兼容。
思路很简单,我们告诉XCode,6.0以上版本使用Autolayout,以下的旧版本不要使用这个就可以了。
将原xib文件inspector中选择”Interface Builder Document”->”Build for”->”iOS 6.0 and Later”,告诉XCode,这个xib在6.0以上设备编译。
将xib文件拷贝一份副本,命名为”xxx_iOS5.xib”,在inspector中选择”Project Deployment Target”,也就是说使用项目部署目标版本(即最低版本5.0),并取消”Use Autolayout”选项。
在代码中根据系统版本加载不同的xib文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) \
(
[
[
[
UIDevice
currentDevice
]
systemVersion
]
compare
:v
options
:NSNumericSearch
]
!=
NSOrderedAscending
)
#define IS_SUPPORT_AUTOLAYOUT SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")
-
(
void
)
viewDidLoad
{
if
(
!
IS_SUPPORT_AUTOLAYOUT
)
{
//for iOS 5.x
[
self
.
tableView
registerNib
:
[
UINib
nibWithNibName
:
@"BBSPostContentCell_iOS5"
bundle
:nil
]
forCellReuseIdentifier
:CellIdentifier
]
;
}
else
{
[
self
.
tableView
registerNib
:
[
UINib
nibWithNibName
:
@"BBSPostContentCell"
bundle
:nil
]
forCellReuseIdentifier
:CellIdentifier
]
;
}
}
|
最后别忘了在高度计算时,区分下代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
-
(
CGFloat
)
tableView
:
(
UITableView
*
)
tableView
heightForRowAtIndexPath
:
(
NSIndexPath
*
)
indexPath
{
if
(
IS_SUPPORT_AUTOLAYOUT
)
{
//Autolayout部分代码,同上
//.....
return
height
;
}
else
{
//for iOS 5.x
//为了简单起见,就直接使用固定值了,当然如果你要自己为iOS5用户手动计算动态高度,也是可以的。
return
81
;
}
}
|
完成了!
2013的最后一篇文章,元旦快乐!
转载请注明:转载自 Tony's blog,原文网址:http://itony.me/381.html