如今,在开发 iOS 应用的过程中,我们经常会采用 AutoLayout 来配置页面布局。然而,UIScrollView 这个家伙好像不好驾驭。别担心,Ficow 来陪你一起驯服这只倔驴子!
只要吃透本文中的几个重要概念,我相信您以后可以游刃有余地使用 AutoLayout 来配置 UIScrollView 的布局。
如果您未曾听说或者使用过 UILayoutGuide
以及 UIScrollView 的 frameLayoutGuide
和 contentLayoutGuide
属性,理解并掌握这些内容对您也会大有裨益。
首先,请看最终效果(请注意右图中红色区域最右边的滚动条):
未达到最大高度,不可滑动 | 达到最大高度,可滑动 |
---|---|
布局的需求:
未达到
最大高度限制时,UIScrollView 和子视图的高度一起增大,UIScrollView 的内容不可以滑动;达到
最大高度限制时,UIScrollView 的高度为最大高度且不再增大,子视图高度继续增大,UIScrollView 的内容可以滑动;这是可滑动时的效果动图:
在继续阅读后文之前,请您根据自己的经验思考一下,您会怎么实现这个需求呢?
如果想到了,欢迎您留言和 Ficow 交流,谢谢~
如果想不到,那也没关系。希望 Ficow 的实战总结可以对您有所帮助。如果错误,也欢迎您指正,谢谢~
如果您已经掌握了 UILayoutGuide,您可以跳过这个小节。
首先,看官方文档的描述:
一个可以和 Auto Layout 交互的矩形框。
UILayoutGuide 可以解决用空白View进行布局时造成的问题:
// 创建 UILayoutGuide
let layoutGuide = UILayoutGuide()
// 添加到 view 里
view.addLayoutGuide(layoutGuide)
// 让 UILayoutGuide 参与到自动布局的工作中
let top = layoutGuide.topAnchor.constraint(equalTo: labelOne.topAnchor)
let bottom = layoutGuide.bottomAnchor.constraint(equalTo: labelTwo.bottomAnchor)
NSLayoutConstraint.activate([top, bottom])
以下为示例代码,复制到一个 ViewController
里并执行即可看到效果。
另外,您可以通过调节 textCount
的值来查看不同的效果。
func testScrollView() {
let container = UIView()
container.backgroundColor = .lightGray
view.addSubview(container)
let containerHeight = container.heightAnchor.constraint(equalToConstant: 500)
containerHeight.priority = .defaultHigh // 与 view 的约束冲突,需要降低优先级
let containerWidth = container.widthAnchor.constraint(equalTo: view.widthAnchor)
containerWidth.priority = .defaultHigh // 与 view 的约束冲突,需要降低优先级
NSLayoutConstraint.activate([
container.leadingAnchor.constraint(equalTo: view.leadingAnchor),
container.topAnchor.constraint(lessThanOrEqualTo: view.topAnchor, constant: 100),
containerWidth, containerHeight
])
let scrollView = UIScrollView()
scrollView.backgroundColor = .darkGray
container.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: container.topAnchor),
scrollView.heightAnchor.constraint(lessThanOrEqualToConstant: 300) // 最大高度约束,优先级为 .required
])
let textCount = Int(1e2) // 短文本
// let textCount = Int(1e3) //长文本
let labelOne = UILabel()
labelOne.textColor = .black
labelOne.numberOfLines = 0
labelOne.backgroundColor = .red
// 防止label的内容被挤压
labelOne.setContentCompressionResistancePriority(.required, for: .vertical)
labelOne.text = String(repeating: 1.description, count: textCount)
let labelTwo = UILabel()
labelTwo.textColor = .black
labelTwo.numberOfLines = 0
labelTwo.backgroundColor = .green
// 防止label的内容被挤压
labelTwo.setContentCompressionResistancePriority(.required, for: .vertical)
labelTwo.text = String(repeating: 2.description, count: textCount)
[labelOne, labelTwo].forEach(scrollView.addSubview(_:))
NSLayoutConstraint.activate([
labelOne.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
labelOne.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
labelOne.topAnchor.constraint(equalTo: scrollView.topAnchor),
labelOne.widthAnchor.constraint(equalTo: view.widthAnchor)
])
NSLayoutConstraint.activate([
labelTwo.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
labelTwo.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
labelTwo.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor),
labelTwo.widthAnchor.constraint(equalTo: labelOne.widthAnchor),
])
let layoutGuide = UILayoutGuide()
scrollView.addLayoutGuide(layoutGuide)
// layoutGuide的顶部与labelOne的顶部要一致
let top = layoutGuide.topAnchor.constraint(equalTo: labelOne.topAnchor)
// layoutGuide的底部与labelTwo的底部要一致
let bottom = layoutGuide.bottomAnchor.constraint(equalTo: labelTwo.bottomAnchor)
NSLayoutConstraint.activate([top, bottom])
// layoutGuide的高度要与scrollView的高度相等
let height = scrollView.heightAnchor.constraint(equalTo: layoutGuide.heightAnchor)
height.priority = .defaultHigh // scrollView 先满足最大高度约束,再满足与layoutGuide等高的约束
NSLayoutConstraint.activate([height])
[container, scrollView, labelOne, labelTwo].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
}
优先级的处理方式
scrollView 最大高度为300的约束的优先级为 .required
,而scrollView与layoutGuide等高约束的优先级为 .high
。如此,就可以达到这样的目的:scrollView 先满足最大高度约束,再满足与layoutGuide等高的约束。
防止label的内容被挤压
label的内容有时候会因为约束被挤压,调用 setContentCompressionResistancePriority
方法配置label竖直方向上的抗挤压优先级。
最终,scrollView 就会根据内部子视图的高度自动调节自身的高度并同时满足最大高度的约束。
其实,我们不需要为 UIScrollView 再添加 UILayoutGuide。UIScrollView 本身就有 frameLayoutGuide
和 contentLayoutGuide
属性。
这是官方文档对这两个属性的描述:
请注意 untransformed
这个形容词,如果 UIScrollView 的 transform 已被更改,就会导致最终结果与您的预期不符。
另外,添加约束到 UIScrollView 本身
和 添加约束到 UIScrollView 的 frameLayoutGuide
也是不同的,如果您遭遇了bug,可以尝试将二者互换。
现在,让我们用 frameLayoutGuide
和 contentLayoutGuide
来改写之前的代码:
func testScrollView2() {
let container = UIView()
container.backgroundColor = .lightGray
view.addSubview(container)
let containerHeight = container.heightAnchor.constraint(equalToConstant: 500)
containerHeight.priority = .defaultHigh // 与 view 的约束冲突,需要降低优先级
let containerWidth = container.widthAnchor.constraint(equalTo: view.widthAnchor)
containerWidth.priority = .defaultHigh // 与 view 的约束冲突,需要降低优先级
NSLayoutConstraint.activate([
container.leadingAnchor.constraint(equalTo: view.leadingAnchor),
container.topAnchor.constraint(lessThanOrEqualTo: view.topAnchor, constant: 100),
containerWidth, containerHeight
])
let scrollView = UIScrollView()
scrollView.backgroundColor = .darkGray
container.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: container.topAnchor),
scrollView.heightAnchor.constraint(lessThanOrEqualToConstant: 300) // 最大高度约束,优先级为 .required
])
let textCount = Int(1e3)
let labelOne = UILabel()
labelOne.textColor = .black
labelOne.numberOfLines = 0
labelOne.backgroundColor = .red
// 防止label的内容被挤压
labelOne.setContentCompressionResistancePriority(.required, for: .vertical)
labelOne.text = String(repeating: 1.description, count: textCount)
let labelTwo = UILabel()
labelTwo.textColor = .black
labelTwo.numberOfLines = 0
labelTwo.backgroundColor = .green
// 防止label的内容被挤压
labelTwo.setContentCompressionResistancePriority(.required, for: .vertical)
labelTwo.text = String(repeating: 2.description, count: textCount)
[labelOne, labelTwo].forEach(scrollView.addSubview(_:))
NSLayoutConstraint.activate([
labelOne.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
labelOne.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
labelOne.topAnchor.constraint(equalTo: scrollView.topAnchor),
labelOne.widthAnchor.constraint(equalTo: view.widthAnchor)
])
NSLayoutConstraint.activate([
labelTwo.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
labelTwo.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
labelTwo.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
labelTwo.topAnchor.constraint(equalTo: labelOne.bottomAnchor),
labelTwo.widthAnchor.constraint(equalTo: labelOne.widthAnchor),
])
// 改写部分
let height = scrollView.contentLayoutGuide.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor)
height.priority = .defaultHigh
height.isActive = true
[container, scrollView, labelOne, labelTwo].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
}
改写之后,UILayoutGuide 相关的代码被进一步简化。
简而言之,就两个要点:
参考内容:
UILayoutGuide
frameLayoutGuide
contentLayoutGuide
觉得不错?点个赞呗~
本文链接:UIScrollView 如何自适应子视图的高度?
转载声明:本站文章如无特别说明,皆为原创。转载请注明:Ficow Shen's Blog