AloeStackView
一个简单的类,用于使用方便的 API 布局一组视图,同时发挥 Auto Layout 的强大功能。
介绍
AloeStackView
是一个类,允许一组视图按照垂直或水平列表布局。在广义上,它与 UITableView
类似,但它的实现完全不同,并且做出了一组不同的权衡。
我们首次在 2016 年的 Airbnb iOS 应用中使用 AloeStackView
。我们后来用它来实现应用中近 200 个屏幕。用例相当多样:从设置屏幕到创建新列表的表单,再到列表共享页面。
AloeStackView
首先专注于使 UI 非常快速、简单和直观地实现。它通过两种方式来实现这一点:
-
它利用 Auto Layout 的功能来自动更新 UI,当对其视图进行更改时。
-
它放弃了如视图回收等
UITableView
的一些功能,以便实现更简单且更安全的 API。
我们发现 AloeStackView
是一件有用的基础设施,希望您也会发现它很有用!
目录
功能
-
允许您保持对视图的强引用并动态更改它们的属性,同时自动布局会自动使 UI 保持最新状态。
-
允许视图动态添加、删除、隐藏和显示,可选的动画效果。
-
包括对可自定义视图之间分隔符的内置支持。
-
提供可扩展的 API,允许在不修改
AloeStackView
本身的情况下添加特殊功能。 -
在高度访问量的 iOS 应用中被广泛使用并经过验证。
-
小型、易于理解的代码库(小于 600 行代码),无外部依赖,将二进制大小增加保持在最低,并使代码贡献和调试变得容易。
系统要求
- 目标设备 iOS 9.0+
- Xcode 10.0+
- Swift 4.0+
示例应用
库中包含一个简单的 示例 iOS 应用。
您可以通过克隆库,打开 AloeStackViewExample.xcworkspace
并运行应用来尝试它。
示例应用展示了 AloeStackView
可用于实现 iOS 应用中屏幕的几种方式。
使用方法
创建 AloeStackView 实例
主要 API 通过 AloeStackView
类访问。
您可以在代码中轻松创建 AloeStackView
的实例。
import AloeStackView
let stackView = AloeStackView()
AloeStackView
是一个 UIView
(具体为 UIScrollView
),因此可以像使用应用中的任何其他视图一样使用它。
另外,如果您想使用 AloeStackView
构建整个 UIViewController
,则可以使用方便的 AloeStackViewController
类
import AloeStackView
public class MyViewController: AloeStackViewController {
public override func viewDidLoad() {
super.viewDidLoad()
stackView.addRow(...)
}
}
AloeStackViewController
与如 UITableViewController
和 UICollectionViewController
等类非常相似,因为它为您创建并管理一个 AloeStackView
。您可以通过 stackView
属性访问 AloeStackView
。使用 AloeStackViewController
而不是在 UIViewController
中创建自己的 AloeStackView
只需少写一些代码。
添加、删除和管理行
AloeStackView
的 API 主要处理“行”。行可以是您希望在 UI 中使用的任何 UIView
。
默认情况下,行垂直排列在一个列中,并且每行都占据 AloeStackView
的全部宽度。
可以通过 AloeStackView
上的 axis
属性来更改方向。当 axis
设置为 .horizontal
时,行并排排列,从左到右顺序排列,并且 AloeStackView
水平滚动,每行占据 AloeStackView
的全部高度。
使用 AloeStackView
构建 UI 时,一般首先添加组成您 UI 的行
for i in 1...3 {
let label = UILabel()
label.text = "Label \(i)"
stackView.addRow(label)
}
如果 AloeStackView
的长度超过可用屏幕空间,内容会自动变为可滚动的。
AloeStackView
提供了一套全面的方法来管理行,包括在开头和结尾插入行、在其它行的上方或下方插入行、隐藏和显示行、删除行以及检索行。
您可以使用 rowInset
属性自定义行周围的间距,以及 setInset(forRow:)
和 setInset(forRows:)
方法。
AloeStackView.swift 中的类文档提供了所有可用 API 的完整详细信息。
处理用户交互
AloeStackView
提供了对行上的点击手势的支持
stackView.setTapHandler(
forRow: label,
handler: { [weak self] label in
self?.showAlert(title: "Row Tapped", message: "Tapped on: \(label.text ?? "")")
})
label.isUserInteractionEnabled = true
只有在行的 isUserInteractionEnabled
为 true
时,点击处理程序才会触发。
处理点击手势的另一种方法是遵循 Tappable
协议
public class ToggleLabel: UILabel, Tappable {
public func didTapView() {
textColor = textColor == .red ? .black : .red
}
}
for i in 1...3 {
let label = ToggleLabel()
label.text = "Label \(i)"
label.isUserInteractionEnabled = true
stackView.addRow(label)
}
遵循 Tappable
允许常见的点击手势处理行为封装在视图内部。这样,您就可以在 AloeStackView
中多次重用视图,而无需每次都编写相同的点击手势处理代码。
动态更改行内容
使用AloeStackView
的一个优点是可以即使在将视图添加到AloeStackView
之后,依然持有其视图的强引用。
如果你更改视图的属性,这些属性会影响整个UI的布局,AloeStackView
将自动重新布局其所有行
stackView.setTapHandler(forRow: label, handler: { label in
label.text = (label.text ?? "") + "\n\nSome more text!"
})
如你所见,在更改视图之前或之后无需通知AloeStackView
。自动布局将确保UI保持最新状态。
样式和分隔符控制
默认情况下,AloeStackView
会在行之间添加分隔符。
开启和关闭分隔符
你可以轻松隐藏添加到AloeStackView
中的任何行的分隔符。
stackView.hidesSeparatorsByDefault = true
属性hidesSeparatorsByDefault
仅适用于新添加的行。已存在于AloeStackView
中的行不会受到影响。
你可以使用方法hideSeparator(forRow:)
、hideSeparators(forRows:)
、showSeparator(forRow:)
和showSeparators(forRows:)
来隐藏或显示现有行的分隔符。
AloeStackView
还提供了一个方便的属性来自动隐藏最后一个分隔符。
stackView.automaticallyHidesLastSeparator = true
自定义分隔符
你可以更改分隔符左右两侧的间距。
stackView.separatorInset = .zero
在垂直方向上,只使用separatorInset
的左右属性。
在水平方向上,分隔符在行之间垂直显示。在这种情况下,只使用separatorInset
的上下属性,它们控制分隔符的上下间距。
与hidesSeparatorsByDefault
一样,属性separatorInset
仅适用于新添加的行。已存在于AloeStackView
中的行不会受到影响。
你可以使用方法setSeparatorInset(forRow:)
和setSeparatorInset(forRows:)
更改现有行的分隔符间距。
AloeStackView
还提供了自定义分隔符颜色和宽度(或厚度)的属性。
stackView.separatorColor = .blue
stackView.separatorWidth = 2
这些属性会影响所有AloeStackView
中的分隔符。
扩展AloeStackView
AloeStackView
是一个公开类,因此很容易通过子类化添加自定义功能而无需更改原始源代码。此外,AloeStackView
还提供两种方法,可用于扩展其功能。
configureCell(_:)
AloeStackView
中的每一行都包装在一个名为StackViewCell
的UIView
子类中。这个视图用于每行的簿记并管理UI,如分隔符和内边距。
每当向AloeStackView
添加或插入行时,就会调用configureCell(_:)
方法。这个方法传递给行的新的StackViewCell
。
您可以重写此方法以根据需要对单元格进行任何自定义,例如支持您添加到AloeStackView
的自定义功能或控制屏幕上行的外观。
此方法总是在设置单元格的任何默认值之后调用,因此在方法中进行的任何更改都不会被系统覆盖。
cellForRow(_:)
每当向AloeStackView
插入行时,都会调用cellForRow(_:)
方法来获取行的单元格。默认情况下,cellForRow(_:)
简单地返回一个包含传入行的新的StackViewCell
。
然而,StackViewCell
是一个公开类,可以子类化以根据需要添加自定义行为和功能。若要使AloeStackView
使用您的自定义单元格,请重写cellForRow(_:)
并返回您自定义子类的实例。
提供自定义的StackViewCell
子类可以更细致地控制行显示方式。它还可以与每行一起存储自定义数据,这在支持您添加到AloeStackView
的任何功能时可能很有用。
要记住的一件事是,AloeStackView
将在从cellForRow(_:)
返回单元格后为其应用默认值。因此,如果您需要进一步自定义单元格,请考虑在configureCell(_:)
中执行此操作。
当何时扩展AloeStackView
这些方法共同为扩展AloeStackView
以添加自定义行为和功能提供了相当大的灵活性。
例如,您可以向AloeStackView
添加新方法来控制行的方式,或者支持新的用户交互类型。您可以自定义StackViewCell
的属性来控制每一行的单独外观。您可以通过子类化StackViewCell
来存储每行的新数据和属性,以便支持您添加的自定义功能。子类化StackViewCell
还可以提供对如何显示行的更精细控制。
然而,这种灵活性不可避免地与复杂性和维护成本相交换。 AloeStackView
有一个全面且易于使用API,可以支持各种用例。因此,在尝试扩展类以添加新功能之前,最好先看看您需要的特性是否可以通过现有的API实现。这通常可以节省时间和精力,既包括开发自定义功能的开销,也包括持续维护的代价。
当何时使用AloeStackView
简短回答
AloeStackView
最适合较短屏幕,内容不超过一屏或两屏的情况。它特别适合接受用户输入、实现表单或由异质视图组成的屏幕。
但是,深入研究AloeStackView
的技术细节也很有用,因为这可以帮助更好地理解适当的使用场景。
更多详情
AloeStackView
是工具箱中的一个非常有用的工具。其简单灵活的API使您能够快速轻松地构建UI。
与UITableView
和UICollectionView
不同,您可以在AloeStackView
中保留视图的强引用,并在任何时间对其进行更改。由于自动布局,这将自动更新整个UI,无需通知AloeStackView
更改。
这使得AloeStackView
非常适合用于表单和需要用户输入屏幕的用例。在这种情况下,保留对用户正在编辑的字段的强引用,并直接用验证反馈更新UI通常很方便。
AloeStackView
没有reloadData
方法,也没有任何通知它关于视图更改的方式。这使得它比像UITableView
这样的类更不容易出错,更容易调试。例如,如果未通知AloeStackView
其所管理视图的基础数据发生更改,它不会崩溃。
由于AloeStackView
在内部使用UIStackView
,在滚动时不会回收视图。这消除了由不正确回收视图引起的一些常见错误。当用户与之交互时,您也不需要独立维护视图的状态,这使得实现某些类型的UI变得简单。
但是,AloeStackView
并不适合所有情况。AloeStackView
在屏幕加载时单次遍历整个UI布局。因此,较长的屏幕在第一次显示UI之前会出现明显的延迟。这对用户来说不是很好的体验,并且可以使应用程序响应导航操作的感觉迟钝。因此,AloeStackView
不适用于实现包含多于几屏幕内容的应用UI。
放弃视图回收也是一种权衡:虽然AloeStackView
编写UI更快,错误更少,但对于较长的屏幕,它的性能会比像UITableView
这样的类更差,占用更多内存。因此,AloeStackView
通常不适用于包含许多相同类型视图的屏幕,这些视图显示类似的数据。在那种情况下,类如UITableView
或UICollectionView
通常表现更好。
尽管AloeStackView
并不是我们在Airbnb构建iOS UI时使用的唯一基础设施组件,但它对我们很多情况下的工作都非常有价值。我们希望你也觉得它很有用!
安装
AloeStackView
可以使用Carthage进行安装。只需将 github "airbnb/AloeStackView"
添加到您的Cartfile。
AloeStackView
可以使用CocoaPods进行安装。只需在您的Podfile中添加 pod 'AloeStackView'
。
贡献
AloeStackView
已针对它最初设计的用例完成了所有功能。然而,iOS上的UI开发永远不会是一个已解决的问题,我们预计新的用例将出现,旧的错误将被发现。
因此我们完全欢迎贡献,包括新的功能、功能请求、错误报告和修复。如果您想做出贡献,只需推送一个带有您更改描述的PR。您还可以创建一个GitHub Issue以提交任何错误报告或功能请求。
如果您想联系项目负责人,请随时发送电子邮件。如果您或您的公司发现此库有用,我们非常乐意听听您的意见!
维护者
AloeStackView
由以下人员开发与维护:
Marli Oshlack ([email protected])
Fan Cox ([email protected])
Arthur Pang ([email protected])
贡献者
AloeStackView
还得到了许多 Airbnb 工程师的帮助。
他们包括:Daniel Crampton, Francisco Diaz, David He, Jeff Hodnett, Eric Horacek, Garrett Larson, Jasmine Lee, Isaac Lim, Jacky Lu, Noah Martin, Phil Nachum, Gonzalo Nuñez, Laura Skelton, Cal Stephens, 和 Ortal Yahdav
此外,如果没有 Jordan Harband, Tyler Hedrick, Michael Bachand, Laura Skelton, Dan Federman 和 John Pottebaum 的帮助和支持,开源此项目将无法实现。
许可证
AloeStackView
根据 Apache License 2.0 发布。有关详情,请参阅 LICENSE 文件。
为什么命名为 AloeStackView?
我们喜欢多肉植物,觉得这个名字很舒缓。