PinLayout 1.10.5

PinLayout 1.10.5

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布日期最新发布2023年11月
SPM支持 SPM

Luc DionLuc Dion 维护。



PinLayout 1.10.5

  • Luc Dion

无需自动布局的极快视图布局。没有魔法,纯代码,完全控制,速度快。简洁的语法,直观,易读 & 可链式调用。PinLayout 可以布局 UIView、NSView 和 CALayer。

"未附加自动布局约束"

要求

  • iOS 9.0+ / tvOS 9.0+ / macOS 10.9+
  • Swift 5.x / 4 / 3 / Objective-C
  • Xcode 13 / 12 / 11 / 10

最近更改/功能

内容


📌PinLayout 正在积极更新。因此,请经常回来查看最新更改。您还可以 Star 它,以便稍后轻松检索。

PinLayout 和 layoutBox

PinLayoutlayoutBox 组织的一部分,其中包含一些使用 Swift 进行布局相关的开源项目。请参阅 layoutBox

PinLayout + Autolayout

您无需选择,可以使用 PinLayout 对某些视图进行布局,对其他视图使用 autolayout。您的视图只需要实现 autolayout 的 intrinsicContentSize 属性。


介绍示例

示例 1

此示例布局了一个图像、一个 UISegmentedControl、一个标签和一条分隔线。此示例会根据设备的大小和方向变化调整其内容。

  • UIImageView的大小为 100x100,周围有 10 像素的边距,布局在 UINavigationBar下方。
  • UISegmentedControl位于商标图像右侧,使用 20 像素的左右边距填充剩余的水平空间。
  • UILabel位于 UISegmentedControl下方,顶部边距为 10 像素。其宽度与 UISegmentedControl 的宽度相匹配。标签为多行,因此其高度必须根据其宽度进行调整。
  • 分隔线位于 UIImageView和 UILabel下方,即最矮的一个下方。分隔线顶部边距为 10 像素,与 UIImageView左对齐,与 UISegmentedControl右对齐。

override func layoutSubviews() {
   super.layoutSubviews() 
   let padding: CGFloat = 10
    
   logo.pin.top(pin.safeArea).left(pin.safeArea).width(100).aspectRatio().margin(padding)
   segmented.pin.after(of: logo, aligned: .top).right(pin.safeArea).marginHorizontal(padding)
   textLabel.pin.below(of: segmented, aligned: .left).width(of: segmented).pinEdges().marginTop(10).sizeToFit(.width)
   separatorView.pin.below(of: [logo, textLabel], aligned: .left).right(to: segmented.edge.right).marginTop(10)
}
  • 4 个视图,4 条线
  • PinLayout通过UIView.pin.safeArea公开 safeAreaInsets,此属性不仅支持 iOS 11,还向后兼容早期 iOS 版本(7/8/9/10)。有关更多信息,请参阅safeAreaInsets 支持情况
  • PinLayout不使用自动布局约束,它是一个手动布局视图的框架。因此,您需要更新在 UIView.layoutSubviews()UIViewController.viewDidLayoutSubviews() 中的布局,以处理容器大小的更改,包括设备旋转。对于支持多任务的应用,您还需要处理 UITraitCollection 的更改。在上面的示例中,PinLayout 的命令位于 UIView 的 layoutSubviews() 方法中。
  • 此示例可在示例应用中找到。有关示例完整源代码,请参阅。

示例 2

此示例展示了 PinLayout 如何根据视图的容器大小轻松调整其布局。

  • 如果容器宽度小于 500 像素,则标签占据全部宽度,并将 UISegmentedControl 放置在其下方。
  • 如果容器宽度大于或等于500像素,UISegmentedControl位于右上角,标签占据剩余的横向空间。

  let margin: CGFloat = 12
        
  if frame.width < 500 {
      textLabel.pin.top().horizontally().margin(margin).sizeToFit(.width)
      segmentedControl.pin.below(of: textLabel).right().margin(margin)
  } else {
      segmentedControl.pin.top().right().margin(margin)
      textLabel.pin.top().left().before(of: segmentedControl).margin(margin).sizeToFit(.width)
  }

📌此示例在示例应用中提供。参见完整的源代码

PinLayout原则和哲学

  • 手动布局(不依赖于自动布局)。
  • PinLayout旨在尽可能简单快捷!实际上,它的速度与手动布局一样快。参见下面的性能结果。
  • 完全控制:您身处布局过程之中,没有神奇的黑色盒子。
  • 一次布局一个视图。使编码和调试变得更加简单。
  • 简洁的语法。使用单独的行布局大多数视图。
  • 在此处查看完整列表...

PinLayout的性能

PinLayout的性能使用布局框架基准测试进行测量。

如您在以下图表中看到的,PinLayout的速度与手动布局相当,或者更快,并且比自动布局快8倍到12倍,并且适用于所有类型的iPhone(5S/6/6S/7/8/X)

更详细的信息,基准测试的结果和解释请在此处查看

文档

支持UIKit的safeAreaInsets

PinLayout可以轻松处理iOS 11的UIView.safeAreaInsets,而且还通过新增一个属性UIView.pin.safeArea来支持旧版iOS的safeAreaInsets(包括iOS 7/8/9/10)。更多详细信息请查看此处

macOS支持

PinLayout支持macOS 10.9及以上版本。

📌在这份文档中,任何参数类型为UIView或UIEdgeInsets的方法,也支持在macOS上使用NSView和NSEdgeInsets。更多信息请见macOS支持

支持从右向左的语言(RTL)

PinLayout支持从左到右(LTR)和从右到左(RTL)的语言。

查看更多详细信息


边的布局

PinLayout 可以为视图的边缘相对于其父视图中边缘进行定位。

示例

此示例布局将视图 A 定位以匹配其父视图的框架,并有 10 像素的边距。它固定了上、左、下和右边缘。

    viewA.pin.top(10).bottom(10).left(10).right(10)

使用 all() 的另一种更简短的解决方案

    view.pin.all(10)

方法:

以下方法用于相对于父视图边缘定位视图的边缘。

📌以下方法中的偏移/边距参数可以是正数或负数。在一般情况下使用正数。

  • top(_ offset: CGFloat) / top(_ offset: Percent) / top() / top(_ margin: UIEdgeInsets)
    定位顶部边缘。偏移指定以像素或其父视图高度百分比表示的父视图顶部边缘的顶部边缘距离。使用 top() 相似于调用 top(0),它将视图的顶部边缘直接定位在其父视图顶部边缘上。使用 top(:UIEdgeInsets)UIEdgeInsets.top 属性,特别适用于与 安全区域、可读和布局边距 一起使用。

  • bottom(_ offset: CGFloat) / bottom(_ offset: Percent) / bottom() / bottom(_ margin: UIEdgeInsets)
    定位底部边缘。偏移指定从父视图底部边缘的底部边缘距离的大小(以像素或其父视图高度百分比表示)。使用 bottom() 相似于调用 bottom(0),它将视图的底部边缘直接定位在其父视图的顶部边缘上。使用 bottom(:UIEdgeInsets)UIEdgeInsets.bottom 属性,特别适用于与 安全区域、可读和布局边距 一起使用。

  • left(_ offset: CGFloat) / left(_ offset: Percent) / left() / left(_ margin: UIEdgeInsets)
    定位左侧边。偏移量指定视图左侧边距相对于父视图左侧边的像素值(或为其父视图宽度的百分比)。left()与调用left(0)相似,将视图的左侧边直接放置在其父视图的左侧边。left(:UIEdgeInsets)使用UIEdgeInsets.left属性,这对于安全区域边距、可读边距和布局边距尤其有用。

  • right(_ offset: CGFloat) / right(_ offset: Percent) / right() / right(_ margin: UIEdgeInsets)
    定位右侧边。偏移量指定视图右侧边相对于父视图右侧边的像素值(或为其父视图宽度的百分比)。right()与调用right(0)相似,将视图的右侧边直接放置在其父视图的右侧边。right(:UIEdgeInsets)使用UIEdgeInsets.right属性,这对于安全区域边距、可读边距和布局边距尤其有用。

  • vCenter(_ offset: CGFloat) / vCenter(_ offset: Percent) / vCenter()
    定位垂直中心(center.y)。偏移量指定视图中心在垂直方向相对于父视图中心的像素值(或为其父视图高度的百分比)。正偏移将视图向下移动,负偏移将视图向上移动,相对于父视图的中心。`vCenter()`与调用`vCenter(0)`相似,将视图垂直中心直接放置在其父视图的垂直中心。

  • hCenter(_ offset: CGFloat) / hCenter(_ offset: Percent) / hCenter()
    定位水平中心(center.x)。偏移量指定视图中心在水平方向相对于父视图中心的像素值(或为其父视图宽度的百分比)。正偏移将视图向右移动,负偏移将视图向左移动,相对于父视图的中心。`hCenter()`与调用`hCenter(0)`相似,将视图水平中心直接放置在其父视图的水平中心。

