极速的视图布局,无需自动布局。 没有魔法,纯代码,完全控制,极速运行。 简洁的语法,直观,可读且可链式调用。 PinLayout 可以布局 UIView、NSView 和 CALayer。
“未附加自动布局约束”
pin.keyboardArea
属性 [iOS 15+]pin.readableMargins
和 pin.layoutMargins
属性。sizeToFit()
方法。 请参阅 调整尺寸。📌 PinLayout 正在积极更新。 所以请经常来查看最新的变化。 您也可以 Star 它以便稍后轻松检索。
PinLayout 是 layoutBox 组织的一部分,其中包含一些与使用 Swift 进行布局相关的开源项目。 请参阅 layoutBox。
您无需选择,可以使用 PinLayout 布局一些视图,而使用自动布局布局其他视图。 您的视图只需要实现自动布局 intrinsicContentSize
属性即可。
此示例布局图像、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)
}
UIView.pin.safeArea
公开 safeAreaInsets
,此属性不仅支持 iOS 11,而且还向后兼容早期 iOS 版本(7/8/9/10)。 有关更多信息,请参阅 safeAreaInsets 支持。UIView.layoutSubviews()
或 UIViewController.viewDidLayoutSubviews()
中更新布局,以处理容器大小的变化,包括设备旋转。 您还需要处理支持多任务处理的应用程序的 UITraitCollection 更改。 在上面的示例中,PinLayout 的命令位于 UIView 的 layoutSubviews()
方法中。此示例演示了 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 比手动布局更快或相同,并且比自动布局快 8 到 12 倍,对于所有类型的 iPhone(5S/6/6S/7/8/X)都是如此。
PinLayout 可以轻松处理 iOS 11 UIView.safeAreaInsets
,但它通过添加属性 UIView.pin.safeArea
更进一步,通过为以前的 iOS 版本(包括 iOS 7/8/9/10)支持 safeAreaInsets。 请参阅此处了解更多详细信息
PinLayout 支持 macOS 10.9+。
📌 在本文档中,任何带有 UIView 或 UIEdgeInsets 类型参数的方法也同样支持在 macOS 上使用,使用 NSView 和 NSEdgeInsets。 有关更多信息,请参阅 macOS 支持。
PinLayout 支持从左到右 (LTR) 和从右到左 (RTL) 的语言。
PinLayout 可以将视图的边缘相对于其父视图边缘进行定位。
此示例布局视图 A 以适合其父视图框架,边距为 10 像素。 它固定顶部、左侧、底部和右侧边缘。
viewA.pin.top(10).bottom(10).left(10).right(10)
另一个更短的可能解决方案,使用 all()
view.pin.all(10)
方法:
以下方法用于相对于其父视图边缘定位视图的边缘。
📌 以下方法中的 offset/margin 参数可以是正数或负数。 在一般情况下,使用正值。
top(_ offset: CGFloat)
/ top(_ offset: Percent)
/ top()
/ top(_ margin: UIEdgeInsets)
定位顶部边缘。 offset 指定顶部边缘与父视图顶部边缘的距离(以像素为单位)(或以其父视图高度的百分比为单位)。 top()
类似于调用 top(0)
,它将视图的顶部边缘直接放置在其父视图的顶部边缘上。 top(:UIEdgeInsets)
使用 UIEdgeInsets.top
属性,这在使用 safeArea、readable 和 layout margins 时特别有用。
bottom(_ offset: CGFloat)
/ bottom(_ offset: Percent)
/ bottom()
/ bottom(_ margin: UIEdgeInsets)
定位底部边缘。 offset 指定底部边缘与父视图底部边缘的距离(以像素为单位)(或以其父视图高度的百分比为单位)。 bottom()
类似于调用 bottom(0)
,它将视图的底部边缘直接放置在其父视图的顶部边缘上。 bottom(:UIEdgeInsets)
使用 UIEdgeInsets.bottom
属性,这在使用 safeArea、readable 和 layout margins 时特别有用。
left(_ offset: CGFloat)
/ left(_ offset: Percent)
/ left()
/ left(_ margin: UIEdgeInsets)
定位左侧边缘。 offset 指定左侧边缘与父视图左侧边缘的距离(以像素为单位)(或以其父视图宽度的百分比为单位)。 left()
类似于调用 left(0)
,它将视图的左侧边缘直接放置在其父视图的左侧边缘上。 left(:UIEdgeInsets)
使用 UIEdgeInsets.left
属性,这在使用 safeArea、readable 和 layout margins 时特别有用。
right(_ offset: CGFloat)
/ right(_ offset: Percent)
/ right()
/ right(_ margin: UIEdgeInsets)
定位右侧边缘。 offset 指定右侧边缘与父视图右侧边缘的距离(以像素为单位)(或以其父视图宽度的百分比为单位)。 right()
类似于调用 right(0)
,它将视图的右侧边缘直接放置在其父视图的右侧边缘上。 right(:UIEdgeInsets)
使用 UIEdgeInsets.right
属性,这在使用 safeArea、readable 和 layout margins 时特别有用。
vCenter(_ offset: CGFloat)
/ vCenter(_ offset: Percent)
/ vCenter()
定位垂直中心点 (center.y)。偏移量指定视图中心点与其父视图中心点在垂直方向上的像素距离(或者其父视图高度的百分比)。正偏移量将视图向下移动,负值将其向上移动,相对于父视图的中心点。vCenter()
类似于调用 vCenter(0)
,它将视图的垂直中心点直接定位在其父视图的垂直中心点上。
hCenter(_ offset: CGFloat)
/ hCenter(_ offset: Percent)
/ hCenter()
定位水平中心点 (center.x)。偏移量指定视图中心点与其父视图中心点在水平方向上的像素距离(或者其父视图宽度的百分比)。正偏移量将视图向右移动,负偏移量将其向左移动,相对于父视图的中心点。hCenter()
类似于调用 hCenter(0)
,它将视图的水平中心点直接定位在其父视图的水平中心点上。
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)
定位顶部、左侧、底部和右侧边缘。边距指定**顶部、底部、左侧和右侧边缘**与其父视图相应边缘的像素距离。类似于调用 view.top(value).bottom(value).left(value).right(value)
。
all()
类似于调用 all(0)
。
all(:UIEdgeInsets)
尤其适用于 safeArea, readable and layout margins。
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.
本节描述的方法类似于上一节 边缘布局 中描述的方法,只是它们同时定位 2 个边缘。 它们可以用作设置 2 个连续边缘的快捷方式。
此示例将视图定位在其父视图的右上角,并将其大小设置为 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)
。
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
.left
。在 RTL(从右到左)方向,类似于使用 .right
。.end
.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().
请注意,Form 示例使用此过滤方法,请参阅 示例应用。
PinLayout 具有在两个其他视图之间水平或垂直定位视图的方法。 这些方法同时布局 2 个边缘。
方法
horizontallyBetween(:UIView, and: UIView)
在两个指定的视图之间水平定位该视图。 该方法布局视图的左边缘和右边缘。 参考视图的顺序无关紧要。 请注意,只有在指定视图之间存在水平空间时,才会应用布局。
verticallyBetween(:UIView, and: UIView)
在两个指定的视图之间垂直定位该视图。 该方法布局视图的顶部边缘和底部边缘。 参考视图的顺序无关紧要。 请注意,只有在指定视图之间存在垂直空间时,才会应用布局。
📌 这些方法可以使用对任何视图的引用,即使它们没有相同的直接父视图/父级! 它适用于具有共享祖先的任何视图。
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 还有一些方法可以在两个其他视图之间水平或垂直定位视图,但还可以指定一个**对齐方式**。
这与 前面的方法 非常相似,除了这里指定了一个对齐方式,并且**三个边缘**同时布局。
方法
horizontallyBetween(:UIView, and: UIView, aligned: VerticalAlign)
在两个指定的视图之间水平定位该视图,并使用指定的 VerticalAlign 对齐它。 该视图将与第一个指定的参考视图相关地对齐。 请注意,只有在指定视图之间存在水平空间时,才会应用布局。
verticallyBetween(:UIView, and: UIView, aligned: HorizontalAlign)
在两个指定的视图之间垂直定位该视图,并使用指定的 HorizontalAlign 对齐它。 该视图将与第一个指定的参考视图相关地对齐。 请注意,只有在指定视图之间存在垂直空间时,才会应用布局。
📌 这些方法将应用与第一个指定的参考视图相关的对齐方式。 如果要使用第二个参考视图对齐它,只需交换视图参数即可。
📌 这些方法可以使用对任何视图的引用,即使它们没有相同的直接父视图/父级! 它适用于具有共享祖先的任何视图。
HorizontalAlignment 值
.left
:视图的左边缘将与第一个视图左对齐。.center
:视图将与第一个视图水平居中。.right
:视图的右边缘将与第一个视图右对齐。.start
.left
。在 RTL(从右到左)方向,类似于使用 .right
。.end
.right
。在 RTL(从右到左)方向,类似于使用 .left
。VerticalAlignment 值
.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/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/底部)。
vCenter(to edge: ViewEdge)
:
将视图的中心垂直放置在另一个视图的边缘上(顶部/vCenter/底部)。
bottom(to edge: ViewEdge)
:
将视图的底部边缘直接放置在另一个视图的边缘上(顶部/vCenter/底部)。
left(to: edge: ViewEdge)
:
将视图的左边缘直接放置在另一个视图的边缘上(左侧/hCenter/右侧)。
hCenter(to: edge: ViewEdge)
:
将视图的中心水平放置在另一个视图的边缘上(左侧/hCenter/右侧)。
right(to: edge: ViewEdge)
:
将视图的右边缘直接放置在另一个视图的边缘上(左侧/hCenter/右侧)。
start(to: edge: ViewEdge)
在 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)
此示例将视图 B 的左边缘放置在视图 A 的右边缘上。 它仅更改视图 B 的左坐标。
viewB.pin.left(to: viewA.edge.right)
此示例将视图 B 水平居中放置在视图 A 内,顶部边距为 10,来自同一视图。
aView.pin.top(to: bView.edge.top).hCenter(to: bView.edge.hCenter).marginTop(10)
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)
使用锚点进行布局。 此示例将视图 B 的 topLeft 锚点固定在视图 A 的 topRight 锚点上。
viewB.pin.topLeft(to: viewA.anchor.topRight)
此示例将视图 B 居中放置在视图 A 的右上角锚点上。
viewB.pin.center(to: viewA.anchor.topRight)
使用多个锚点进行布局。 也可以组合两个锚点来固定视图的位置和大小。 以下示例将视图 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 比边距和锚点定位具有更高的优先级。
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
值。
方法
sizeToFit()
该方法根据视图的内容需求调整视图的大小,使其使用最合适的空间量。此拟合类型与在视图上调用 sizeToFit() 具有相同的效果。最终大小来自调用带有当前视图 bounds 的 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 的右侧,四周留有 10 像素的边距,并调整 UILabel 的高度以适应文本大小。请注意,UILabel 的高度已更改以适应其内容。
label.pin.after(of: image, aligned: .top).right().marginHorizontal(10).sizeToFit(.width)
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)
margin(_ insets: NSDirectionalEdgeInsets)
UIView.directionalLayoutMargins
设置所有边距非常有用。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)
以下部分说明如何应用 CSS/PinLayout 边距规则。
下表说明了如何以及何时应用左侧和右侧边距,具体取决于已固定哪些视图的属性。
视图的固定属性 | 左边距 | 右边距 |
---|---|---|
左 | 将视图向右移动 | - |
左侧和宽度 | 将视图向右移动 | - |
右 | - | 将视图向左移动 |
右侧和宽度 | - | 将视图向左移动 |
左侧和右侧 | 减小宽度以应用左边距 | 减小宽度以应用右边距 |
hCenter | 将视图向右移动 | 将电影视图向左移动 |
注意:-
表示未应用边距。
下表说明了如何以及何时应用顶部和底部边距,具体取决于已固定哪些视图的属性。
视图的固定属性 | 顶部边距 | 底部边距 |
---|---|---|
顶部 | 将视图向下移动 | - |
顶部和高度 | 将视图向下移动 | - |
底部 | - | 将视图向上移动 |
底部和高度 | - | 将视图向上移动 |
顶部和底部 | 减小高度以应用顶部边距 | 减小高度以应用底部边距 |
vCenter | 将视图向下移动 | 将电影视图向上移动 |
view.pin.left().margin(10)
view.pin.right().width(100).marginHorizontal(10)
在此示例中,应用了左和右边距
view.pin.left().right().margin(10)
在此示例中,应用了左、右和顶部边距。请注意,视图的宽度已减小以应用左侧和右侧边距。
view.pin.top().left().right().height(100).margin(10)
在此示例中,应用了左、右、顶部和底部边距。
view.pin.top().bottom().left().right().margin(10)
pinEdges()
方法在应用边距之前固定四个边缘(顶部、左侧、底部和右侧边缘)。
此方法在已固定宽度和/或高度属性的情况下非常有用。此方法是一个附加组件,在 CSS 中没有等效项。
如果没有 pinEdges()
,则将应用边距规则,并且视图将向左移动。
view.pin.left().width(100%).marginHorizontal(20)
有了 pinEdges()
,即使仅设置了左侧和宽度,也会应用左侧和右侧边距。原因是调用 pinEdges() 已在应用边距之前将两个水平边缘固定在其位置。
view.pin.left().width(100%).pinEdges().marginHorizontal(20)
注意:在那种特殊情况下,也可以通过不同的方式实现相同的结果
view.pin.left().right().marginHorizontal(20)
设置视图纵横比。纵横比解决了知道元素的一个维度和纵横比的问题,这对于图像特别有用。
仅当可以确定单个维度(宽度或高度)时才应用纵横比,在这种情况下,纵横比将用于计算另一个维度。
方法
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 通过以下属性来暴露它们:
UIView.pin.safeArea
: 暴露 UIKit 的 UIView.safeAreaInsets
/ UIView.safeAreaLayoutGuide
。UIView.pin.readableMargins
: 暴露 UIKit 的 UIView.readableContentGuide
。UIView.pin.layoutMargins
: 暴露 UIKit 的 UIView.layoutMargins
/ UIView.layoutMarginsGuide
。UIView.pin.keyboardArea
: 暴露 UIKit 的 UIView.keyboardLayoutGuide
。 [iOS 15+]下图展示了 iPad 横屏模式下的三个区域。(safeArea, readableMargins, layoutMargins)
请查看 示例 App 中的 SafeArea & readableMargins 示例。
PinLayout 可以轻松处理 iOS 11 的 UIView.safeAreaInsets
,并且通过添加属性 UIView.pin.safeArea
,可以进一步支持早期 iOS 版本(包括 iOS 7/8/9/10)的 safeAreaInsets。PinLayout 还扩展了对 iOS 7/8/9/10 上 UIView.safeAreaInsetsDidChange()
回调的支持。
UIView.pin.safeArea
视图的安全区域表示未被导航栏、标签栏、工具栏以及其他遮挡视图控制器视图的祖先视图覆盖的区域。
PinLayout 通过 UIView.pin.safeArea
暴露视图的 safeAreaInsets,该属性在 iOS 11 上可用,但在 iOS 7/8/9/10 上也可用! 这使您可以立即有机会在任何 iOS 版本中使用此属性。即使您不使用 PinLayout 布局视图,UIView.pin.safeArea
也可用。
在 iOS 11 设备上运行时,此属性只是简单地暴露 UIKit 的 UIView.safeAreaInsets
。但在之前的 iOS 版本中,PinLayout 使用 UIViewController
的 topLayoutGuide 和 bottomLayoutGuide 中的信息来计算 safeArea。
// 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)
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 调用 UIView.safeAreaInsetsDidChange()
(默认情况下,iOS 11 本身会调用此方法)。
属性 Pin.safeAreaInsetsDidChangeMode
支持 3 种模式:
always: (默认模式) 在此模式下,PinLayout 将自动为 iOS 版本 7/8/9/10 调用您的视图的 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: (可选模式) 在此模式下,仅当视图实现 PinSafeAreaInsetsUpdate
协议时,PinLayout 才会调用您的视图的 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 上的默认模式。
此示例在 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 中可用。请参阅完整的示例 源代码
pin.readableMargins: UIEdgeInset
:UIView.pin.readableMargins
属性将 UIKit 的 UIView.readableContentGuide
暴露为 UIEdgeInsets。这非常有用,因为 UIKit 仅使用 UILayoutGuide 将 readableContent 区域暴露给 Auto Layout。 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
的示例。
pin.layoutmargins: UIEdgeInset
UIView.pin.layoutMargins
属性直接暴露 UIKit UIView.layoutMargins
的值。该属性的存在只是为了与其他区域保持一致:pin.safeArea
、pin.readableMargins
和 pin.layoutmargins
。因此,其使用并非必需。 view.pin.left(container.pin.layoutmargins)
view.pin.left(container.layoutmargins) // Similar to the previous line
pin.keyboardArea: CGRect
[iOS 15+]UIKit
值 UIView.keyboardLayoutGuide
。它表示键盘覆盖视图的区域 (CGRect
)。当键盘可见时,可用于调整布局。[iOS 15+] container.pin.bottom(view.pin.keyboardArea.top)
以下方法对于调整视图的宽度和/或高度以包裹其所有子视图非常有用。这些方法还会调整子视图的位置以创建紧密的包裹。
方法
wrapContent()
wrapContent(padding: CGFloat)
wrapContent(padding: UIEdgeInsets)
wrapContent(:WrapType)
wrapContent(:WrapType, padding: CGFloat)
wrapContent(:WrapType, padding: UIEdgeInsets)
类型
WrapType
值.horizontally
: 调整视图的宽度并更新子视图的水平位置。.vertically
: 仅调整视图的高度并更新子视图的垂直位置。.all
: 调整视图的宽度和高度并更新子视图的位置。这是 wrapContent()
方法的默认 WrapType 参数值。 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()
方法调用的结果。
这是初始状态
此示例显示了如何调整具有子视图(imageView
和 label
)的视图 (containerView
) 的大小以适应其子视图的大小,然后将其置于其父视图的中心。
label.pin.below(of: imageView, aligned: .center).marginTop(4)
containerView.pin.wrapContent(padding: 10).center()
containerView
的大小并定位其子视图,以在其周围创建紧密的包裹,周围填充 10 像素。containerView
也位于其父视图(superview)的中心。方法
justify(_ : HorizontalAlign)
水平对齐视图。当视图的左侧、右侧和宽度已设置(使用 width/minWidth/maxWidth)时,此方法会水平对齐视图。在这种情况下,视图可能小于左侧和右侧边缘之间的可用空间。一个视图可以 左对齐、居中对齐、右对齐、起始对齐*、结束对齐*。
align(_ : VerticalAlign)
垂直对齐视图。当视图的顶部、底部和高度已设置(使用 height/minHeight/maxHeight)时,此方法会垂直对齐视图。在这种情况下,视图可能小于顶部和底部边缘之间的可用空间。一个视图可以 顶部对齐、居中对齐 或 底部对齐。
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)
作为手动布局过程的一部分调整视图大小,是通过 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 中可用。
注意
到目前为止,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
,应用旋转变换,然后在布局视图。当相对于具有变换的视图(例如:below(of:UIView), top(to edge: ViewEdge), ...)布局视图时,pin
和 pinFrame
的反应也不同。
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 的约束规则无法应用或无效时,它可以在控制台中显示警告。
以下是一些警告列表:
新固定的属性与其他已固定的属性冲突。
示例
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
一些单独的警告也可以单独启用/禁用
Pin.activeWarnings.noSpaceAvailableBetweenViews: Boolean
horizontallyBetween(...)
或 verticallyBetween(...)
指定的视图之间没有可用空间时,将显示警告。Pin.activeWarnings. aspectRatioImageNotSet: Boolean
您应该始终按相同的顺序指定方法,这样可以使布局代码更容易理解。以下是我们的首选排序
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 来实现布局动画。
以下动画示例可在示例 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 将 PinLayout 集成到您的 Xcode 项目中,请在您的 Podfile
中指定它
pod 'PinLayout'
然后,运行 pod install
。
https://github.com/layoutBox/PinLayout
要使用 Carthage 将 PinLayout 集成到您的 Xcode 项目中,请在您的 Cartfile
中指定它
github "layoutBox/PinLayout"
然后,运行 carthage update
以构建框架,并将构建的 PinLayout.framework
拖到您的 Xcode 项目中。
PinLayout 的示例 App 演示了 PinLayout 的一些用法示例。
包含的示例
pin.safeArea
, pin.readableMargins
和 pin.layoutMargins
的示例wrapContent()
的示例autoSizeThatFits
的示例PinLayout 可以在 macOS 上布局 **NSView**。所有 PinLayout 的属性和方法都可用,但以下例外:
PinLayout **仅支持具有使用翻转坐标系的父视图 (superview) 的视图**,即计算属性 var isFlipped: Bool
返回 true 的视图。在翻转的坐标系中,原点位于视图的左上角,并且 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()
。
PinLayout 可以布局 **CALayer**。所有 PinLayout 的属性和方法都可用,但以下例外:
sizeToFit(:FitType)
。可以将 sizeToFit(:FitType)
的支持添加到您的自定义 CALayer 子类,只需使这些图层符合 SizeCalculable
协议并实现所需的 sizeThatFits(:CGSize)
函数。CALayer.pin.safeArea
和 CALayer.pin.readableMargins
属性不可用。aspectRatio()
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)
由于 ARC(自动引用计数),PinLayout 在包含 .pin
的行完全执行后立即布局视图,这在 iOS/tvOS/macOS 模拟器和设备上运行良好。但在 Xcode Playgrounds 中,ARC 的工作方式与预期不符,对象引用保留的时间更长。这是一个有据可查的问题,对 PinLayout 的行为影响很小。
PinLayout 还公开了一个 Objective-C 接口,该接口与 Swift 接口略有不同。
问:当设备旋转改变时,布局没有更新。
答: 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
。
示例 App 中的所有示例都正确处理了 safeAreaInsets
,并且在 iPhone X 的横向模式下有效。许多 PinLayout 的方法都接受 UIEdgeInsets 作为参数。
请注意,**只有 UIViewController 的主视图** 必须处理 safeAreaInsets
,子视图不必处理它。
问:如何调整 UIView 容器以匹配其所有子视图?
答: 所提出的解决方案由 **Form** 示例用于其圆角背景。假设您要调整容器的高度以匹配其所有子视图(子视图)。
containerView.pin.topCenter().width(100%).marginTop(10)
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%)
如果您有疑问,可以查看已 回答的问题。
对于任何**评论**、**想法**、**建议**、**问题**,只需打开一个 issue。
如果您觉得 PinLayout 有趣,请务必 **Star** 它。 您稍后可以轻松地检索它。
如果您想贡献代码,欢迎您!
PinLayout 的灵感来自其他出色的布局框架,包括
PinLayout 的近期历史记录可在 CHANGELOG 以及 GitHub Releases 中找到。
fitSize()
在弃用 10 个月后已被移除。现在应该使用 sizeToFit(...)
代替。请参阅 调整尺寸。(2018-08-21)MIT 许可