Swift教程

AutoLayout Tips

本文主要是介绍AutoLayout Tips,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

AutoLayout 的三个阶段

每个启用自动布局的UIView在初始化后经过三个步骤:约束更新、布局和渲染。

约束更新 Update Constraints

这一步做的事情是基于约束计算 frame,系统自顶向下遍历视图层级,即从父视图到子视图,调用每个视图的updateConstraints()方法。

setNeedsUpdateConstraints会使约束失效,安排下一个 runloop 内更新约束。如果约束已经失效(被标记为需要更新),updateConstraintsIfNeeded会在合适的时候触发updateConstraints

Apple 建议不要重写updateConstraints,除非发现更改现有的约束太慢,此时需要在updateConstraints中批量更新约束,同时要保证实现尽可能高效。

布局 Layout

在此步骤中,每个视图的 frame 都将使用 Update 阶段中计算的值进行更新。系统自底向上遍历视图,即从子视图到父视图,依次调用layoutSubviews

当发生这两种情况时,需要重写layoutSubviews

  • 约束不足以描述视图的布局。
  • 需要手动写代码计算 frame

调用setNeedsLayout会使布局失效,向系统表示视图的布局需要重新计算。如果布局已经失效,layoutIfNeeded会触发layoutSubviews。它们的关系同 setNeedsUpdateConstraints 以及 updateConstraintsIfNeeded 方法的工作机制类似。

重写layoutSubviews时需要注意:

  • super.layoutSubviews().
  • 不要调用setNeedsLayoutsetNeedsUpdateConstraints,不然会死循环。
  • 不要修改当前层次结构之外的视图约束。
  • 要小心更改当前层次结构的视图的约束。它将触发一个 Update 步骤,然后是另一个 Layout 步骤,可能会创建一个死循环。

渲染 Display

此步骤负责将像素显示到屏幕上。默认情况下,UIView将所有工作传递给一个它的CALayer,它包含当前视图状态的像素位图。此步骤与是否用自动布局无关。

这里关键的方法是drawRect。大多数情况下,我们可以组合使用系统已有的 view 和 layer 来构建UI,除非你使用OpenGL ES, Core Graphics 或者 UIKit 做自定义绘制,不然不需要重写这方法。

所有诸如背景颜色、添加子视图等这些操作都是自动绘制的。

假如重写了drawRect,切记调用setNeedsDisplay(_:)传入需要重绘的部分,不要直接调用drawRect,就同setNeedsLayout一样。

UIViewController相关

步骤一和步骤二在 UIViewController 中有对应的部分:

  • Update: updateViewConstraints
  • Layout: viewWillLayoutSubviews / viewDidLayoutSubviews.

viewDidLayoutSubviews是其中最重要的。它用来通知视图控制器它的视图已经完成了布局步骤(即它的bounds已经改变)。当layoutSubviews完成后,在 view 的所有者视图控制器上,会触发 viewDidLayoutSubviews 调用。这里视图已经布局完它的子视图,并且它在屏幕上还不可见,所以我们应该把所有依赖于布局或者大小的代码放在 viewDidLayoutSubviews 中,而不是放在 viewDidLoad 或者 viewDidAppear 中。这是避免使用过时的布局位置数据的唯一方法。

技巧细节

Intrinsic Content Size

Intrinsic content size是基于视图内容的固有大小。例如,一个UIImageViewIntrinsic content size就是它的图像大小。

这里有两个技巧可以帮助简化布局和减少约束的数量:

  • 为自定义视图重写intrinsicContentSize方法,根据内容返回合适的尺寸。
  • 如果一个视图只有一个维度的固有大小,你仍然应该覆盖intrinsicContentSize并为未知维度返回UIViewNoIntrinsicMetric

Alignment Rectangle

AutoLayout 使用对齐矩形来定位视图。需要注意的是,intrinsicContentSize 指的是对齐矩形,而不是 frame。

默认情况下,视图的对齐矩形等于用alignmentRectInsets修改过的 frame。为了更好地控制对齐矩形,也可以重写alignmentRect(forFrame:)frame(forAlignmentRect:)

我们来看看对齐矩形是如何影响视图定位的。

这里有一个带着30 points阴影的 image view。绿色和黑色阴影都属于同一张 image。

001@2x

我已经叠加了红线来显示父视图的水平和垂直中心线。imageview 约束在父视图的中心,但是视图内容绿色方框,显然没有居中。

调试 Alignment Rectangles

Xcode10.2开始, Interface Builder 可以显示我们自定义的对齐矩形。通过(Editor > Canvas > Layout Rectangles)这个步骤可以在 Interface Builder 画布中显示对齐矩形。

也可以在运行时显示视图的对齐矩形。打开 scheme 编辑器,加一个启动参数-UIViewShowAlignmentRects YES

003

此时运行起来,视图的对齐矩形会被黄色框高亮。

Alignment rects shown in yellow

可以看到自动布局将视图中的黄色对齐矩形居中。它不知道我们需要绿色方框居中。为了忽略掉阴影,我们需要一个新的对齐矩形,从底部和右侧去掉30点:

005@2x

如果把图片放到了 Asset Catalog 里,我们可以直接修改对齐矩形。在 attributes inspector 栏下,如图所示部分修改:

006

如果使用多个倍数的图像(1x, 2x, 3x),那么需要为每个图像指定边距值。这里需要为1x增加30个像素,为2x增加60个像素,为3x增加90个像素。

那么如何通过代码修改呢?我们给UIImageView加个扩展:

extension UIImageView { 
  convenience init?(named name: String, top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) {
    guard let image = UIImage(named: name) else {
        return nil
    }
    let insets = UIEdgeInsets(top: top, left: left, bottom: bottom, right: right)
    let insetImage = image.withAlignmentRectInsets(insets)
    self.init(image: insetImage)
  }
}
复制代码

在控制器中使用时:

override func viewDidLoad() {
  super.viewDidLoad()
  setupImageView()
}

private func setupImageView() {
 guard let imageView = UIImageView(named: "Shadow", top: 0, left: 0, bottom: 30, right: 30) else {
      fatalError("Can't create image")
  }
  view.addSubview(imageView)
}
复制代码

再次运行,我们就将看到绿色居中:

007@2x

上面的例子中阴影是属于图片的一部分,我们还可以通过 UIKit 加阴影,这种方式不会影响对齐矩形。

这篇关于AutoLayout Tips的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!