支持LTR(从左到右)和RTL(从右到左)语言的函数。
  • start(_ offset: CGFloat) / start(_ offset: Percent) / start() / start(_ margin: UIEdgeInsets)↔️
    根据LTR语言方向定位左侧或右侧边。在LTR方向中,偏移量指定视图左侧边相对于父视图左侧边的像素值(或为其父视图宽度的百分比)。在RTL方向中,偏移量指定视图右侧边相对于父视图右侧边的像素值(或为其父视图宽度的百分比)。
    start()与调用start(0)相似。start(:UIEdgeInsets)在LTR方向中使用UIEdgeInsets.left属性,在RTL方向中使用UIEdgeInsets.right属性。

  • end(_ offset: CGFloat) / end(_ offset: Percent) / end() / end(_ margin: UIEdgeInsets)↔️
    根据LTR语言方向,定位左侧或右侧边缘。在LTR方向中,偏移量指定相对于父视图右侧边缘的像素距离(或相对于父视图宽度的百分比)。在RTL方向中,偏移量指定相对于父视图左侧边缘的像素距离(或相对于父视图宽度的百分比)。end()与调用end(0)类似。end(:UIEdgeInsets)在LTR方向中使用UIEdgeInsets.right属性,在RTL方向中使用UIEdgeInsets.left。

固定多个边缘的方法

  • all(_ margin: CGFloat) / all() / all(_ margin: UIEdgeInsets)
    定位顶部、左侧、底部和右侧边缘。外边距指定相对于父视图对应边缘的像素距离。
    all()与调用all(0)类似。
    all(:UIEdgeInsets)尤其适用于安全区域、可读和布局外边距

  • horizontally(_ margin: CGFloat) / horizontally(_ margin: Percent) / horizontally() / horizontally(_ margin: UIEdgeInsets)
    定位左侧和右侧边缘。外边距指定相对于其父视图对应边缘的像素距离(或相对于父视图宽度的百分比)。
    horizontally()与调用horizontally(0)类似。
    horizontally(:UIEdgeInsets)使用UIEdgeInsets的left和right值来固定左侧和右侧边缘。

  • vertically(_ margin: CGFloat) / vertically(_ margin: Percent) / vertically() / vertically(_ margin: UIEdgeInsets)
    定位顶部和底部边缘。外边距指定相对于其父视图对应边缘的像素距离(或相对于父视图高度的百分比)。
    vertically()与调用vertically(0)类似。
    vertically(:UIEdgeInsets)使用UIEdgeInsets的top和bottom值来固定顶部和底部边缘。

使用示例
   view.pin.top(20).bottom(20)   // The view has a top margin and a bottom margin of 20 pixels 
   view.pin.top().left()         // The view is pinned directly on its parent top and left edge
   view.pin.all()                // The view fill completely its parent (horizontally and vertically)
   view.pin.all(pin.safeArea)    // The view fill completely its parent safeArea 
   view.pin.top(25%).hCenter()   // The view is centered horizontally with a top margin of 25%
   view.pin.left(12).vCenter()   // The view is centered vertically
   view.pin.start(20).end(20)    // Support right-to-left languages.
   view.pin.horizontally(20)     // The view is filling its parent width with a left and right margin.
   view.pin.top().horizontally() // The view is pinned at the top edge of its parent and fill it horizontally.

相对于父视图的布局多个边缘

本节介绍了与前一节边布局中描述的方法类似的方法,不同之处在于它们可以同时定位两条边。可以用作设置两条连续边时的快捷方式。

示例

该示例将视图定位在其父视图顶部右侧,并设置大小为100像素。

	viewA.pin.topRight().size(100)

这与以下等效

	viewA.pin.top().right().size(100)

方法

📌以下方法中的offset参数可以是正数也可以是负数。在一般情况下,使用正数值。

  • topLeft(_ offset: CGFloat) / topLeft()
    定位顶部和左侧边。偏移量指定从其父视图对应边的距离(以像素为单位)。topLeft()类似于调用topLeft(0)

  • topCenter(_ topOffset: CGFloat) / topCenter()
    定位顶部和水平中心(center.x)。偏移量指定从父视图顶部边的距离(以像素为单位)。topCenter()类似于调用topCenter(0)

  • topRight(_ offset: CGFloat) / topRight()
    定位顶部和右侧边。偏移量指定从其父视图对应边的距离(以像素为单位)。topRight()类似于调用topRight(0)

  • centerLeft(_ leftOffset: CGFloat) / centerLeft()
    定位垂直中心(center.y)和左侧边。偏移量指定从父视图左侧边的距离(以像素为单位)。centerLeft()类似于调用centerLeft(0)

  • center(_ offset: CGFloat) / center()
    定位水平和垂直中心(center.y)。偏移量指定从父视图中心的偏移量(以像素为单位)。center()类似于调用center(0)

  • centerRight(_ rightOffset: CGFloat) / centerRight()
    定位垂直中心(center.y)和右侧边。偏移量指定从父视图右侧边的距离(以像素为单位)。centerRight()类似于调用centerRight(0)

  • bottomLeft(_ offset: CGFloat) / bottomLeft()
    定位底部和左侧边。偏移量指定从其父视图对应边的距离(以像素为单位)。bottomLeft()类似于调用bottomLeft(0)

  • bottomCenter(_ bottomOffset: CGFloat) / bottomCenter()
    定位底部和水平中心(center.x)。偏移量指定从父视图底部边的距离(以像素为单位)。bottomCenter()类似于调用bottomCenter(0)

  • bottomRight(_ offset: CGFloat) / bottomRight()
    定位底部和右侧边。偏移量指定从其父视图对应边的距离(以像素为单位)。bottomRight()类似于调用bottomRight(0)

支持 LTR (从左到右) 和 RTL (从右到左) 语种的方案。
  • topStart(_ offset: CGFloat) / topStart()↔️
    在 LTR 方向下定位顶部和左边。在 RTL 方向下定位顶部和右边。

  • topEnd(_ offset: CGFloat) / topEnd()↔️
    在 LTR 方向下定位顶部和右边。在 RTL 方向下定位顶部和左边。

  • bottomStart(_ offset: CGFloat) / bottomStart()↔️
    在 LTR 方向下定位底部和左边。在 RTL 方向下定位底部和右边。

  • bottomEnd(_ offset: CGFloat) / bottomEnd()↔️
    在 LTR 方向下定位底部和右边。在 RTL 方向下定位底部和左边。

  • centerStart(_ offset: CGFloat) / centerStart()↔️
    在 LTR 方向下定位垂直中心(center.y)和左边。在 RTL 方向下定位垂直中心(center.y)和右边。

  • centerEnd(_ offset: CGFloat) / centerEnd()↔️
    在 LTR 方向下定位垂直中心(center.y)和右边。在 RTL 方向下定位垂直中心(center.y)和左边。

使用示例
   // Position a view at the top left corner with a top and left margin of 10 pixels
   view.pin.topLeft(10)

   // Position the 4 edges with a margin of 10 pixels.
   view.pin.topLeft(10).bottomRight(10)

相对边布局

基于相对定位的布局

PinLayout 提供了基于其他视图定位的方法。视图可以相对于一个或多个相对视图进行布局。以下方法用于布局一个视图的边缘(顶部、底部、左侧或右侧)。

方法

  • above(of: UIView) / above(of: [UIView])
    将视图定位在指定的视图(或多个视图)的上方。可以指定一个或多个相对视图。此方法定位视图的底部边缘。

  • below(of: UIView) / below(of: [UIView])
    将视图定位在指定的视图(或多个视图)的下方。可以指定一个或多个相对视图。此方法定位视图的顶部边缘。

  • before(of: UIView) / before(of: [UIView])↔️
    在 LTR 方向上,视图位于指定视图(或多个视图)的左侧。在 RTL 方向上,视图位于右侧。可以指定一个或多个相对视图。

  • after(of: UIView) / after(of: [UIView])↔️
    在 LTR 方向上,视图位于指定视图(或多个视图)的右侧。在 RTL 方向上,视图位于左侧。可以指定一个或多个相对视图。

  • left(of: UIView) / left(of: [UIView])
    将视图定位在指定视图(或多个视图)的左侧。类似于 before(of:)。可以指定一个或多个相对视图。此方法定位视图的右侧边缘。

  • right(of: UIView) / right(of: [UIView])
    将视图定位在指定视图(或多个视图)的右侧。类似于 after(of:)。可以指定一个或多个相对视图。此方法定位视图的左侧边缘。

📌 多个相对视图:例如,对 `below(of: [...])` 的调用指定多个相对视图,视图将被布局在这些视图下方。

📌这些方法可以将视图的相对位置固定到任何视图,即使它们不具有相同的直接父视图!它与具有共享祖先的任何视图都兼容。

使用示例
	view.pin.after(of: view4).before(of: view1).below(of: view3)
	view.pin.after(of: view2)
	view.pin.below(of: [view2, view3, view4])
示例

以下示例将使用相对定位方法,将视图C放置在视图A和B之间,间距为10px。

	viewC.pin.top().after(of: viewA).before(of: viewB).margin(10)

这是一个使用的等效解决方案。

	viewC.pin.top().left(to: viewA.edge.right).right(to: viewB.edge.left). margin(10)

