EasyPeasy 是一个 Swift 框架,让您能够以编程方式创建 Auto Layout 约束,而无需头痛和永无止境的样板代码。除了基本功能外,EasyPeasy 还为您解决大多数约束冲突,并且还可以将条件闭包附加到约束上,这些闭包在应用约束之前进行评估。通过这种方式,您可以根据平台、尺寸类别、方向或控制器的状态来安装 Auto Layout 约束,一切都易如反掌!
在 EasyPeasy 的快速浏览中,我们假设您已经了解了不同 Auto Layout API 的优点和缺点,因此您不会在这里看到并排的代码比较,只需阅读并决定 EasyPeasy 是否适合您。
下面的示例非常简单,但展示了使用 EasyPeasy 实现它是多么轻松。
UILayoutGuide 和 NSLayoutGuide 支持。v.1.2.1 或更早版本的库。v.1.3.1。v.1.4.2。v.1.8.0。v.1.9.0 及以上版本。(感谢 Bas van Kuijck)。EasyPeasy 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile
pod "EasyPeasy"
EasyPeasy 与 Carthage 兼容。要将 EasyPeasy 作为依赖项添加到您的项目中,只需将以下行添加到您的 Cartfile
github "nakiostudio/EasyPeasy"
并像往常一样运行 carthage update。
EasyPeasy 兼容 iOS(8 及以上版本)、tvOS(9 及以上版本)和 OS X(10.10 及以上版本)。该框架已使用 Xcode 7 和 Swift 2.0 进行测试,但如果您在使用不同版本时发现任何问题,请随时报告。
EasyPeasy 是一组位置和尺寸属性,您可以将其应用于您的视图。您可以从所有使用 Auto Layout 的 UI 类(视图子类、布局指南等)中可用的 easy 属性管理这些属性。
例如,要将视图的宽度设置为 200px,您需要创建一个类为 Width 的属性,其常量值为 200,然后使用 easy.layout(_:) 方法将该属性应用于视图。
myView.easy.layout(Width(200))
因为没有高度的视图什么都不是,所以我们可以一次应用多个属性,如下所示
myView.easy.layout(
Width(200),
Height(120)
)
在前面的示例中,应用了两个属性,因此创建并添加了两个约束:一个 constant = 200 的宽度约束和一个 constant = 120 的高度约束。
在不知不觉中,我们刚刚创建了一个 EasyPeasy Constant 结构体,其中包含 NSLayoutConstraint 的常量、乘数和关系。
EasyPeasy 提供了一种使用不同 NSLayoutRelations 创建常量的简便方法
.Equal:像我们之前的示例 Width(200) 中那样创建。.GreaterThanOrEqual:像这样简单地创建 Width(>=200),这意味着我们的视图宽度大于或等于 200px。.LessThanOrEqual:按如下方式创建 Width(<=200)。有一个自定义运算符可以简化 NSLayoutConstraint 乘数的创建。您可以像这样使用它 Width(*2),这意味着我们视图的宽度是某个东西的两倍,我们稍后会提到如何建立与某个东西的关系。
此外,您可以将 multipliers 与 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 文档 中找到它们的完整描述。
| 属性 | 属性 | 属性 | 属性 |
|---|---|---|---|
| 左 | 右 | 上 | 下 |
| 前导 | 后缘 | 中心 X | 中心 Y |
| 左边距 | 右边距 | 上边距 | 下边距 |
| 前导边距 | 后缘边距 | 边距内中心 X | 边距内中心 Y |
| 首基线 | 末基线 | -- | -- |
与 DimensionAttributes 具有 like: 方法来建立 Auto Layout 关系一样,您可以使用类似的方法对 PositionAttributes 执行相同的操作。此方法是
func to(view: UIView, _ attribute: ReferenceAttribute? = nil) -> Self
下面的示例将 contentLabel 定位在 headerView 下方 10px,并与 headerView 具有相同的左边距。
contentLabel.easy.layout(
Top(10).to(headerView),
Left().to(headerView, .Left)
)
这些属性是在幕后创建多个 DimensionAttributes 或 PositionAttributes 的属性。例如,Size 属性将分别使用其宽度和高度 NSLayoutConstraints 创建 Width 和 Height 属性。
以下是可用的 CompoundAttributes
Size:如前所述,此属性会将 Width 和 Height 属性应用于视图。它可以以多种方式初始化,并且根据初始化方式,结果可能会有所不同。以下是一些示例// 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)))
Priority 枚举执行与 UILayoutPriority 相同的功能,它由五种情况构成
low:它创建一个 Auto Layout 优先级,其 Float 值为 1。
medium:它创建一个 Auto Layout 优先级,其 Float 值为 500。
high:它创建一个 Auto Layout 优先级,其 Float 值为 750。
required:它创建一个 Auto Layout 优先级,其 Float 值为 1000。
custom:它指定由开发人员在关联值 value 的情况下定义的 Auto Layout 优先级。示例:.custom(value: 650.0)。
为了将这些优先级中的任何一个应用于 Attribute,必须使用方法 .with(priority: Priority)。以下示例为应用于 view 的 Top Attribute 赋予 UILayoutPriority 的 500 值
view.easy.layout(Top(>=50).with(.medium))
您还可以将 Priority 应用于 Attributes 数组(此操作将覆盖先前应用于 Attribute 的优先级)。
view.easy.layout([
Width(200),
Height(200)
].with(.medium))
EasyPeasy 的特性之一是使用 Conditions 或闭包来评估是否应将约束应用于视图。
方法 when(condition: Condition) 将 Condition 闭包设置为 Attribute。
有很多用例,下面的示例展示了如何根据自定义变量应用不同的约束
var isCenterAligned = true
...
view.easy.layout(
Top(10),
Bottom(10),
Width(250),
Left(10).when { !isCenterAligned },
CenterX(0).when { isCenterAligned }
)
这些 Condition 闭包可以在视图的生命周期中重新评估,要做到这一点,您只需要调用便捷方法 easy.reload()。
view.easy.reload()
请记住,这些 Condition 闭包存储在属性中,因此您需要捕获您在闭包中访问的那些变量。例如
descriptionLabel.easy.layout(
Height(100).when { [weak self] in
return self?.expandDescriptionLabel ?? false
}
)
您还可以将 Condition 应用于 Attributes 数组(此操作将覆盖先前应用于 Attribute 的 Conditions)。
view.easy.layout([
Width(200),
Height(240)
].when { isFirstItem })
view.easy.layout([
Width(120),
Height(140)
].when { !isFirstItem })
此仅限 iOS 的功能是 Condition 闭包的变体,它不接收任何参数并返回布尔值。相反,Context 结构体作为参数传递,根据要应用 Attributes 的 UIView 的 UITraitCollection 提供一些额外的信息。
此 Context 结构体上可用的属性是
isPad:如果当前设备是 iPad,则为 true。isPhone:如果当前设备是 iPhone,则为 true。isHorizontalVerticalCompact:如果水平和垂直尺寸类别均为 .Compact,则为 true。isHorizontalCompact:如果水平尺寸类别为 .Compact,则为 true。isVerticalCompact:如果垂直尺寸类别为 .Compact,则为 true。isHorizontalVerticalRegular:如果水平和垂直尺寸类别均为 .Regular,则为 true。isHorizontalRegular:如果水平尺寸类别为 .Regular,则为 true。isVerticalRegular:如果垂直尺寸类别为 .Regular,则为 true。这是应用于 Attributes 数组的 ContextualConditions 的示例
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。
自版本 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。
如 属性 部分所述,您可以使用方法 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(_:) 方法将 Attributes 应用于 UIView 后,如何与它们进行交互。
我们在介绍部分简要提到 EasyPeasy 解决了大多数约束冲突,这是真的。通常,为了更新约束或约束的常量,您必须保留对 NSLayoutConstraint 的引用,并在需要时更新常量。使用 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 帐户的出现仅仅是对一些杰出的开发人员的致敬 :)
或者,您可以克隆 此处 提供的 Playground 项目来体验 EasyPeasy。
EasyPeasy 是一个文档完善的框架,因此所有文档化的类和方法都可以在 Cocoadocs 中找到。
Carlos Vidal - @nakiostudio
EasyPeasy 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。