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

“未附加自动布局约束”

要求

近期更改/功能

内容


📌 PinLayout 正在积极更新。 所以请经常来查看最新的变化。 您也可以 Star 它以便稍后轻松检索。

PinLayout 和 layoutBox

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

PinLayout + Autolayout

您无需选择,可以使用 PinLayout 布局一些视图,而使用自动布局布局其他视图。 您的视图只需要实现自动布局 intrinsicContentSize 属性即可。


入门示例

示例 1

此示例布局图像、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)
}

示例 2

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

  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 比手动布局更快或相同,并且比自动布局快 8 到 12 倍,对于所有类型的 iPhone(5S/6/6S/7/8/X)都是如此。

请在此处查看更多详细信息、结果和基准说明.

文档

UIKit safeAreaInsets 支持

PinLayout 可以轻松处理 iOS 11 UIView.safeAreaInsets,但它通过添加属性 UIView.pin.safeArea 更进一步,通过为以前的 iOS 版本(包括 iOS 7/8/9/10)支持 safeAreaInsets。 请参阅此处了解更多详细信息

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)

方法:

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

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

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

定位多个边缘的方法

使用示例
   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.

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

本节描述的方法类似于上一节 边缘布局 中描述的方法,只是它们同时定位 2 个边缘。 它们可以用作设置 2 个连续边缘的快捷方式。

示例

此示例将视图定位在其父视图的右上角,并将其大小设置为 100 像素。

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

这等效于

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

方法

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

支持从左到右 (LTR) 和从右到左 (RTL) 语言的方法。
使用示例
   // 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 具有相对于其他视图进行定位的方法。可以相对于**一个或多个相对视图**来布局视图。以下方法布局一个视图的边缘(顶部、底部、左侧或右侧)。

方法

📌 **多个相对视图**:例如,如果调用 `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 还具有相对于其他视图进行定位的方法,但也可以指定**对齐方式**。可以相对于**一个或多个相对视图**来布局视图。

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

方法

HorizontalAlignment

VerticalAlignment

📌 多个相对视图:例如,如果调用 `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().

请注意,Form 示例使用此过滤方法,请参阅 示例应用


在其他视图之间布局

PinLayout 具有在两个其他视图之间水平或垂直定位视图的方法。 这些方法同时布局 2 个边缘。

方法

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

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

此示例在两个其他视图之间水平定位一个视图,左右边距为 5 像素,并将其顶部边缘设置为 10 像素。

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

请注意,使用对齐参数也可以实现相同的结果,请在 下一节 中进行描述

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

或者使用 相对边缘布局

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

带有对齐方式的在其他视图之间布局

PinLayout 还有一些方法可以在两个其他视图之间水平或垂直定位视图,但还可以指定一个**对齐方式**。

这与 前面的方法 非常相似,除了这里指定了一个对齐方式,并且**三个边缘**同时布局。

方法

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

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

HorizontalAlignment 值

VerticalAlignment 值

使用示例
	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 视图的边缘:

使用边缘进行布局

PinLayout 具有将视图的边缘(顶部、左侧、底部、右侧、开始或结束边缘)附加到另一个视图边缘的方法。

方法

📌 这些方法可以将视图的边缘固定到任何其他视图的边缘,即使它们没有相同的直接父视图! 它适用于具有共享祖先的任何视图。

使用示例
	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 视图的锚点:

使用锚点进行布局

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

以下方法将相应的视图锚点放置在另一个视图的锚点上。

方法

📌 这些方法可以将视图的锚点固定到任何其他视图的锚点,即使它们没有相同的直接父视图! 它适用于具有共享祖先的任何视图。

使用示例
    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/height/size 比边距和锚点定位具有更高的优先级。

使用示例
	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 具有根据视图内容调整视图大小的方法。

最终大小始终遵循 minWidth/maxWidth/minHeight/maxHeight 值。

方法

使用示例
     // 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 的右侧,四周留有 10 像素的边距,并调整 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)。它们的值始终得到满足。

方法

使用示例
	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 的边距。

方法

使用示例
	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 中应用水平边距?

下表说明了如何以及何时应用左侧和右侧边距,具体取决于已固定哪些视图的属性。

视图的固定属性 左边距 右边距
将视图向右移动 -
左侧和宽度 将视图向右移动 -
- 将视图向左移动
右侧和宽度 - 将视图向左移动
左侧和右侧 减小宽度以应用左边距 减小宽度以应用右边距
hCenter 将视图向右移动 将电影视图向左移动

注意:- 表示未应用边距。


何时以及如何在 PinLayout 中应用垂直边距?

下表说明了如何以及何时应用顶部和底部边距,具体取决于已固定哪些视图的属性。

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

边距示例

示例 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)

宽高比

设置视图纵横比。纵横比解决了知道元素的一个维度和纵横比的问题,这对于图像特别有用。

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

方法

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

此示例将 UIImageView 布局在顶部并将其水平居中,它还会将其宽度调整为 50%。视图的高度将使用图像纵横比自动调整。

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

safeArea、readable、layout 和键盘边距

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 横屏模式下的三个区域。(safeArea, readableMargins, layoutMargins)

请查看 示例 App 中的 SafeArea & readableMargins 示例。

1. pin.safeArea