这同样是一个使用horizontallyBetween()的等效解决方案。请参阅其他视图之间的布局

	viewC.pin.horizontallyBetween(viewA, and: viewB, aligned: .top).marginHorizontal(10)

使用相对边和对准的布局

PinLayout 还提供了一些方法来定位其他视图,同时也可以指定一个对齐方式。视图可以相对于一个或多个相对视图进行布局。

这与相对边布局非常相似,只是在这次布局了两个边。

方法

  • above(of: UIView, aligned: HorizontalAlignment)
    above(of: [UIView], aligned: HorizontalAlignment)
    将视图放置在指定的视图(或视图列表)之上,并使用指定的HorizontalAlignment对齐。可以指定一个或多个相对视图。

  • below(of: UIView, aligned: HorizontalAlignment)
    below(of: [UIView], aligned: HorizontalAlignment)
    将视图放置在指定的视图(或视图列表)之下,并使用指定的HorizontalAlignment对齐。可以指定一个或多个相对视图。

  • before(of: UIView, aligned: VerticalAlignment)↔️
    before(of: [UIView], aligned: VerticalAlignment)↔️
    在 LTR 方向上,视图位于指定视图(或多个视图)的左侧。在 RTL 方向上,视图位于右侧。可以指定一个或多个相对视图。

  • after(of: UIView, aligned: VerticalAlignment)↔️
    after(of: [UIView], aligned: VerticalAlignment)↔️
    在 LTR 方向上,视图位于指定视图(或多个视图)的右侧。在 RTL 方向上,视图位于左侧。可以指定一个或多个相对视图。

  • left(of: UIView, aligned: VerticalAlignment)
    left(of: [UIView], aligned: VerticalAlignment)
    将视图放置在指定视图(或视图列表)的左侧,并使用指定的VerticalAlignment进行对齐。类似于before(of:)。可以指定一个或多个相对视图。

  • right(of: UIView, aligned: VerticalAlignment)
    right(of: [UIView], aligned: VerticalAlignment)
    将视图放置在指定视图(或视图列表)的右侧,并使用指定的VerticalAlignment进行对齐。类似于after(of:)。可以指定一个或多个相对视图。

HorizontalAlignment

  • .left:视图的左边缘将与相对视图(或指定的最左侧视图)左对齐。
  • .center:视图将与相对视图水平居中对齐(或使用视图列表时,为水平居中的平均值)。
  • .right:视图的右边缘将与相对视图(或指定的最右侧视图)右对齐。
  • .start↔️:
    在LTR方向中,类似于使用.left。在RTL方向中,类似于使用.right
  • .end↔️:
    在LTR方向中,类似于使用.right。在RTL方向中,类似于使用.left

VerticalAlignment

  • .top:视图的顶部边缘将与相对视图(或指定的最顶部视图)顶部对齐。
  • .center:视图将与相对视图垂直居中对齐(或使用视图列表时,为垂直居中的平均值)。
  • .bottom:视图的底部边缘将与相对视图(或指定的最底部视图)底部对齐。

📌 多个相对视图:例如,调用 `below(of: [...], aligned:)` 时指定多个相对视图,视图将被布局在所有这些视图的下方。对齐方式将应用于所有相对视图。

📌这些方法可以将视图相对于任何视图进行布局,即使它们不具有相同的直接父视图/父级!它可以与任何具有共享祖先的视图一起使用。

用法示例
	view.pin.above(of: view2, aligned: .left)
	view.pin.below(of: [view2, view3, view4], aligned: .left)
	view.pin.after(of: view2, aligned: .top).before(of: view3, aligned: .bottom)
示例

以下示例将视图B布局在视图A下方,并在其中心对齐。

	viewB.pin.below(of: viewA, aligned: .center)

这是使用锚点实现的等效解决方案

	viewB.pin.topCenter(to: viewA.anchor.bottomCenter)
示例

以下示例将视图A布局在 UIImageView和UILabel的下方。视图A应与UIImageView左对齐,与UILabel右对齐,顶部边距为10像素。

	a.pin.below(of: [imageView, label], aligned: .left).right(to: label.edge.right).marginTop(10)

这是使用其他方法的等效解决方案

   let maxY = max(imageView.frame.maxY, label.frame.maxY)  // Not so nice
   a.pin.top(maxY).left(to: imageView.edge.left).right(to: label.edge.right).marginTop(10)

仅使用可见的相对视图进行定位

所有PinLayout的相对方法都可以接受视图数组(例如:below(of: [UIView]))。使用这些方法,可以在PinLayout使用视图列表之前过滤相对视图列表。

您可以定义自己的筛选方法,但PinLayout有一个名为visible的筛选方法,可以用于仅相对于可见视图进行布局。这在某些视图可能会根据情况显示或隐藏时非常有用。

   view.pin.below(of: visible([ageSwitch, ageField])).horizontally().

请注意,“表单”示例使用了此过滤方法,请参阅示例应用


其他视图之间的布局

PinLayout提供了方法来在两个其他视图之间定位视图,无论是水平还是垂直。这些方法同时布局两个边缘。

方法

  • horizontallyBetween(:UIView, and: UIView)
    将视图放置在两个指定视图之间。该方法布局视图的左右边缘。参考视图的顺序无关紧要。请注意,如果指定的视图之间存在水平空间,则仅应用布局。

  • verticallyBetween(:UIView, and: UIView)
    将视图垂直放置在两个指定视图之间。该方法布局视图的上下边缘。参考视图的顺序无关紧要。请注意,如果指定的视图之间存在垂直空间,则仅应用布局。

📌这些方法可以使用任何视图的引用,即使它们没有相同的直接superview/parent!它适用于任何具有共享祖先的视图。

使用示例
	view.pin.horizontallyBetween(viewA, and: viewB)
	view.pin.verticallyBetween(viewC, and: viewD)
示例

此示例使用5像素的左右边距水平定位视图,并将其顶部边缘设置为10像素。

   view.pin.horizontallyBetween(viewA, and: viewB).top(10).marginHorizontal(5)

请注意, Same结果也可以使用 alignment参数实现,将在下一节中介绍。

   view.pin.horizontallyBetween(viewA, and: viewB, aligned: .top).marginHorizontal(5)

或使用相对边缘布局

   view.pin.after(of: viewA).before(of: viewB).top(10).marginHorizontal(5)

使用对齐设置在不同视图之间布局

PinLayout 还提供方法来在两个其他视图之间定位一个视图,无论是水平还是垂直,但也可以指定一个 对齐方式

这与上一节中的方法非常相似,但在这里指定了对齐方式,并且同时进行了 三个边缘 的布局。

方法

  • horizontallyBetween(:UIView, and: UIView, aligned: VerticalAlign)
    在两个指定的视图之间水平定位视图,并使用指定的 VerticalAlign 进行对齐。视图将与指定的第一个参考视图相关对齐。请注意,只有当指定视图之间存在水平空间时,才会应用布局。

  • verticallyBetween(:UIView, and: UIView, aligned: HorizontalAlign)
    在两个指定的视图之间垂直定位视图,并使用指定的 HorizontalAlign 进行对齐。视图将与指定的第一个参考视图相关对齐。请注意,只有当指定视图之间存在垂直空间时,才会应用布局。

📌这些方法将应用与第一个指定的参考视图相关的对齐方式。如果您想使用第二个参考视图进行对齐,只需交换视图参数即可。

📌这些方法可以使用任何视图的引用,即使它们没有相同的直接superview/parent!它适用于任何具有共享祖先的视图。

水平对齐值

  • .left:视图的左边缘将左对齐到第一个视图。
  • .center:视图将与第一个视图水平居中。
  • .right:视图的右边缘将与第一个视图右对齐。
  • .start↔️:
    在LTR方向中,类似于使用.left。在RTL方向中,类似于使用.right
  • .end↔️:
    在LTR方向中,类似于使用.right。在RTL方向中,类似于使用.left

垂直对齐值

  • .top:视图的顶部边缘将与第一个视图顶部对齐。
  • .center:视图将与第一个视图垂直居中。
  • .bottom:视图的底部边缘将与第一个视图底部对齐。
使用示例
	view.pin.horizontallyBetween(viewA, and: viewB, aligned: .top)
	view.pin.verticallyBetween(viewC, and: viewD, aligned: .center)
示例

此示例示例展示了如何垂直地将视图放置在两个其他视图之间,并以顶部和底部边距为10像素相对于第一个视图居中对齐。

   view.pin.verticallyBetween(viewA, and: viewB, aligned: .center).marginVertical(10)

边框

PinLayout UIView的边框

PinLayout为UIView/NSView添加了边框属性。这些属性用于引用其他视图的边框。

PinLayout视图的边框:

  • UIView.edge.top
  • UIView.edge.vCenter
  • UIView.edge.bottom
  • UIView.edge.left
  • UIView.edge.hCenter
  • UIView.edge.right
  • UIView.edge.start↔️
  • UIView.edge.end↔️

使用边框进行布局

PinLayout提供了方法来将一个视图的边(顶部、左侧、底部、右侧、开始或结束边)连接到另一个视图的边。

