Layoutless
Layoutless 让您花更少的时间编写 UI 代码。它提供了一种声明式地样式化和布局视图的方法。以下是使用 Layoutless 编写的 UI 代码的示例:
class ProfileView: View {
let imageView = UIImageView(style: Stylesheet.profileImage)
let nameLabel = UILabel(style: Stylesheet.profileName)
override var subviewsLayout: AnyLayout {
return stack(.vertical, alignment: .center)(
imageView,
nameLabel
).fillingParent(insets: 12)
}
}
Layoutless 不仅仅是简化 Auto Layout 代码的另一种 DSL,而是 Auto Layout 和 UIKit 之上的一个层,提供了一种抽象常见布局模式并启用一致样式的方法。它是一个非常轻量级的库 - 大约 1k 行代码。
Layoutless 有三个主要特性。
布局模式
为了使 UI 代码更具声明性,必须有一种方法来抽象和重用常见的布局模式,例如视图的大小、在父视图中布局视图或堆叠和组合多个视图。Layoutless 通过提供使您能够创建这些模式的类型来实现这一点。
基本模式
指南
假设我们需要构建一个新闻文章屏幕,其中顶部有一个图像,下面有文本表示文章正文。首先,我们会这样定义我们的视图:
let imageView = UIImageView()
let bodyLabel = UILabel()
现在,让我们构建我们的布局。布局定义了在视图层次结构中单个视图的大小、位置和结构。虽然没有布局,但我们可以使用Layout
类型来表示布局。然而,我们很少直接处理它,因为框架为UIView和几个全局函数提供了扩展方法,这些方法可用于构建布局。
例如,要堆叠我们的两个视图垂直排列,我们可以使用stack
函数。
let layout = stack(.vertical)(
imageView,
bodyLabel
)
接下来,我们希望主体有侧边距,这样就不会从屏幕的一边排列到另一边。这只需要树立一个视图。
let layout = stack(.vertical)(
imageView,
bodyLabel.insetting(left: 18, right: 18)
)
不错,直到我们遇到一个不适合屏幕的新闻文章。哦,男孩,不是滚动视图...但是,使用Layoutless它们是个乐事。为了让我们的堆叠可滚动,我们只需要再调用一个方法。
let layout = stack(.vertical)(
imageView,
bodyLabel.insetting(left: 18, right: 18)
).scrolling(.vertical)
现在我们的堆叠是垂直可滚动的。最后,我们需要定义我们的可滚动堆叠如何在父视图中布局。我们希望填满父视图,即将所有四个边限制在父视图的边上。我们可以这样做到:
let layout = stack(.vertical)(
imageView,
bodyLabel.insetting(left: 18, right: 18)
).scrolling(.vertical).fillingParent()
最终我们得到一个《布局》变量,它是《布局》类型的实例。它代表了我们的布局描述。此时还没有真正的布局。还没有设置约束。为了构建布局并让框架创建所有相关的约束和中介视图,我们需要布局我们的视图。
layout.layout(in: parentView)
这样就完成了!框架将创建必要的自动布局约束,将堆叠嵌入滚动视图,并将其添加为父视图的子视图。要了解有关更多布局模式的详细信息,请查看实现:点此查看。它非常简单!您可以轻松创建自己的模式。如果您认为某个东西可能对每个人都有用,请随时提交PR。
如果您想了解如何使事情更加简单,请继续阅读。
基础视图
构建声明式布局是一个很棒的体验,但是最后一行用来布局我们的布局并不是声明式的,它是一个命令式调用,我们对此感到不高兴。
我们需要一个地方,我们可以只是放置我们的布局,并让“系统”决定何时进行布局。因此,Layoutless提供了基础UIKit视图的子类,我们应该使用它们作为布局的构建块。这些子类非常简单,但它们使我们能够做到这一点:
class ArticleView: View { // or ArticleViewController: ViewController
let imageView = UIImageView()
let bodyLabel = UILabel()
override var subviewsLayout: AnyLayout {
return stack(.vertical)(
imageView,
bodyLabel.insetting(left: 18, right: 18)
).scrolling(.vertical).fillingParent()
}
}
View
基本上是UIView的子类,具有可覆盖的subviewsLayout
属性以提供自己的布局。这就是全部。请参考实现:这里是详细信息。
Layoutless提供了基础视图,如:View
、Control
、Label
、Button
、ImageView
、TextField
等。
样式
为了使UI代码更具声明性,除了解决布局问题外,我们还必须解决样式问题。出乎意料的是,这个问题的解决方案非常简单。您可以在关于它的文章中找到详细的解释,所以让我们来看看它是如何工作的。
我们将在即将样式化的视图或视图控制器的扩展中定义一个名为“样式表”的东西。样式表只是一个包含一组样式的命名空间(即枚举)。
extension ArticleView {
enum Stylesheet {
static let image = Style<UIImageView> {
$0.contentMode = .center
$0.backgroundColor = .lightGray
}
static let body = Style<UILabel> {
$0.font = .systemFont(ofSize: 14)
$0.textColor = .black
}
}
}
如您所见,每个样式都是Style
类型的实例。您可以通过提供一个用于样式化给定类型视图的闭包来创建Style。就是这么简单。
为了使用我们的样式,我们将使用框架提供的便利初始化器来实例化我们的视图
class ArticleView: View {
let imageView = UIImageView(style: Stylesheet.image)
let bodyLabel = UILabel(style: Stylesheet.body)
...
}
高级
布局组
通用应用通常会根据屏幕大小提供不同的布局。支持iPhone方向和iPad的应用程序可以为其屏幕的一些或全部提供三个不同的布局。iPad应用通常在全屏模式下提供一种布局,在分屏模式下提供另一种布局。大多数这些场景都需要屏幕动态更新布局,以响应用户与应用程序或设备的交互(例如旋转)。在现实生活中,这意味着根据需要维护不同的约束集并激活或停用它们。
无布局使所有这些都变得容易。您只需要定义基于一组特质的布局,然后让框架处理其他一切,从选择合适的布局到动态更改布局,当特质集合发生变化时。只需像通常那样定义布局,然后使用layoutSet
来构建基于当前活动特质的最终布局(或布局的一部分),例如
class MyViewController: ViewController {
override var subviewsLayout: AnyLayout {
let portrait: AnyLayout = ...
let landscape: AnyLayout = ...
return layoutSet(
traitQuery(traitCollection: UITraitCollection(horizontalSizeClass: .compact)) { portrait },
traitQuery(traitCollection: UITraitCollection(horizontalSizeClass: .regular)) { landscape }
)
}
}
如您所示,在layoutSet
中,我们列出了许多查询。每个查询都代表一个布局,在匹配当前特质时将激活。我们可以通过UITraitCollection
或屏幕大小进行查询。例如
layoutSet(
traitQuery(width: .lessThanOrEqual(1000)) { ... },
traitQuery(width: .greaterThanOrEqual(1000)) { ... }
)
查询特质集应该是互斥集。
请注意,应用程序的关键窗口必须使用Layoutless Window
或其子类才能使动态布局更改正常工作。
需求
- iOS 9.0+ / tvOS 9.0+
- Xcode 9
安装
Carthage
github "DeclarativeHub/Layoutless"
CocoaPods
pod 'Layoutless'
通讯
- 如果您想问一个普通问题,请提交一个问题。
- 如果您发现了一个错误,请提交一个bug或不带修复的pull request。
- 如果您有一个特性请求,请提交一个带有提案的issue。
- 如果您想贡献,提交一个pull request(请包括单元测试)。
许可
MIT 许可证 (MIT)
版权所有 (c) 2017-2018 Srdan Rasic (@srdanrasic)
在此特此授予任何获得此软件和相关文档(以下简称“软件”)副本的个人,免费、无限制地处理该软件的权利,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件副本,并允许向软件提供方提供软件的个人这样做,但受以下条件的约束:
上述版权声明和本许可声明应包含在任何软件的副本或实质性部分中。
本软件按“原样”提供,不提供任何类型的保证,无论是明示的、暗示的还是其他方式,包括但不限于对适销性、针对特定目的的适宜性和非侵权的保证。在没有任何事件发生时,作者或版权所有者不应对任何索赔、损害或其他责任,无论基于合同、侵权或其他,因软件或其使用或其他与软件相关的事项而引起。