PinLayout 可以轻松处理 iOS 11 的 UIView.safeAreaInsets,并且通过添加属性 UIView.pin.safeArea,可以进一步支持早期 iOS 版本(包括 iOS 7/8/9/10)的 safeAreaInsets。PinLayout 还扩展了对 iOS 7/8/9/10 上 UIView.safeAreaInsetsDidChange() 回调的支持。

属性
使用示例
   // 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()
使用 UIView.pin.safeArea 的示例

此示例在 safeArea 内布局 4 个子视图。UINavigationBar 和 UITabBar 是半透明的,因此即使容器 UIView 位于两者之下,我们也可以使用其 UIView.pin.safeArea 将其子视图保持在 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 的设备上也运行良好。

📌 此示例在 示例 App 中可用。请参阅完整的示例 源代码


2. pin.readableMargins

属性
使用示例
	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)

📌 示例 App 包含一些使用 pin.readableMargins 的示例。


3. pin.layoutmargins

属性
用法示例
	view.pin.left(container.pin.layoutmargins)
	view.pin.left(container.layoutmargins)     // Similar to the previous line

4. pin.keyboardArea

属性
用法示例
   container.pin.bottom(view.pin.keyboardArea.top)

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()

justify() / align()

方法

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

此示例在 superview 的左边缘和右边缘之间布局一个视图,最大大小为 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)
示例

此示例将视图 B 水平居中于视图 A 右侧剩余的空间中。视图 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() 作为正常布局过程的一部分被调用时执行。

生成的大小还会考虑应用于子视图的边距,即使是在底部和尾部。 自动调整大小可以非常容易地编写一次布局逻辑,并以几乎不需要额外工作的方式添加适当的调整大小行为。

自动调整大小的示例在 示例 App 中可用。

注意

  1. 自动调整大小目前仅在 iOS 上可用。
  2. 自动调整大小仍处于 beta 阶段,欢迎提出任何意见。

UIView 的变换

UIView.pinUIView.pinFrame

到目前为止,UIView.pin 用于布局视图,但还有一个名为 UIView.pinFrame 的属性,在视图具有变换 (UIView.transform、缩放、旋转等) 的情况下,它的作用略有不同。

示例

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

使用旋转变换的示例

使用 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 的结果
旋转变换

相对于具有变换的视图进行布局

当相对于具有变换的视图(例如:below(of:UIView), top(to edge: ViewEdge), ...)布局视图时,pinpinFrame 的反应也不同。

使用缩放变换的示例

在以下示例中,视图 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 的结果
缩放变换

警告

PinLayout 的警告

当 PinLayout 的约束规则无法应用或无效时,它可以在控制台中显示警告。

以下是一些警告列表:

启用/禁用警告

属性

分别启用/禁用警告

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


PinLayout 风格指南

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


使用 PinLayout 的动画

PinLayout 可以轻松地对视图进行动画处理。可以使用多种策略通过 PinLayout 来实现布局动画。

有关更多详细信息,请参阅使用 PinLayout 进行动画处理的部分

以下动画示例可在示例 App 中找到。


更多示例

调整为容器大小

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

单元格 A

   a1.pin.vertically().left().width(50)
   a2.pin.after(of: a1, aligned: .top).bottomRight().marginLeft(10)

单元格 B

   b2.pin.vertically().right().width(50)
   b1.pin.before(of: b2, aligned: .top).bottom().left().marginRight(10)

单元格 C

   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.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 Package Manager (SPM)

  1. 在 Xcode 中,从菜单中选择 **File > Swift Packages > Add Package Dependency**
  2. 指定 URL https://github.com/layoutBox/PinLayout

Carthage

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

github "layoutBox/PinLayout"

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


示例 App

PinLayout 的示例 App 演示了 PinLayout 的一些用法示例。

请参阅示例 App 部分以获取更多信息

包含的示例


macOS 支持

PinLayout 可以在 macOS 上布局 **NSView**。所有 PinLayout 的属性和方法都可用,但以下例外:


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)

Xcode Playgrounds 中的 PinLayout

由于 ARC(自动引用计数),PinLayout 在包含 .pin 的行完全执行后立即布局视图,这在 iOS/tvOS/macOS 模拟器和设备上运行良好。但在 Xcode Playgrounds 中,ARC 的工作方式与预期不符,对象引用保留的时间更长。这是一个有据可查的问题,对 PinLayout 的行为影响很小。

有关在 Xcode playgrounds 中使用 PinLayout 的更多详细信息,请参见此处


使用 Objective-C 的 PinLayout

PinLayout 还公开了一个 Objective-C 接口,该接口与 Swift 接口略有不同。

请在此处查看更多详细信息


常见问题解答

示例 App 中的所有示例都正确处理了 safeAreaInsets,并且在 iPhone X 的横向模式下有效。许多 PinLayout 的方法都接受 UIEdgeInsets 作为参数。

请注意,**只有 UIViewController 的主视图** 必须处理 safeAreaInsets,子视图不必处理它。


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

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

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

如果您觉得 PinLayout 有趣,请务必 **Star** 它。 您稍后可以轻松地检索它。

如果您想贡献代码,欢迎您!

谢谢

PinLayout 的灵感来自其他出色的布局框架,包括

历史

PinLayout 的近期历史记录可在 CHANGELOG 以及 GitHub Releases 中找到。

近期重大变更

许可

MIT 许可