方法

  • top(to edge: ViewEdge):
    将视图的顶部边框直接放置在另一个视图的边框(顶部/垂直中心/底部)上。

  • vCenter(to edge: ViewEdge):
    垂直放置视图的中心,使其直接与另一个视图的边框(顶部/垂直中心/底部)对齐。

  • bottom(to edge: ViewEdge):
    将视图的底部边框直接放置在另一个视图的边框(顶部/垂直中心/底部)上。

  • left(to: edge: ViewEdge):
    将视图的左侧边框直接放置在另一个视图的边框(左侧/水平中心/右侧)上。

  • hCenter(to: edge: ViewEdge):
    在水平方向上放置视图的中心,使其直接与另一个视图的边框(左侧/水平中心/右侧)对齐。

  • right(to: edge: ViewEdge):
    将视图的右侧边框直接放置在另一个视图的边框(左侧/水平中心/右侧)上。

  • start(to: edge: ViewEdge)↔️在LTR方向上,将视图的左侧边框直接放置在另一个视图的边框上。
    在RTL方向上,将视图的右侧边框直接放置在另一个视图的边框上。

  • end(to: edge: ViewEdge)↔️
    在LTR方向上,将视图的顶部边框直接放置在另一个视图的边框上。
    在RTL方向上,将视图的底部边框直接放置在另一个视图的边框上。

📌这些方法可以将一个视图的边框固定到任何其他视图的边框上,即使它们不具有相同的直接父视图!它与任何具有共享祖先的视图一起工作。

用法示例
	view.pin.left(to: view1.edge.right)
	view.pin.left(to: view1.edge.right).top(to: view2.edge.right)
示例 1

此示例将视图 B 的左边沿与视图 A 的右边沿对齐。它仅更改视图 B 的左边沿坐标。

	viewB.pin.left(to: viewA.edge.right)
示例 2

此示例将视图 B 水平居中于视图 A 内,顶部距离同视图为 10。请参阅图片示例:

    aView.pin.top(to: bView.edge.top).hCenter(to: bView.edge.hCenter).marginTop(10)

锚点

PinLayout 视图的锚点

PinLayout 将锚点属性添加到 UIView/NSView 中。这些属性用于引用其他视图的锚点。

PinLayout 视图的锚点:

  • UIView.anchor.topLeft / UIView.anchor.topCenter / UIView.anchor.topRight
  • UIView.anchor.topStart / UIView.anchor.topEnd↔️
  • UIView.anchor.centerLeft / UIView.anchor.centers / UIView.anchor.centerRight
  • UIView.anchor.centerStart / UIView.anchor.centerEnd↔️
  • UIView.anchor.bottomLeft / UIView.anchor.bottomCenter / UIView.anchor.bottomRight
  • UIView.anchor.bottomStart / UIView.anchor.bottomEnd↔️

使用锚点进行布局

PinLayout 可以使用锚点来定位与其他视图相关的视图。

以下方法定位视图的相应锚点到达另一个视图的锚点。

方法

  • topLeft(to anchor: Anchor)
  • topCenter(to anchor: Anchor)
  • topRight(to anchor: Anchor)
  • topStart(to anchor: Anchor)↔️
  • topEnd(to anchor: Anchor)↔️
  • centerLeft(to anchor: Anchor)
  • center(to anchor: Anchor)
  • centerRight(to anchor: Anchor)
  • centerStart(to anchor: Anchor)↔️
  • centerEnd(to anchor: Anchor)↔️
  • bottomLeft(to anchor: Anchor)
  • bottomCenter(to anchor: Anchor)
  • bottomRight(to anchor: Anchor)
  • bottomStart(to anchor: Anchor)↔️
  • bottomEnd(to anchor: Anchor)↔️

📌这些方法可以锁定视图的锚点到任何其他视图的锚点,即使它们没有相同的直接父视图!它与任何具有共享祖先的视图一起工作。

用法示例
    view.pin.topCenter(to: view1.anchor.bottomCenter)
    view.pin.topLeft(to: view1.anchor.topLeft).bottomRight(to: view1.anchor.center)
示例 1

使用锚点进行布局。此示例将视图B的topLeft锚点固定在视图A的topRight锚点上。

	viewB.pin.topLeft(to: viewA.anchor.topRight)
示例2

此示例将视图B中心定位在视图A的右上角锚点上。

	viewB.pin.center(to: viewA.anchor.topRight)
示例3

使用多个锚点布局。还可以将两个锚点组合起来定位和调整视图的大小。下面的示例将视图C定位在视图A和视图B之间,水平边距为10px。

	viewC.pin.topLeft(to: viewA.anchor.topRight)
	         .bottomRight(to: viewB.anchor.bottomLeft).marginHorizontal(10)

使用horizontallyBetween()的另一种可能的解决方案

	viewC.pin.horizontallyBetween(viewA, and: viewB, aligned: .top).height(of: viewA).marginHorizontal(10)

宽度、高度和大小

调整视图的宽度、高度和大小

PinLayout具有设置视图宽度和高度的方法。

方法

  • width(:CGFloat) / width(:Percent)
    此值指定视图的宽度(以像素计或为其父视图的百分比)。该值必须是正数。

  • width(of: UIView)
    将视图的宽度设置为引用视图的宽度。

  • height(:CGFloat) / height(:Percent)
    此值指定视图的高度(以像素计或为其父视图的百分比)。该值必须是正数。

  • height(of: UIView)
    将视图的高度设置为引用视图的高度。

  • size(:CGSize) / size(:Percent)
    该值指定视图的宽度和高度(以像素为单位或为其父视图的百分比)。值必须为非负数。

  • size(_ sideLength: CGFloat)
    该值指定视图的宽度和高度(以像素为单位),创建一个正方形视图。值必须为非负数。

  • size(of: UIView)
    将视图的大小设置为与引用视图的大小相匹配

📌width/height/size的优先级高于edges和anchors定位。

使用示例
	view.pin.width(100)
	view.pin.width(50%)
	view.pin.width(of: view1)
	
	view.pin.height(200)
	view.pin.height(100%).maxHeight(240)
	
	view.pin.size(of: view1)
	view.pin.size(50%)
	view.pin.size(250)

调整大小

PinLayout提供了根据内容调整视图大小的方法。

结果大小始终会尊重minWidthmaxWidthminHeightmaxHeight值。

方法

  • sizeToFit()
    此方法根据视图内容需求调整视图的大小,以便使用最合适的空间量。此拟合类型的效果等同于对一个视图调用sizeToFit()。结果大小来自于调用sizeThatFits(..)的结果,该调用使用了当前视图的边界。特别适用于具有内秉大小(标签、按钮等)的控件/视图。

  • sizeToFit(: FitType)
    此方法根据sizeThatFits(:CGSize)方法的结果调整视图的大小。
    PinLayout将根据fitType参数值调整视图的宽度或高度。
    如果已指定边距,则在调用视图的sizeThatFits(:CGSize)方法之前应用这些边距。

    参数fitType识别将用于调整视图大小的参考维度(宽度/高度)。

  • .width:该方法根据参考宽度调整视图的大小。

    • 如果已将相关的宽度属性固定(例如:宽度、左边距和右边距等),则参考宽度将由这些属性确定,如果没有这些属性,则使用当前视图的宽度
    • 结果宽度始终将匹配参考宽度
  • .height:该方法根据参考高度调整视图的大小。

    • 如果已将相关的高度属性固定(例如:高度、顶部和底部、边距等),则参考高度将由这些属性确定,如果没有这些属性,则使用当前视图的高度
    • 结果高度始终将匹配参考高度
  • .widthFlexible:与.width类似,但PinLayout不会将结果宽度约束为与参考宽度相匹配。结果宽度可能更小或更大,具体取决于视图的sizeThatFits(..)方法的结果。例如,如果一个单行的UILabel字符串小于参考宽度,它可能返回较小的宽度。

  • .heightFlexible:与.height类似,但PinLayout不会将结果高度约束为与参考高度相匹配。结果高度可能更小或更大,具体取决于视图的sizeThatFits(..)方法的结果。

使用示例
     // Adjust the view's size based on the result of `UIView.sizeToFit()` and center it.
     view.pin.center().sizeToFit()

     // Adjust the view's size based on a width of 100 pixels.
     // The resulting width will always match the pinned property `width(100)`.
     view.pin.width(100).sizeToFit(.width)
 
     // Adjust the view's size based on view's current width.
     // The resulting width will always match the view's original width.
     // The resulting height will never be bigger than the specified `maxHeight`.
     view.pin.sizeToFit(.width).maxHeight(100)
 
     // Adjust the view's size based on 100% of the superview's height.
     // The resulting height will always match the pinned property `height(100%)`.
     view.pin.height(100%).sizeToFit(.height)
 
    // Adjust the view's size based on view's current height.
    // The resulting width will always match the view's original height.
    view.pin.sizeToFit(.height)

    // Since `.widthFlexible` has been specified, its possible that the resulting
    // width will be smaller or bigger than 100 pixels, based of the label's sizeThatFits()
    // method result.
    label.pin.width(100).sizeToFit(.widthFlexible)
