EasyPeasy Logo

CI Status Version Carthage compatible Coverage

EasyPeasy 是一个 Swift 框架,让您能够以编程方式创建 Auto Layout 约束,而无需头痛和永无止境的样板代码。除了基本功能外,EasyPeasy 还为您解决大多数约束冲突,并且还可以将条件闭包附加到约束上,这些闭包在应用约束之前进行评估。通过这种方式,您可以根据平台、尺寸类别、方向或控制器的状态来安装 Auto Layout 约束,一切都易如反掌!

EasyPeasy 的快速浏览中,我们假设您已经了解了不同 Auto Layout API 的优点和缺点,因此您不会在这里看到并排的代码比较,只需阅读并决定 EasyPeasy 是否适合您。

EasyPeasy 的一点魅力

下面的示例非常简单,但展示了使用 EasyPeasy 实现它是多么轻松。


First touch

特性

指南

目录

安装

Swift 兼容性

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 是一组位置和尺寸属性,您可以将其应用于您的视图。您可以从所有使用 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 创建常量的简便方法

乘数

有一个自定义运算符可以简化 NSLayoutConstraint 乘数的创建。您可以像这样使用它 Width(*2),这意味着我们视图的宽度是某个东西的两倍,我们稍后会提到如何建立与某个东西的关系。

此外,您可以将 multipliersEqual.GreaterThanOrEqualLessThanOrEqual 关系结合使用。例如,Width(>=10.0*0.5) 创建一个 NSLayoutConstraint,其 value = 10.0relation = .GreaterThanOrEqualmultiplier = 0.5,而 Width(==10.0*0.5) 创建一个 NSLayoutConstraint,其 value = 10.0relation = .Equalmultiplier = 0.5

属性

EasyPeasy 提供了与 NSLayoutConstraint 属性一样多的 Attribute 类,以及我们称之为 CompoundAttributes 的东西(我们稍后将解释这些属性)。

DimensionAttributes

只有两个维度属性 WidthHeight。您可以使用方法 func like(view: UIView) -> Self 在您的视图 DimensionAttribute 和另一个视图之间创建 Auto Layout 关系。例子

contentLabel.easy.layout(Width().like(headerView))

这行代码将创建一个约束,将 contentLabel 的宽度设置为等于 headerView 的宽度。

PositionAttributes

下表显示了可用的不同位置属性。因为它们的行为类似于 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)
)

CompoundAttributes

这些属性是在幕后创建多个 DimensionAttributesPositionAttributes 的属性。例如,Size 属性将分别使用其宽度和高度 NSLayoutConstraints 创建 WidthHeight 属性。

以下是可用的 CompoundAttributes

// 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)))
// 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)))
// 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)))
// 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)))
// 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 相同的功能,它由五种情况构成

为了将这些优先级中的任何一个应用于 Attribute,必须使用方法 .with(priority: Priority)。以下示例为应用于 viewTop Attribute 赋予 UILayoutPriority500

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 数组(此操作将覆盖先前应用于 AttributeConditions)。

view.easy.layout([
  Width(200),
  Height(240)
].when { isFirstItem })

view.easy.layout([
  Width(120),
  Height(140)
].when { !isFirstItem })

ContextualConditions

此仅限 iOS 的功能是 Condition 闭包的变体,它不接收任何参数并返回布尔值。相反,Context 结构体作为参数传递,根据要应用 AttributesUIViewUITraitCollection 提供一些额外的信息。

Context 结构体上可用的属性是

这是应用于 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 })
ContextualCondition 重新评估

正如我们之前看到的,您可以通过调用 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 }
  )
}

如您所见,EasyPeasyUIViews 提供的所有不同属性和优点也适用于 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(_:) 方法将 Attributes 应用于 UIView 后,如何与它们进行交互。

更新约束

我们在介绍部分简要提到 EasyPeasy 解决了大多数约束冲突,这是真的。通常,为了更新约束或约束的常量,您必须保留对 NSLayoutConstraint 的引用,并在需要时更新常量。使用 EasyPeasy,您只需要将另一个相同或不同类型的 Attribute 应用于您的 UIView。在下面的示例中,我们有两个方法,一个方法在其中设置约束 viewDidLoad(),另一个方法在其中我们要更新 headerViewTop 属性。

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 无法防止冲突(至少目前是这样)。当多个约束无法满足时,就会发生这种情况,即存在 LeftRight 约束,并且还应用了 Width 约束(所有约束都具有相同的优先级)。但是 EasyPeasy 足够智能,可以防止冲突,例如,当用 CenterX 属性替换 LeftRight 属性时。

清除约束

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 的实际应用。

Demo iOS Demo macOS

注意:演示应用程序中的消息不是真实的,并且这些 Twitter 帐户的出现仅仅是对一些杰出的开发人员的致敬 :)

EasyPeasy playground

或者,您可以克隆 此处 提供的 Playground 项目来体验 EasyPeasy

Playground

自动生成的文档

EasyPeasy 是一个文档完善的框架,因此所有文档化的类和方法都可以在 Cocoadocs 中找到。

作者

Carlos Vidal - @nakiostudio

许可证

EasyPeasy 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。