EasyPeasy 是一个让它在 Swift 框架中创建无头痛和永不结束的样板代码的自动布局约束的技术。除了基本功能外,EasyPeasy 解决了大多数约束冲突,并可以附加在约束条件闭包之前进行评估,这样您就可以根据平台、大小类、方向……或控制器状态安装一个自动布局约束,简单快捷!
在本快速浏览中,我们假设您已经知道不同自动布局 API 的优缺点,因此在这里不会进行比较,只需阅读并决定 EasyPeasy 是否适合您。
EasyPeasy 入门
下面的示例相当简单,但展示了使用 EasyPeasy 无需麻烦就能实现的结果。
特性
- 兼容 iOS、tvOS 和 macOS。
- 轻量级且易于使用的特定领域语言。
- 解决自动布局冲突。
- 快速轻松地更新约束。
- 条件应用约束。
- 支持
UILayoutGuide
和NSLayoutGuide
。
指南
- 第 I 部分:EasyPeasy 掌握自动布局 - 简介
- 第 II 部分:EasyPeasy 掌握自动布局 - 基础
- 第 III 部分:EasyPeasy 掌握自动布局 - 关系
- 掌握EasyPeasy自动布局 IV:优先级 (即将推出...)
- 掌握EasyPeasy自动布局 V:布局指南 (即将推出...)
- 掌握EasyPeasy自动布局 VI:高级 (即将推出...)
目录
安装
Swift 兼容性
- 要使用 Swift 2.2,请使用 EasyPeasy
v.1.2.1
或更早版本的库。 - 要使用 Swift 2.3,请使用 EasyPeasy
v.1.3.1
。 - 要使用 Swift 3,请使用 EasyPeasy
v.1.4.2
。 - 要使用 Swift 4,请使用 EasyPeasy
v.1.8.0
。 - 要使用 Swift 5,请使用 EasyPeasy
v.1.9.0
及以上版本。(感谢 Bas van Kuijck)。
Cocoapods
EasyPeasy 通过 Cocoapods 提供。要安装它,只需将以下行添加到您的 Podfile 中
pod "EasyPeasy"
Carthage
EasyPeasy兼容Carthage。要将EasyPeasy添加为项目依赖,只需在Cartfile中添加以下行:
github "nakiostudio/EasyPeasy"
然后像平常一样运行carthage update
。
兼容性
EasyPeasy与iOS(8及以上)、tvOS(9及以上)和OS X(10.10及以上)兼容。此框架已在Xcode 7和Swift 2.0上进行了测试,但请不要犹豫,报告您可能发现的任何不同版本的问题。
使用说明
EasyPeasy是一组位置和尺寸属性,您可以将其应用到视图上。您可以通过Autolayout(与Auto Layout合作的UI类,视图子类、布局引导等)中可用的easy
属性来管理这些属性。
例如,要将宽度设置为200px的视图,您会创建一个具有常量值200
的Width
类属性,然后通过使用easy.layout(_:)
方法将该属性应用到视图上。
myView.easy.layout(Width(200))
因为我们没有宽度的视图什么都不是,所以您可以一次性应用多个属性,如下所示:
myView.easy.layout(
Width(200),
Height(120)
)
在上面的示例中,已应用了两个属性,因此创建了两个约束:一个具有constant = 200
的宽度约束和一个具有constant = 120
的高度约束。
常量
不知道自己,刚刚创建了一个包含常量、乘数和NSLayoutConstraint关系的EasyPeasy
Constant
结构体。
关系
EasyPeasy提供了一种简单的方法来创建具有不同NSLayoutRelations
的常量。
.Equal
:它创建方式如我们之前的例子Width(200)
所展示。.GreaterThanOrEqual
:它创建得像这样简单Width(>=200)
,意味着我们的视图的宽度大于或等于200px。.LessThanOrEqual
:它创建如下所示Width(<=200)
。
倍数器
有一个自定义运算符,可以简化创建 NSLayoutConstraint
倍数器的操作。您可以使用它如下 Width(*2)
,这意味着我们的视图宽度是 某个值 的两倍,我们稍后将介绍如何建立与这个 某个值 的关系。
此外,您可以将 倍数器
与 Equal
、.GreaterThanOrEqual
和 LessThanOrEqual
关系结合使用。例如,Width(>=10.0*0.5)
创建一个 NSLayoutConstraint
,其 value = 10.0
、relation = .GreaterThanOrEqual
和 multiplier = 0.5
,而 Width(==10.0*0.5)
创建一个 NSLayoutConstraint
,其 value = 10.0
、relation = .Equal
和 multiplier = 0.5
。
属性
EasyPeasy 提供了与 NSLayoutConstraint
具有相同数量的 Attribute
类,此外还提供了一些我们称为 CompoundAttributes
的属性(我们将在稍后解释这些属性)。
尺寸属性
仅提供了两种尺寸属性 Width
和 Height
。您可以通过使用方法 func like(view: UIView) -> Self
,在您的视图 DimensionAttribute
和其他视图之间创建一个 Auto Layout 关系。示例
contentLabel.easy.layout(Width().like(headerView))
这一行代码将创建一个约束,将其设置为 contentLabel
的宽度等于 headerView
的宽度。
位置属性
下表显示了可用的不同位置属性。因为它们的行为类似于 NSLayoutConstraint
属性,您可以在 Apple 文档 中找到它们的完整描述。
属性 | 属性 | 属性 | 属性 |
---|---|---|---|
Left | Right | Top | Bottom |
Leading | Trailing | CenterX | CenterY |
LeftMargin | RightMargin | TopMargin | BottomMargin |
LeadingMargin | TrailingMargin | CenterXWithinMargins | CenterYWithinMargins(页面内容垂直居中相对于边距) |
FirstBaseline(基准线下方第一行) | LastBaseline(基准线下方最后一行) | -- | -- |
除了DimensionAttributes
有like:
方法来建立自动布局关系外,你还可以使用类似的方法来处理PositionAttributes
。这个方法是
func to(view: UIView, _ attribute: ReferenceAttribute? = nil) -> Self
以下示例将contentLabel
在headerView
下方10px的位置进行定位,且与headerView
具有相同的左边距。
contentLabel.easy.layout(
Top(10).to(headerView),
Left().to(headerView, .Left)
)
CompoundAttributes
这些属性在内部会创建多个DimensionAttributes
或PositionAttributes
。例如,Size
属性将创建一个包含宽度与高度的NSLayoutConstraints
的Width
和Height
属性。
以下是可用的CompoundAttributes
Size
:如前所述,此属性将向视图应用宽度和高度属性。它可以通过多种方式初始化,结果可能会因此有所不同。以下是一些示例
// Apply width = 0 and height = 0 constraints
view.easy.layout(Size())
// Apply width = referenceView.width and height = referenceView.height constraints
view.easy.layout(Size().like(referenceView))
// Apply width = 100 and height = 100 constraints
view.easy.layout(Size(100))
// Apply width = 200 and height = 100 constraints
view.easy.layout(Size(CGSize(width: 200, height: 100)))
Edges
:此属性同时创建Left
、Right
、Top
和Bottom
属性。示例
// Apply left = 0, right = 0, top = 0 and bottom = 0 constraints to its superview
view.easy.layout(Edges())
// Apply left = 10, right = 10, top = 10 and bottom = 10 constraints to its superview
view.easy.layout(Edges(10))
// Apply left = 10, right = 10, top = 5 and bottom = 5 constraints to its superview
view.easy.layout(Edges(UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)))
Center
:它创建CenterX
和CenterY
属性。示例
// Apply centerX = 0 and centerY = 0 constraints to its superview
view.easy.layout(Center())
// Apply centerX = 10 and centerY = 10 constraints to its superview
view.easy.layout(Center(10))
// Apply centerX = 0 and centerY = 50 constraints to its superview
view.easy.layout(Center(CGPoint(x: 0, y: 50)))
Margins
:此属性同时创建LeftMargin
、RightMargin
、TopMargin
和BottomMargin
属性。示例
// Apply leftMargin = 0, rightMargin = 0, topMargin = 0 and bottomMargin = 0 constraints to its superview
view.easy.layout(Margins())
// Apply leftMargin = 10, rightMargin = 10, topMargin = 10 and bottomMargin = 10 constraints to its superview
view.easy.layout(Margins(10))
// Apply leftMargin = 10, rightMargin = 10, topMargin = 5 and bottomMargin = 5 constraints to its superview
view.easy.layout(Margins(UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)))
CenterWithinMargins
:它创建CenterXWithinMargins
和CenterYWithinMargins
属性。示例
// Apply centerXWithinMargins = 0 and centerYWithinMargins = 0 constraints to its superview
view.easy.layout(CenterWithinMargins())
// Apply centerXWithinMargins = 10 and centerYWithinMargins = 10 constraints to its superview
view.easy.layout(CenterWithinMargins(10))
// Apply centerXWithinMargins = 0 and centerYWithinMargins = 50 constraints to its superview
view.easy.layout(CenterWithinMargins(CGPoint(x: 0, y: 50)))
Priorities
Priority
枚举与UILayoutPriority
执行相同的功能,并分为五个情况
-
low
:它创建一个具有Float
值1
的较低的自动布局优先级。 -
medium
:它创建一个具有Float
值500
的中等优先级。 -
high
:它创建一个具有Float
值750
的高优先级。 -
required
:它创建一个具有Float
值1000
的必需优先级。 -
custom
:它指定开发者在相关值value
中定义的自动布局优先级。示例:.custom(value: 650.0)
。
为了将这些优先级应用于任何Attribute
,必须使用方法.with(priority: Priority)
。以下示例将UILayoutPriority
的值500
应用于应用于view
的Top
Attribute
。
view.easy.layout(Top(>=50).with(.medium))
您还可以将一个Priority
应用于一个Attributes
数组(此操作将覆盖应用到Attribute
的先前优先级)。
view.easy.layout([
Width(200),
Height(200)
].with(.medium))
条件
EasyPeasy的一个独特之处在于使用条件
或闭包,以评估是否应该对视图应用约束。
方法when(condition: Condition)
将条件
闭包设置到属性
。
有很多用例,下面的示例展示了如何根据自定义变量应用不同的约束:
var isCenterAligned = true
...
view.easy.layout(
Top(10),
Bottom(10),
Width(250),
Left(10).when { !isCenterAligned },
CenterX(0).when { isCenterAligned }
)
条件重新评估
这些条件
闭包可以在视图的生命周期内重新评估,为此您只需调用便利方法easy.reload()
。
view.easy.reload()
请注意,这些条件
闭包存储在属性中,因此您需要捕获闭包内访问的变量。例如:
descriptionLabel.easy.layout(
Height(100).when { [weak self] in
return self?.expandDescriptionLabel ?? false
}
)
您还可以将一个条件
应用于一个属性
数组(此操作将覆盖之前应用于属性的限制)。
view.easy.layout([
Width(200),
Height(240)
].when { isFirstItem })
view.easy.layout([
Width(120),
Height(140)
].when { !isFirstItem })
ContextualConditions
这仅适用于iOS的特性是条件
闭包的一个变种,它不接受参数并返回一个布尔值。取而代之的是,传递一个上下文
结构体作为参数,提供一些基于即将应用的UIView
的UITraitCollection
额外信息。
此上下文
结构体上的可用属性有:
isPad
:如果当前设备是iPad则为true。isPhone
:如果当前设备是iPhone则为true。isHorizontalVerticalCompact
:如果水平和垂直大小类都是.Compact
则为true。isHorizontalCompact
:如果水平大小类是.Compact
则为true。isVerticalCompact
:如果垂直大小类是.Compact
则为true。isHorizontalVerticalRegular
:如果水平和垂直大小类都是.Regular
则为true。isHorizontalRegular
:如果水平大小类是.Regular
则为true。isVerticalRegular
:如果垂直大小类是.Regular
则为true。
以下是应用于数组属性的上下文条件
示例
view.easy.layout([
Size(250),
Center(0)
].when { $0.isHorizontalRegular })
view.easy.layout([
Top(0),
Left(0),
Right(0),
Height(250)
].when { $0.isHorizontalCompact })
上下文条件重新评估
如我们之前看到的,您可以通过调用easy.reload()
便捷方法来重新评估Condition
闭包。这也适用于ContextualConditions
,因此如果您希望在您的视图UITraitCollection
发生更改时更新约束,则需要在traitCollectionDidChange(_:)
中调用easy.reload()
方法。
另外,EasyPeasy可以自动完成这一步。默认情况下,它是禁用的,因为这需要方法交换;要启用它,只需在-D EASY_RELOAD
编译器标志的框架中进行编译。
UILayoutGuides
从版本v.0.2.3(以及iOS 9项目及以上)开始,EasyPeasy集成了UILayoutGuides
支持。
应用约束
将约束应用于UILayoutGuide
与我们在前几节中讨论的一样简单,只需使用easy.layout(_:)
方法应用您想要的EasyPeasy
属性。
func viewDidLoad() {
super.viewDidLoad()
let layoutGuide = UILayoutGuide()
self.view.addLayoutGuide(layoutGuide)
layoutGuide.easy.layout(
Top(10),
Left(10),
Right(10),
Height(100).when { Device() == .iPad },
Height(60).when { Device() == .iPhone }
)
}
如您所见,EasyPeasy为UIViews
提供的所有不同属性和好东西也适用于UILayoutGuides
。
连接 UILayoutGuides 和 UIViews
如属性部分所述,您可以使用to(_:_)
和like(_:_)
方法在UIView
属性与其他UIViews
属性之间创建约束关系。现在您可以利用这些方法在您的UIView
属性和UILayoutGuide
之间创建关系。
let layoutGuide = UILayoutGuide()
let separatorView: UIView
let label: UILabel
func setupLabel() {
self.label.easy.layout(
Top(10).to(self.layoutGuide),
CenterX(0),
Size(60)
)
self.separatorView.easy.layout(
Width(0).like(self.layoutGuide),
Height(2),
Top(10).to(self.label),
CenterX(0).to(self.label)
)
}
最后一点
在这一节中,我们将解释如何使用easy.layout(_:)
方法,在将属性应用于UIView
之后与之交互,这一点虽然非最后但同样重要。
更新约束
在介绍部分我们简要提到了EasyPeasy
可以解决大多数约束冲突,这是真的。通常,为了更新一个约束或其常量,你必须保留对NLayoutConstraint
的引用,并在需要时更新常量。使用EasyPeasy
,你只需将另一个Attribute
应用到同一个或不同类型的UIView
上。以下示例中有两个方法,一个是设置约束的方法viewDidLoad()
,另一个是我们要更新headerView
的Top
属性的方法。
func viewDidLoad() {
super.viewDidLoad()
headerView.easy.layout(
Top(0),
Left(0),
Right(0),
Height(60)
)
}
func didTapButton(sender: UIButton?) {
headerView.easy.layout(Top(100))
}
就是这样!我们已经更新了Top
约束,无需关心保留引用或安装/卸载新的约束。
但是,有些情况下EasyPeasy
无法防止冲突(至少目前是这样)。这是当多个约束无法满足时,也就是说,如果一个Left
和Right
约束被应用,同时也应用了一个Width
约束(它们都有相同的优先级)。但是EasyPeasy
足够聪明,可以防止冲突,比如说当你用CenterX
属性替换Left
和Right
属性时。
清除约束
EasyPeasy
提供了一种扩展UIView
的方法,可以清除使用框架安装在一个UIView
上的所有约束。此方法为func easy.clear()
。
view.easy.clear()
动画约束
使用 EasyPeasy 动画约束非常简单直观,只需在一个动画块中将一个或多个 Attributes
应用到您的视图中,然后就可以出发了,无需担心约束冲突。示例
UIView.animateWithDuration(0.3) {
view.easy.layout(Top(10))
view.layoutIfNeeded()
}
示例项目
别忘了克隆存储库并运行 iOS 和 OS X 示例项目,以查看 EasyPeasy 的实际应用。
![]() |
![]() |
注意:演示应用程序中的消息不是真实的,那些 Twitter 账户的出现,仅仅是对一些优秀开发者的致敬:)
EasyPeasy 演示场
或者,您可以克隆 Playground 项目来使用 EasyPeasy,项目地址为 这里。
自动生成的文档
EasyPeasy 是一个文档良好的框架,因此所有已记录的类和方法都可以在 Cocoadocs 中找到。
作者
Carlos Vidal - @nakiostudio
许可证
EasyPeasy 根据MIT许可证提供。有关更多信息,请参阅 LICENSE 文件。