示例

以下示例布局中,将UILabel布局在UIImageView的右侧,周围有10px的边距,并调整UILabel的高度以适应文本大小。请注意,UILabel的高度已改变以适应其内容。

	label.pin.after(of: image, aligned: .top).right().marginHorizontal(10).sizeToFit(.width)

minWidth, maxWidth, minHeight, maxHeight

PinLayout有设置视图的最小和最大宽度以及最小和最大高度的方法。

📌minWidth/maxWidth & minHeight/maxHeight具有最高的优先级。高于尺寸(width/height/size,sizeToFit,aspectRatio)和边缘定位(top/left/bottom/right)。它们的值始终得到满足。

方法

  • minWidth(:CGFloat)
    minWidth(:Percent)
    该值指定视图的最小宽度,以像素为单位(或占其父视图的百分比)。该值必须是非负值。

  • maxWidth(:CGFloat)
    maxWidth(:Percent)
    该值指定视图的最大宽度,以像素为单位(或占其父视图的百分比)。该值必须是非负值。

  • minHeight(:CGFloat)
    minHeight(:Percent)
    该值指定视图的最小高度,以像素为单位(或占其父视图的百分比)。该值必须是非负的。

  • maxHeight(:CGFloat)
    maxHeight(:Percent)
    此值指定视图中视图的最大高度,以像素为单位(或为其父视图的百分比值)。此值必须为非负数。

使用示例
	view.pin.left(10).right(10).maxWidth(200)
	view.pin.width(100%).maxWidth(250)
	
	view.pin.top().bottom().maxHeight(100)
	view.pin.top().height(50%).maxHeight(200)
示例

此示例布局将视图放置在顶部20像素处,并从左到右水平排列,最大宽度为200像素。如果父视图小于200像素,视图将占据全部水平空间,但对于更大的父视图,视图将居中。

   viewA.pin.top(20).hCenter().width(100%).maxWidth(200)

以下是用 justify() 方法实现的等效解决方案。该方法在下一节中解释

   viewA.pin.top(20).horizontally().maxWidth(200).justify(.center)

边距

PinLayout 提供了应用边距的方法。PinLayout 运用边距的方式类似于 CSS。

方法

  • marginTop(:CGFloat) / marginTop(: Percent)
    设置顶部边距,以像素或其父视图高度的百分比值表示。
  • marginLeft(:CGFloat) / marginLeft(: Percent)
    设置左侧边距,以像素或其父视图宽度的百分比值表示。
  • marginBottom(:CGFloat) / marginBottom(: Percent)
    设置底部边距,以像素或其父视图高度的百分比值表示。
  • marginRight(:CGFloat) / marginRight(: Percent)
    设置右侧边距,以像素或其父视图宽度的百分比值表示。
  • marginStart(:CGFloat) / marginStart(: Percent)↔️
    设置起始边距。取决于 Pin.layoutDirection(...) 的值。在 LTR 方向中,起始边距指定 左边距。在 RTL 方向中,起始边距指定 右边距
  • marginEnd(:CGFloat) / marginEnd(: Percent)↔️
    设置结束边距。取决于 Pin.layoutDirection(...) 的值。在 LTR 方向中,结束边距指定 右边距。在 RTL 方向中,结束边距指定 左边距
  • marginHorizontal(:CGFloat) / marginHorizontal(: Percent)
    设置左、右、起始和结束边距为指定的值。
  • marginVertical(:CGFloat) / marginVertical(: Percent)
    设置上下边距为指定的值。
  • margin(:CGFloat) / margin(: Percent)
    将值应用于所有边距(顶部、左侧、底部、右侧),以像素或以父视图宽度/高度的百分比表示。
  • margin(:UIEdgeInsets)
    使用UIEdgeInsets设置所有边距。此方法特别有用,可使用安全区域、可读性和布局边距来设置所有边距。
  • margin(_ insets: NSDirectionalEdgeInsets)
    使用NSDirectionalEdgeInsets设置所有边距。此方法在支持RTL/LTR语言的视图中布局时很有用。
  • margin(_ vertical: CGFloat, _ horizontal: CGFloat)
    margin(_ vertical: Percent, _ horizontal: Percent)
    分别设置垂直边距(顶部、底部)和水平边距(左侧、右侧、起始、结束)。
  • margin(_ top: CGFloat, _ horizontal: CGFloat, _ bottom: CGFloat)
    margin(_ top: Percent, _ horizontal: Percent, _ bottom: Percent)
    分别设置顶部、水平边距和底部边距。
  • margin(_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat)
    margin(_ top: Percent, _ left: Percent, _ bottom: Percent, _ right: Percent)
使用示例
	view.pin.top().left().margin(20)
	view.pin.bottom().marginBottom(20)
	view.pin.horizontally().marginHorizontal(20)
	view.pin.all().margin(10, 12, 0, 12)

PinLayout边距规则

以下部分解释了CSS/PinLayout边距规则是如何应用的。

PinLayout中何时以及如何应用水平边距?

此表解释了根据哪个视图的属性已被固定,在何时以及如何应用左和右边距。

视图的固定属性 左侧边距 右侧边距
左侧 将视图向右移动 -
左侧和宽度 将视图向右移动 -
右侧 - 将视图向左移动
右侧和宽度 - 将视图向左移动
左侧和右侧 减少宽度以应用左侧边距 将宽度减小以应用右外边距
水平居中 将视图向右移动 电影视图左侧

注意:-表示没有应用外边距。


何时以及在PinLayout中应用垂直外边距的规则是什么?

此表解释了根据已固定的视图属性是何时以及如何应用上、下外边距。

视图的固定属性 上外边距 下外边距
顶部 移动视图向下 -
顶部和高度 移动视图向下 -
底部 - 移动视图向上
底部和高度 - 移动视图向上
顶部和底部 减小高度以应用顶部外边距 减小高度以应用底部外边距
垂直居中 移动视图向下 电影视图向上

外边距示例

示例 1

在此示例中,仅应用了外边距

	view.pin.left().margin(10)
示例 2

在本例中,仅应用了边距。

	view.pin.right().width(100).marginHorizontal(10)
示例 3

在本例中,同时应用了边距和边距

	view.pin.left().right().margin(10)
示例 4

在本例中,同时应用了边距、边距和边距。请注意,为了应用左右边距,视图的宽度已经减少。

	view.pin.top().left().right().height(100).margin(10)
示例 5

在本例中,同时应用了边距、边距、边距和边距。

	view.pin.top().bottom().left().right().margin(10)

pinEdges()和边距

pinEdges()方法在应用边距之前固定了四个边缘(顶部、左侧、底部和右侧)。

此方法适用于宽度或高度属性已固定的场景。这是一个附加功能,在CSS中没有等效方法。

没有 pinEdges 的示例

没有使用 pinEdges() 时,将应用边距规则,而视图将被移动到左边。

	view.pin.left().width(100%).marginHorizontal(20)
具有 pinEdges 的示例

使用 pinEdges() 虽然只设置了左侧和宽度,但也会应用左右边距。原因是调用 pinEdges() 已在应用边距前固定了两个水平边框的位置。

	view.pin.left().width(100%).pinEdges().marginHorizontal(20)

注意:在特定情况下,同样可以使用不同的方法达到相同的结果

	view.pin.left().right().marginHorizontal(20)

宽高比

设置视图的宽高比。宽高比解决了已知元素一个维度和一个宽高比的问题,这一点对于图片特别有用。

仅当可以确定单个维度(宽度或高度)时,才应用宽高比,在这种情况下,将使用宽高比来计算另一个维度。

  • 宽高比定义为宽度和高度之间的比例(宽度 / 高度)。
  • 宽高比 2 表示宽度是高度的两倍。
  • 宽高比尊重项的 min(minWidth/minHeight)和 max(maxWidth/maxHeight)维度。

方法

  • aspectRatio(_ ratio: CGFloat):
    使用 CGFloat 设置视图宽高比。宽高比定义为宽度和高度之间的比例(宽度 / 高度)。

  • aspectRatio(of view: UIView):
    使用另一个 UIView 的宽高比来设置视图宽高比。

  • aspectRatio():
    如果布局的视图是 UIImageView,此方法将使用 UIImageView 的图像尺寸设置宽高比。对于其他类型的视图,此方法没有影响。

使用实例
	aView.pin.left().width(100%).aspectRatio(2)
	imageView.pin.left().width(200).aspectRatio()
示例

此示例在顶部布局一个UIImageView并将其水平居中,同时调整其宽度为50%。视图的高度将自动按图像宽高比进行调整。

   imageView.pin.top().hCenter().width(50%).aspectRatio()

安全区域、可读区域和键盘边距

UIKit公开了四种区域/指南,可用于布局视图。PinLayout通过以下属性公开它们:

  1. UIView.pin.safeArea:公开UIKit的UIView.safeAreaInsets / UIView.safeAreaLayoutGuide
  2. UIView.pin.readableMargins:公开UIKit的UIView.readableContentGuide
  3. UIView.pin.layoutMargins:公开UIKit的UIView.layoutMargins / UIView.layoutMarginsGuide
  4. UIView.pin.keyboardArea:公开UIKit的UIView.keyboardLayoutGuide。 [iOS 15+]

