Autolayout笔记:自定义View

Autolayout笔记:自定义View

如果你想在自定义View里用Autolayout进行布局的话,有下面几个点需要注意:

  • 指定Intrinsic Content Size
  • 区分frame和alignment rect
  • 是否支持baseline-aligned布局
  • 对子视图进行精确的布局控制

下面将从这些方面逐步讲解。

指定Intrinsic Content Size

这部分的基本概念在上一篇文章已经讲过,这里不再赘述,主要注意三点:

  1. 重写intrinsicContentSize方法。如果这个视图只有一个方向的尺寸设置了Intrinsic Size,那么为另一个方向的尺寸返回 UIViewNoIntrinsicMetric/NSViewNoIntrinsicMetric
  2. 当view的某些属性的改变会影响到Intrinsic Content Size时,需要调用invalidateIntrinsicContentSize,例如当UILabel的text变化时,就需要重新计算Intrinsic Content Size。
  3. 当实现了intrinsicContentSize方法后,如果想进一步控制当View的实际大小和intrinsicContentSize`冲突时的行为,需要实现Compression Resistance和Content Hugging这方面的方法,具体做法请看这里

区分frame和alignment rect

Autolayout系统的布局操作是基于alignment rect而非frame。绝大部分情况下它们是一样的,但是当你设置了alignmentRectInsets或者重写了alignmentRectForFrame:frameForAlignmentRect:时就需要注意两者的差异。

img

如图所示,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的最终位置是由viewForBaselineLayoutalignmentRectInsets共同决定的。

如何对子视图进行精确的布局控制

给子视图添加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的boundscenter。因此在layoutSubviews方法里可以对布局进行任意的调整。

最极端的情况是重写layoutSubviews/layout时不调用父类的实现。这就意味着系统虽然根据Constraints计算出视图树的位置,但你并没有应用计算的结果,换言之就是你在这个视图中完全放弃Autolayout布局,完全根据自己的意愿对子视图进行布局。

如果你仍然想使用Autolayout布局子视图,你可以先调用[super layoutSubviews]/[super layout],然后对布局进行微调。这样可以创建一些通过Constraints无法实现的布局,比如,由到视图大小之间的关系或是视图之间间距的关系来定义的布局。

参考