利用Auto Layout打造弹性布局——iOS项目推荐
看到上面的动态效果了吗?这是一款简单而有趣的iOS应用效果。利用Auto Layout,实现起来非常容易。今天我将向你介绍如何构建这个效果,即使你是Auto Layout的新手,也能快速上手。
如果你希望跟随教程实践,可以克隆项目到初始状态,然后随着阅读逐步实现效果:
git clone https://github.com/TwoLivesLeft/StretchyLayout.git
cd StretchyLayout
git checkout Step-1
以下是我们的实现步骤:
- 创建基础非弹性应用
- 修改视图层次以添加必要的约束,使它变得有弹性
- 对应用进行优化,提升用户体验
非弹性版应用
这是基础版本的应用视图结构。主要包含三个部分:头部UIImageView
,文本容器和长UILabel
。红色线条代表了Auto Layout的约束,还有一个UIScrollView
以及视图控制器的根视图。
我们将使用名为SnapKit的框架来构建这个例子。SnapKit是iOS上的一个简单框架,它使得Apple的Auto Layout API更加易用。使用它,编程体验将更加愉快。
大部分代码位于StretchyViewController
类的viewDidLoad
中。以下展示了如何设置初始约束:
首先声明私有成员变量:
private let scrollView = UIScrollView()
private let infoText = UILabel()
private let imageView = UIImageView()
然后在视图控制器的视图中添加了一个滚动视图,接着是图像和文本视图(还有个背景视图,提供红色背景)。
// 将滚动视图的边缘与视图控制器的视图对齐
scrollView.snp.makeConstraints { make in
make.edges.equalTo(view)
}
// 图像视图的顶部与滚动视图对齐,左侧和右侧与视图控制器的视图对齐
// 使用宽高比约束,高度为其宽度的70%
imageView.snp.makeConstraints { make in
make.top.equalTo(scrollView)
make.left.right.equalTo(view)
make.height.equalTo(imageView.snp.width).multipliedBy(0.7)
}
// 文本容器的顶部与图像视图底部对齐,左侧和右侧与视图控制器的视图对齐,底部与滚动视图底部对齐
textContainer.snp.makeConstraints { make in
make.top.equalTo(imageView.snp.bottom)
make.left.right.equalTo(view)
make.bottom.equalTo(scrollView)
}
// 文本的边缘与文本容器对齐,留出14点的内边距
infoText.snp.makeConstraints { make in
make.edges.equalTo(textContainer).inset(14)
}
注:想获取此阶段的代码,请执行git checkout Step-1
。
简短的插曲:关于SnapKit
注意到我们在代码中使用了SnapKit。这是一个很棒的工具,下面是一个简单的介绍:
只需访问任何UIView
的snp
成员对象,即可调用makeConstraints
方法,传递一个闭包作为参数,闭包接收一个ConstraintMaker
对象(这里称为make
)。
然后你可以通过make
对象将一个视图的边缘或锚点绑定到其他视图、布局指南或常量。
myView.snp.makeConstraints {
make in
make.edges.equalTo(view)
}
这会将myView
的边缘与view
的边缘对齐。
简洁且可读。强烈建议你使用SnapKit代替原生的Auto Layout API。
塑造弹性效果
我们要如何从普通视图转为这样的效果呢?
关键在于,无论视图是否是兄弟关系,只要它们有一个共同祖先,Auto Layout都能解决约束。而且,滚动视图内的视图可以与滚动视图外的视图建立约束。这就是我们实现弹性效果的方式。
在上述图中,亮红色线表示约束。请注意,现在图像视图的顶部被固定到了最外层视图的顶部,但其底部仍与图片容器视图的底部对齐。这意味着当滚动视图向下滚动时,图像视图会拉伸以满足其约束。
第一步,我们将UIImageView
替换为空的容器视图。
let imageContainer = UIView()
imageContainer.backgroundColor = .darkGray
scrollView.addSubview(imageContainer)
imageContainer.snp.makeConstraints { make in
make.top.equalTo(scrollView)
make.left.right.equalTo(view)
make.height.equalTo(imageContainer.snp.width).multipliedBy(0.7)
}
然后,我们在滚动视图内添加图像视图,并将其顶部与视图控制器的视图对齐,而不是滚动视图的顶部(刚刚添加的容器视图是与滚动视图的顶部对齐的)。
scrollView.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.left.right.equalTo(imageContainer)
// 这是关键行!
make.top.equalTo(view)
make.bottom.equalTo(imageContainer.snp.bottom)
}
如上所示,这两行代码实现了所需效果。图像容器视图的滚动行为与非弹性应用相同。但我们将实际的图像视图放置在其上方,将其底部与容器视图的底部对齐,顶部则固定在视图控制器视图的顶部。
由于设置了imageView.contentMode = .scaleAspectFill
,所以在滚动视图下拉时,图像内容会在图像视图内部等比例放大。
注:要获得此阶段的代码,请执行git checkout Step-2
。
但是有个小问题
如果你运行这段代码,用手指滑动屏幕会产生预期的效果:图像会随手势缩放并反弹。但如果你尝试向上滚动以阅读文本,你会发现无法滚动。
为什么呢?
当我们向上滚动时,我们实际上是在压缩UIImageView
,使其高度变为零。它的顶部必须等于视图的顶部,底部必须等于文本容器的顶部。所以,尽管滚动视图仍在“滚动”,但由于文本容器紧贴着图像视图,图像视图拒绝超出视图的顶部,导致滚动无效。
Auto Layout从技术角度解决了我们的约束,但这不是我们想要的结果。
解决问题
我们需要更改图像视图的约束方式。修改后的代码如下:
imageView.snp.makeConstraints { make in
make.left.right.equalTo(imageContainer)
// 注意优先级
make.top.equalTo(view).priority(.high)
// 我们还增加了一个高度约束
make.height.greaterThanOrEqualTo(imageContainer.snp.height).priority(.required)
// 保持底部约束
make.bottom.equalTo(imageContainer.snp.bottom)
}
现在我们有了顶部约束、底部约束和高度约束。这是Auto Layout的一个强大之处:我们可以有冲突的约束,系统会按优先级顺序打破约束。这就是我们期望的效果。
首先,我们保留原始的约束:图像视图的顶部固定在视图的顶部,赋予其.high
优先级。
然后,我们添加额外的约束:图像的高度必须大于或等于图像容器的高度(记住,图像容器有一个宽高比约束)。这个约束的优先级是.required
。
因此,当我们向上滚动时会发生什么呢?
由于图像的高度约束优先级更高,Auto Layout会打破优先级最低的约束以解决问题。这样,滚动行为就会恢复正常,让我们能够向上滚动并阅读文本。
(另外一种做法是移除高度约束,只将顶部约束优先级设为.high
。这会导致iOS在压缩图像视图至零高度时依然能滚动。结合.scaleAspectFill
的内容模式,可以创建类似偏移效果。试试看,你可能会更喜欢这种感觉。)
注:要获得此阶段的代码,请执行git checkout Step-3
。
细节优化
最后,我们还需要处理三个问题,让应用看起来更专业。
- 防止文本过度滚动
- 尊重安全区域
- 滚动视图自适应iPhone X底部指示器
对于第一点,你可以在滚动超出了视图底部时,使文本背后的背景视图扩展出来。详情请参考代码,实现原理与图像视图相同。
注:要获得此阶段的代码,请执行git checkout Step-4
。
对于第二点,我们希望在iPhone X上文本不覆盖home指示器。为了处理底部的安全区域,我们可以利用iOS 11新增的safeAreaInsets
属性。同时,我们也需要手动调整滚动视图的滚动范围。
项目的特点总结如下:
- 简单易懂:通过清晰的步骤和SnapKit的易用性,开发者可以轻松理解并实现弹性布局。
- 充分利用Auto Layout:通过动态调整约束优先级,实现了滚动时图像和文本的弹性效果。
- 兼容性好:考虑了iPhone X的安全区域,确保良好的用户体验。
- 实战性强:不仅提供了代码示例,还允许用户在每个步骤中自己动手实践,加深理解和记忆。
立即尝试并加入这个项目,一起探索Auto Layout的无限可能吧!