以下图像显示了横向模式下的iPad上的3个区域。(安全区域、可读边距、布局边距)

请参阅示例应用中的安全区域 & 可读边距示例。

1. pin.safeArea

PinLayout可以轻松处理iOS 11的UIView.safeAreaInsets,但它更进一步,通过添加一个属性UIView.pin.safeArea支持旧版iOS发行版的safeAreaInsets(包括iOS 7/8/9/10)。PinLayout还扩展了对iOS 7/8/9/10上的UIView.safeAreaInsetsDidChange()回调的支持。

属性
  • UIView.pin.safeArea
    视图的安全区域代表不覆盖导航栏、标签栏、工具栏和其他遮挡视图控制器的视图的祖先的区域。

    PinLayout通过UIView.pin.safeArea公开视图的安全区域边界,这个属性在iOS 11上可用,但在iOS 7/8/9/10上也 适用!这使得你立即有机会在任何iOS版本中使用该属性。UIView.pin.safeArea即使在未经PinLayout布局视图的情况下也可用。

    当在iOS 11设备上运行时,此属性简单公开UIKit的UIView.safeAreaInsets。但在以前的iOS版本中,PinLayout使用UIViewControllertopLayoutGuidebottomLayoutGuide信息来计算安全区域。

使用示例
   // Layout from a UIView
   view.pin.all(pin.safeArea)             // Fill the parent safeArea
   view.pin.top(pin.safeArea)             // Use safeArea.top to position the view
   view.pin.left(pin.safeArea.left + 10)  // Use safeArea.left plus offset of 10 px
   view.pin.horizontally(pin.safeArea)    // Fill horizontally the parent safeArea
	
   // Layout from a UIViewController(), you access 
   // its view safeArea using 'view.pin.safeArea'.
   button.pin.top(view.pin.safeArea)
UIView.safeAreaInsetsDidChange()
  • iOS 11还引入了当视图的安全区域变更时会被调用的方法UIView.safeAreaInsetsDidChange()。这个方法只在你的应用在iOS 11设备上运行时调用。PinLayout扩展了它,并且也在包括iOS 9/10在内的旧版iOS中支持此方法。

  • 注意,如果你在UIView.layoutSubviews()UIViewController.viewDidLayoutSubviews()中处理布局,那么你可能不需要实现safeAreaInsetsDidChange()。默认情况下,布局会在安全区域safeAreaInsets变化时变得无效,并且这些方法会被调用。

  • 控制PinLayout中UIView.safeAreaInsetsDidChange()的调用
    你可以控制PinLayout如何在iOS 7/8/9/10(默认情况下iOS 11会原生调用此方法)上调用UIView.safeAreaInsetsDidChange()

    Pin.safeAreaInsetsDidChangeMode属性支持3种模式:

    • 始终(默认模式):在这个模式下,PinLayout将在iOS 7/8/9/10的iOS版本上自动调用你的视图的safeAreaInsetsDidChange()方法。

       Pin.safeAreaInsetsDidChangeMode = .always // Default mode
       ...
       
       class CustomerView: UIView {
          override func safeAreaInsetsDidChange() {
            // This method will be called on iOS 11, but also on iOS 7/8/9/10 
            // because "Pin.safeAreaInsetsDidChangeMode" has been set to ".always".
            if #available(iOS 11.0, *) {
               super.safeAreaInsetsDidChange()
            }
            ...
         }
      }
    • optIn: (默认模式)在此模式下,PinLayout 仅在视图实现了 PinSafeAreaInsetsUpdate 协议时才会调用视图的 safeAreaInsetsDidChange() 方法。这确保了 PinLayout 不会干扰任何预期在 iOS 11 上调用 safeAreaInsetsDidChange() 的源代码。

       Pin.safeAreaInsetsDidChangeMode = .optIn
       ...
       
       class CustomerView: UIView, PinSafeAreaInsetsUpdate {
          override func safeAreaInsetsDidChange() {
            // This  method will be called on iOS 11, but also on iOS 7/8/9/10 
            // because the view implements the protocol PinSafeAreaInsetsUpdate
            if #available(iOS 11.0, *) {
               super.safeAreaInsetsDidChange()
            }
             ...
         }
      }
    • disable:在此模式下,PinLayout 在 iOS 8/9/10 上不会调用 UIView.safeAreaInsetsDidChange。注意,在 iOS 8 上,这是默认模式。

使用 UIView.pin.safeArea 的示例

此示例布局在安全区域内放置 4 个子视图。由于 UINavigationBar 和 UITabBar 是半透明的,即使容器 UIView 处于两者之下,我们也可以使用其 UIView.pin.safeArea 来保持其子视图处于安全区域内。

   topTextLabel.pin.top(pin.safeArea.top + 10).hCenter()
   iconImageView.pin.hCenter().vCenter(-10%)
   textLabel.pin.below(of: iconImageView).hCenter().width(60%).maxWidth(400).sizeToFit(.width).marginTop(20)
   scanButton.pin.bottom(pin.safeArea).hCenter().width(80%).maxWidth(300).height(40)

此示例在 iPhone X(iOS 11)上运行得非常好,但它也可以在任何 iOS 7、8、9 和 10 的设备上运行。

📌此示例可在 示例应用 中找到。查看示例完整 源代码


2. pin.readableMargins

属性
  • pin.readableMargins:UIEdgeInset:
    PinLayout 的 UIView.pin.readableMargins 属性将 UIKit 的 UIView.readableContentGuide 作为 UAEdgeInset 暴露出来。这非常实用,因为 UIKit 只通过 UILayoutGuide 暴露可读内容区域给自动布局。
使用示例
	label.pin.horizontally(pin.readableMargins)   // the label fill horizontally the readable area.
	view.pin.all(container.pin.readableMargins)   // the view fill its parent's readable area.
	view.pin.left(pin.readableMargins)

📌示例应用程序Examples App包含使用pin.readableMargins的一些示例。


3. pin.layoutmargins

属性
  • pin.layoutmargins: UIEdgeInset
    PinLayout的UIView.pin.layoutMargins属性直接暴露了UIKit的UIView.layoutMargins的值。该属性存在只是为了与其他区域保持一致:pin.safeAreapin.readableMarginspin.layoutmargins。因此,其使用并不是必须的。
使用示例
	view.pin.left(container.pin.layoutmargins)
	view.pin.left(container.layoutmargins)     // Similar to the previous line

4. pin.keyboardArea

属性
  • pin.keyboardArea: CGRect [iOS 15及以上]
    该属性暴露了 UIKit 的值 UIView.keyboardLayoutGuide。它表示键盘覆盖视图的面积(CGRect)。在键盘可见时用于调整布局。[iOS 15及以上]
用法示例
   container.pin.bottom(view.pin.keyboardArea.top)

WrapContent

以下方法可用于调整视图的宽度和/或高度以包裹所有子视图。这些方法还将子视图的位置调整为紧密包裹。

方法

  • wrapContent()
    wrapContent(padding: CGFloat)
    wrapContent(padding: UIEdgeInsets)
    调整视图的宽度和高度以包裹所有子视图。此方法还将子视图的位置调整为紧密包裹。还可以为所有子视图指定可选的填充。
  • wrapContent(:WrapType)
    wrapContent(:WrapType, padding: CGFloat) wrapContent(:WrapType, padding: UIEdgeInsets)
    调整视图的宽度和高度以包裹所有子视图。接受一个 WrapType 参数以定义包裹类型。还可以为所有子视图指定可选的填充。

类型

  • WrapType
    • .horizontally:调整视图的宽度并更新子视图的水平位置。
    • .vertically:仅调整视图的高度并更新子视图的垂直位置。
    • .all:调整视图的宽度和高度并更新子视图的位置。这是默认的 WrapType 参数值 wrapContent() 方法。
使用示例
	view.pin.wrapContent().center()   // wrap all subviews and centered the view inside its parent.
	view.pin.wrapContent(padding: 20) // wrap all subviews with a padding of 20 pixels all around
	view.pin.wrapContent(.horizontally)
示例

此示例显示了不同wrapContent()方法调用的结果。
这是初始状态

源代码 结果 描述
view.pin.wrapContent() 调整视图的高度和宽度以紧密适应其子视图。
view.pin.wrapContent(padding: 10) 调整视图的高度和宽度,并在其子视图周围添加10像素的填充。
view.pin.wrapContent(.horizontally) 仅调整视图的宽度。
view.pin.wrapContent(.vertically) 仅调整视图的高度。
示例

此示例展示了如何调整一个有子视图(imageViewlabel)的视图(containerView)以适应其子视图的大小,然后在其父视图内居中。

   label.pin.below(of: imageView, aligned: .center).marginTop(4)
   containerView.pin.wrapContent(padding: 10).center()
  • 第1行:将标签放置在imageView下方,对其中心对齐,顶部边距为4像素。
  • 第2行:调整containerView的大小,并将其子视图放置在周围填充10像素的紧密包裹中。同时containerView也在其父视图(superview)内部居中。

