Autolayout笔记:自定义View
如果你想在自定义View里用Autolayout进行布局的话,有下面几个点需要注意:
- 指定Intrinsic Content Size
- 区分frame和alignment rect
- 是否支持baseline-aligned布局
- 对子视图进行精确的布局控制
下面将从这些方面逐步讲解。
指定Intrinsic Content Size
这部分的基本概念在上一篇文章已经讲过,这里不再赘述,主要注意三点:
- 重写
intrinsicContentSize
方法。如果这个视图只有一个方向的尺寸设置了Intrinsic Size,那么为另一个方向的尺寸返回UIViewNoIntrinsicMetric
/NSViewNoIntrinsicMetric
。 - 当view的某些属性的改变会影响到Intrinsic Content Size时,需要调用
invalidateIntrinsicContentSize
,例如当UILabel的text变化时,就需要重新计算Intrinsic Content Size。 - 当实现了
intrinsicContentSize
方法后,如果想进一步控制当View的实际大小和intrinsicContentSize`冲突时的行为,需要实现Compression Resistance和Content Hugging这方面的方法,具体做法请看这里
区分frame和alignment rect
Autolayout系统的布局操作是基于alignment rect而非frame。绝大部分情况下它们是一样的,但是当你设置了alignmentRectInsets
或者重写了alignmentRectForFrame:
和frameForAlignmentRect:
时就需要注意两者的差异。
如图所示,Autolayout所有的约束(包括指定宽和高)都施加在alignment rect上,当你指定了alignmentRectInsets
后View的frame是根据alignment rect和alignmentRectInsets
计算出来的。上图里给view添加了left=80、top=100、width=100和height=100约束,使alignment rect上left为80、top为100、width为100、height为100,然后Autolayout系统根据alignmentRectInsets计算出view的frame,这里frame就是
{alignmentRect.left-alignmentRectInsets.left
,alignmentRect.top-alignmentRectInsets.top
,alignmentRect.width+alignmentRectInsets.left+alignmentRectInsets.right
,alignmentRect.height+alignmentRectInsets.top+alignmentRectInsets.bottom
}
即{60
,80
,140
,140
}
是否支持baseline-aligned布局
如果需要支持baseline布局,就需要实现viewForBaselineLayout
(iOS平台) ,系统默认实现只是简单地返回self
。如果重写这个方法它返回的View的底边会作为baseline,而且这个View必须是你自定义View的子视图。在OS X中实现baseline布局需要重写baselineOffsetFromBottom
返回一个从视图底部边缘开始的offset,默认返回0。
如果自定义View的frame和alignment rect不一样(设置了alignmentRectInsets
或者重写了alignmentRectForFrame:
和frameForAlignmentRect:
)需要注意Autolayout布局是施加在alignment rect上的,自定义的view的最终位置是由viewForBaselineLayout
和alignmentRectInsets
共同决定的。
如何对子视图进行精确的布局控制
给子视图添加Constraints 官方建议的方法是在 updateConstraints
里添加子视图的Constraints,而且要保证 [super updateConstraints]
一定要在你自己的Constraints添加完之后再调用。而且在这个方法里不能“invalidate any constraints”如果Constraints失效时,需要移除对应的Constraints,并调用setNeedsUpdateConstraints
来刷新。
如果是用添加Constraints的方式给子视图布局的话,这个自定义视图就仅仅在Autolayout环境下才能正常工作,因此可以重写requiresConstraintBasedLayout
方法返回YES来告知系统必须使用Autolayout布局。
子视图布局 回顾一下之前说起过的Autolayout布局的3个步骤,当Update constraints阶段结束后,就进入Layout view阶段,此时就会调用layoutSubviews
方法来根据Constraints计算出来的结果来调整view的bounds
和center
。因此在layoutSubviews
方法里可以对布局进行任意的调整。
最极端的情况是重写layoutSubviews
/layout
时不调用父类的实现。这就意味着系统虽然根据Constraints计算出视图树的位置,但你并没有应用计算的结果,换言之就是你在这个视图中完全放弃Autolayout布局,完全根据自己的意愿对子视图进行布局。
如果你仍然想使用Autolayout布局子视图,你可以先调用[super layoutSubviews]
/[super layout]
,然后对布局进行微调。这样可以创建一些通过Constraints无法实现的布局,比如,由到视图大小之间的关系或是视图之间间距的关系来定义的布局。