justify() / align()

方法

  • justify(_ : 水平对齐)
    水平对齐视图。此方法在设置(使用宽度的/最小宽度/最大宽度)左、右和宽度的视图中水平对齐视图。在这种情况下,视图可能小于左右边缘之间的空间。视图可以被左对齐《strong>left`,中轴对齐《strong>center`,右对齐《strong>right`,起点对齐`*《strong>start`,终点对齐`*《strong>end`。

  • 对齐(_ : 垂直对齐方式)
    垂直对齐视图。此方法在设置(使用高度/最小高度/最大高度)顶部、底部和高度的视图中垂直对齐视图。在这种情况下,视图可能小于顶部和底部边缘之间的空间。视图可以被顶对齐`《strong>top`,中轴对齐`《strong>center`或底对齐`《strong>bottom`。

使用示例
	view.pin.horizontally().marginHorizontal(20).maxWidth(200).justify(.center)
	view.pin.below(of: A).above(of: B).width(40).align(.center)
示例

此示例布局将视图位于其父视图的左侧和右侧边缘之间,最大长度为200像素。如果没有使用`justify(:HorizontalAlign)`方法,视图将左对齐。

   viewA.pin.horizontally().maxWidth(200)

与上面相同的示例,但使用`justify(.center)`。

   viewA.pin.horizontally().maxWidth(200).justify(.center)

最后使用`justify(.right)`。

   viewA.pin.horizontally().maxWidth(200).justify(.right)
示例

此示例在视图A右方剩余空间中水平居中视图B。视图B的宽度为100像素。

   viewB.pin.after(of: viewA, aligned: .top).right().width(100).justify(.center)

仅限于UIView的天机自动布局

在手动布局过程中设置视图大小使用 sizeThatFits(_ size: CGSize) 实现,在这个方法中,视图会返回基于其父视图大小理想的尺寸。实现大小代码总是很繁琐,因为你必须先为布局编写相同的代码,然后才是为大小编写代码。大小通常使用与布局相同的规则,但实现略有不同,因为在大小计算过程中不应该修改任何子视图的 frame。由于 PinLayout 已经处理了布局,因此利用其布局引擎来计算大小是非常有意义的。

传统示例
    override func layoutSubviews() {
        super.layoutSubviews()
        scrollView.pin.all()
        imageView.pin.top().horizontally().sizeToFit(.width).margin(margin)
        textLabel.pin.below(of: imageView).horizontally().sizeToFit(.width).margin(margin)
        scrollView.contentSize = CGSize(width: scrollView.bounds.width, height: textLabel.frame.maxY + margin)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        let availableSize = CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude)
        return CGSize(width: size.width, height:
        imageView.sizeThatFits(availableSize).height +
            margin +
            textLabel.sizeThatFits(availableSize).height +
            margin
        )
    }
使用示例
    override func layoutSubviews() {
        super.layoutSubviews()
        performLayout()
        didPerformLayout()
    }

    private func performLayout() {
        scrollView.pin.all()
        imageView.pin.top().horizontally().sizeToFit(.width).margin(margin)
        textLabel.pin.below(of: imageView).horizontally().sizeToFit(.width).margin(margin)
    }
    
    private func didPerformLayout() {
        scrollView.contentSize = CGSize(width: scrollView.bounds.width, height: textLabel.frame.maxY + margin)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        autoSizeThatFits(size, layoutClosure: performLayout)
    }

通过调用 autoSizeThatFits 并传递给定的大小和一个布局闭包,可以在该闭包中执行由 PinLayout 制定的任何布局操作,而不会影响视图层次中的任何子视图的 frame。另一方面,任何非 PinLayout 相关的代码也将被执行。因此,将布局代码放在单独的函数中非常重要,以避免在大小调整期间产生任何副作用,如在上例中设置滚动视图的内容大小或可能在集合视图布局中分配 itemSize。这种依赖于布局的代码只应在调用 layoutSubviews() 作为正常布局过程的一部分时执行。

结果大小还会考虑到子视图上应用的边距,甚至包括底部和右边界。自动布局使您能够一次性编写布局逻辑,并几乎无需额外努力即可添加适当的大小行为。

自动大小示例可在 示例应用 中找到。

注意

  1. 天机自动布局目前仅适用于iOS。
  2. 天机自动布局目前仍处于测试版,因此任何评论都受到欢迎。

UIView 的变换

UIView.pinUIView pinFrame

直到现在,UIView.pin 都用于布局视图,但还有一个名为 UIView.pinFrame 的属性,在某些情况下,当视图有一个变换(UIView.transform,缩放,旋转等)时会做不同的事情。

  • pin:设置非变换视图的位置和大小。布局是在变换之前应用的。这对于想要使用变换来动画视图而不修改其布局非常有用。

  • pinFrame:设置变换视图的位置和大小。布局是在变换之后应用的。

示例

以下示例使用此视图的初始大小和位置

使用旋转变换的示例

使用 pin

  view.transform = .init(rotationAngle: CGFloat.pi / 2)
  view.pin.center().width(100).height(50)

使用 pinFrame

  view.transform = .init(rotationAngle: CGFloat.pi / 2)
  view.pinFrame.center().width(100).height(50)
使用 pin 的结果 使用 pinFrame 的结果
旋转变换
  • 使用 pin 时,视图被布局,然后应用旋转变换。
  • 使用 pinFrame 时,先应用旋转变换,然后布局视图。

与变换参照视图的布局

当布局视图相对于具有变换的视图(例如:下方(of:UIView),顶部(to edge: ViewEdge),...)时,pinpinFrame 的响应也会不同。

  • pin:PinLayout将使用相对于视图的未变换大小和位置。
  • pinFrame:PinLayout将使用相对于视图的变换大小和位置。
使用缩放变换的示例

以下示例中,视图A拥有1.5x1.5的缩放变换。视图B被布局在视图A下方。

使用 pin

  aView.transform = .init(scaleX: 1.5, y: 1.5)
  aView.pin.width(100).height(50)
  bView.pin.below(of: aView, aligned: .left)

使用 pinFrame

aView.transform = .init(scaleX: 1.5, y: 1.5)
aView.pin.width(100).height(50)
bView.pinFrame.below(of: aView, aligned: .left)
使用 pin 的结果 使用 pinFrame 的结果
缩放变换
  • 使用 pin,视图B被布局在未变换的视图A下方(以虚线表示)。
  • 使用 pinFrame,视图B被布局在变换后的视图A下方。

警告

PinLayout的警告

当无法应用或已存在无效的pin规则时,PinLayout可能会在控制台中显示警告。

以下是一些警告示例

  • 新附加的pin属性与其他已附加的pin属性冲突。
    示例
    view.pin.left(10).right(10).width(200)
    👉布局冲突:width(200)无法应用,因为它与以下已设置的属性冲突:left: 0, right: 10。

  • 新固定的属性已经设置为另一个值。
    示例
    view.pin.width(100).width(200)
    👉布局冲突:width(200)无法应用,因为它的值已经被设置为100。

  • 正在布局的视图尚未添加到父视图。
    示例
    view.pin.width(100)
    👉布局警告:width(100)无法应用,在使用此方法进行布局之前必须将视图添加为子视图。

  • 视图被用作参考,无论是直接使用还是通过其锚点或边缘,但尚未添加到父视图。
    示例
    view.pin.left(of: view2)
    👉布局警告:left(of: view2)无法应用,在使用之前必须将视图添加为子视图。

  • 宽度和高度必须是正数值。
    示例
    view.pin.width(-100)
    👉布局警告:宽度 (-100) 必须大于或等于 0。

  • justify(.left|.center|.right)已使用,但未设置左、右坐标。
    示例
    view.pin.left().width(250).justify(.center)
    👉PinLayout 警告:justify(center) 无法应用,必须设置左、右坐标以对齐视图。

  • 布局必须在 主线程 上执行。
    👉PinLayout 警告:布局必须在主线程上执行!

  • 布局必须在 主线程 上执行。

  • ...

启用/禁用警告

属性
  • Pin.logWarnings: Boolean
    此属性指定PinLayout的警告是否在控制台显示。在Debug (#if DEBUG) 中默认值为true,否则为false。可以在运行时修改该值。

分别启用/禁用警告

一些单独的警告也可以分别启用/禁用

  • Pin.activeWarnings.noSpaceAvailableBetweenViews: Boolean
    如果为真,当在调用 horizontallyBetween(...)verticallyBetween(...) 中指定视图之间没有间距时,会显示警告。
  • Pin.activeWarnings. aspectRatioImageNotSet: Boolean
    如果为真,当在未设置有效 UIImage 的 UIImageView 上调用 'aspectRatio()' 时,会显示警告。

PinLayout 样式指南

  • 您应该始终按照相同顺序指定方法,这样可以使布局行更容易理解。下面是我们推荐的顺序:
    view.pin.[EDGE|ANCHOR|RELATIVE].[WIDTH|HEIGHT|SIZE].[pinEdges()].[MARGINS].[sizeToFit()]

    此顺序反映了 PinLayout 内部的逻辑。首先应用 pinEdges(),然后应用边距,最后应用 sizeToFit()

     view.pin.top().left(10%).margin(10, 12, 10, 12)
     view.pin.left().width(100%).pinEdges().marginHorizontal(12)
     view.pin.horizontally().margin(0, 12).sizeToFit(.width)
     view.pin.width(100).height(100%)
  • 您应该始终以相同的顺序指定边,这是我们的建议顺序:
    TOP, BOTTOM, LEFT, RIGHT

     view.pin.top().bottom().left(10%).right(10%)
  • 如果布局行太长,可以分成多行。

     textLabel.pin.below(of: titleLabel)
        .before(of: statusIcon).after(of: accessoryView)
        .above(of: button).marginHorizontal(10)

📌PinLayout 的方法调用顺序无关紧要,布局结果始终相同。


使用 PinLayout 进行动画

PinLayout 可以轻松地动画化视图。可以使用多种策略通过 PinLayout 动画布局。

请参阅“使用 PinLayout 进行动画”部分以获取更多详细信息

此动画示例可在 示例应用 中找到。


更多示例

调整容器大小

以下示例显示了如何使用PinLayout调整视图的大小和位置以适应其容器的大小。在这种情况下,容器是单元格。

单元格A

  • A1左对齐,宽度为50px
  • A2填充剩余空间
   a1.pin.vertically().left().width(50)
   a2.pin.after(of: a1, aligned: .top).bottomRight().marginLeft(10)

单元格B

  • B2右对齐,宽度固定为50px
  • B1填充剩余空间
   b2.pin.vertically().right().width(50)
   b1.pin.before(of: b2, aligned: .top).bottom().left().marginRight(10)

单元格C

  • C2居中,宽度固定为50px
  • C1填充剩余的左侧空间
  • C3填充剩余的右侧空间
   c2.pin.vertically().hCenter().width(50)
   c1.pin.before(of: c2, aligned: .top).bottom().left().marginRight(10)
   c3.pin.after(of: c2, aligned: .top).bottom().right().marginLeft(10)

单元格D

  • D1占容器宽度的25%
  • D2占容器宽度的50%
  • D3填充剩余空间
   d1.pin.vertically().left().width(25%)
   d2.pin.after(of: d1, aligned: .top).bottom().width(50%).marginLeft(10)
   d3.pin.after(of: d2, aligned: .top).bottom().right().marginLeft(10)

安装

CocoaPods

要使用CocoaPods将PinLayout集成到您的Xcode项目中,请在您的`Podfile`中指定它

    pod 'PinLayout'

然后,运行`pod install`。

Swift 包管理器 (SPM)

  1. 从 Xcode 菜单中选择 文件 > Swift 包 > 添加包依赖
  2. 指定 URL https://github.com/layoutBox/PinLayout

Carthage

要使用 Carthage 在 Xcode 项目中集成 PinLayout,请在您的 Cartfile 中指定它。

github "layoutBox/PinLayout"

然后,运行 carthage update 来构建框架,并将构建的 PinLayout.framework 拖入您的 Xcode 项目。


示例应用

PinLayout 的示例应用展示了 PinLayout 的使用示例。

查看示例应用部分以获取更多信息 See the Example App section to get more information

包含示例

  • 之前在本 README 中展示的简介示例
  • 使用具有可变高度单元格的UITableView的示例。
  • 使用具有可变高度单元格的UICollectionView的示例。
  • 展示如何使用 PinLayout 进行动画的示例。
  • 使用 pin.safeArea, pin.readableMarginspin.layoutMargins 的示例
  • 使用 wrapContent() 的示例
  • 使用 autoSizeThatFits 的示例
  • 示例展示了从右到左(RTL)语言支持。
  • 示例展示了一个简单的表单示例。
  • 示例展示了相对边(Relative Edges)布局。
  • 使用了Objective-C的示例。
  • ...


macOS支持

PinLayout可以在macOS上布局NSView。除了以下情况,所有PinLayout的属性和方法都可用:

  • PinLayout仅支持具有父视图(superview)并使用翻转坐标系的视图,即返回值为true的属性var isFlipped: Bool的视图。在翻转坐标系中,原点位于视图的左上角,y值向下延伸。UIKit使用这种坐标系。在非翻转坐标系(默认模式)中,原点位于视图的左下角,正y值向上延伸。有关NSView.isFlipped的更多信息,请参阅Apple的文档。非翻转坐标系统的支持将很快添加。

  • sizeToFit(:FitType)仅支持继承自NSControl的实例。您可以将对sizeToFit(:FitType)的支持添加到自定义NSView子类,只需让这些视图符合SizeCalculable协议并实现所需的sizeThatFits(:CGSize)函数即可。

  • NSView.pin.safeArea属性不可用,AppKit没有与UIView.safeAreaInsets等效的属性。

  • NSView.pin.readableMargins属性不可用,AppKit没有与UIView.readableContentGuide等效的属性。

  • aspectRatio()不带参数。


CALayer支持

PinLayout可以布局CALayer。所有PinLayout的属性和方法都可用,以下为例外:

用法示例
aLayer = CALayer()
bLayer = CALayer()
view.layer.addSublayer(aLayer)
view.layer.addSublayer(bLayer)
...

aLayer.pin.top(10).left(10).width(20%).height(80%)
bLayer.pin.below(of: aLayer, aligned: .left).size(of: aLayer)

PinLayout 在 Xcode 沙箱中的使用

由于 ARC(自动引用计数)的存在,PinLayout 在沙箱中的布局是在执行完包含 .pin 的行后立即完成的。但是在 Xcode 沙箱中,ARC 并没有按预期工作,对象引用保留的时间更长。这是一个记录良好的问题,对 PinLayout 的行为产生了一定影响。

更多详情请参阅这里使用 PinLayout 在 Xcode 沙箱中


使用 Objective-C 进行 PinLayout

PinLayout 还提供了一个略低于 Swift 接口的 Objective-C 接口。

更多信息请见此处


常见问题解答 (FAQ)

  • 问:当设备旋转时,布局不更新。
    答:PinLayout 不使用自动布局约束,它是一个手动布局视图的框架。因此,您需要在 UIView.layoutSubviews()UIViewController.viewDidLayoutSubviews() 中更新布局,以处理容器大小的变化,包括设备旋转。如果您使用支持多任务的应用程序,还需要处理 UITraitCollection 的变化。

  • 问:如何处理新的 iOS 11 UIView.safeAreaInsets 和 iPhone X?
    答:iOS 11 介绍了 UIView.safeAreaInsets,以特别支持 iPhone X 的横屏模式。在此模式下,UIView.safeAreaInsets 有左和右的边距。使用 PinLayout 处理这种情况的最简单方法是通过添加一个将包含所有视图子级的 contentView,调整此 contentView 以匹配 safeAreaInsets 或 PinLayout 的 UIView.pin.safeArea

所有示例中的 示例应用 都正确处理了 safeAreaInsets 并在横屏模式下在 iPhone X 上工作。许多 PinLayout 方法接受一个 UIEdgeInsets 作为参数。

请注意,**仅当 UIViewController 的主视图** 必须处理 safeAreaInsets 时,子视图不必处理它。

  • 问:如何调整 UIView 容器以匹配所有其子视图?
    答:建议的解决方案用于 Form 示例的圆角背景。假设您想调整容器的宽度以匹配所有子视图(子视图)。

    1. 首先设置容器宽度和位置
      containerView.pin.topCenter().width(100%).marginTop(10)
    2. 布局所有子视图。
    3. 最后,将容器高度设置为匹配最后一个子视图的 Y 位置
      containerView.pin.height(child6.frame.maxY + 10)
  • 问:如何从 CGFloat、Float 或 Int 值应用百分比?
    答:许多 PinLayout 方法的参数类型为 Percent。您可以通过简单地在您的值后面添加 `%` 运算符来指定此类参数(例如:view.pin.left(10%).width(50%)。如果您有类型为 CGFloat、Float 或 Int 的值,只需添加 `%` 运算符即可。

     let percentageValue: CGFloat = 50
     view.pin.width(percentageValue%)

问题、评论、想法、建议、问题……

如果您有问题,您可以在这里查看已回答的问题。

对于任何评论想法建议问题,只需打开一个问题

如果您觉得PinLayout很有趣,感谢您Star它。您稍后可以轻松检索它。

如果您想贡献力量,欢迎加入我们!

谢谢

PinLayout受到了其他优秀布局框架的启发,包括:

  • HTML的CSS:管理绝对定位中的外边距和底部/右坐标位置。
  • MCUIViewLayout:良好的绝对和相对定位。
  • Qt:锚点和边缘管理。
  • SnapKit:锚点干净的接口。

历史

PinLayout的最新历史在CHANGELOG中也可用,也见GitHub Releases

最近的重 breaking change

  • fitSize() 已于10个月后弃用,之后已移除。《.sizeToFit(...)》应现在使用。参见调整大小。(2018-08-21)

授权

MIT授权