前因
据Nic说现场是大便,陈坤是马桶,所以能很好地融合在一起。
然而在日常的开发工作中,我们遇到AutoLayout和ScrollView,未必就能将它们很好地融合在一起。
今天Q群里也有朋友在踩AutoLayout和ScrollView的坑,过程中两者融合得不是很好,于是便在群里声泪俱下。为了让这类惨剧少那么一点天空蓝那么一点。
Demo
I'm Demo Address
后果
为了让这类惨剧少那么一点天空蓝那么一点。Demo地址已经在上面了,我分别用StoryBoard和代码(我使用的是PureLayout)示范了一个例子。
完成后的界面是这样的:
其中代码的实现也很简单,一并附上。示例中将写约束的方法写在Controller里并不是最佳实践,实际应用时请自行封装(逃(并非凑字数XD
import UIKit
class HOCCCodeController: UIViewController {
var didSetupConstraints = false
let scrollView = UIScrollView.newAutoLayoutView()
let container = UIView.newAutoLayoutView()
// MARK: Container Subviews
let imageView: UIImageView = {
let imageView = UIImageView.newAutoLayoutView()
imageView.contentMode = .ScaleAspectFit
imageView.image = UIImage(named: "hocc")
return imageView
}()
...
...
// MARK: LifeCircle
extension HOCCCodeController {
override func loadView() {
// Add Subviews
view = UIView()
view.backgroundColor = .whiteColor()
view.addSubview(scrollView)
scrollView.addSubview(container)
let subviews = [imageView, songNameLabel, singerLabel, contentView, lyricLabel]
for subview in subviews {
container.addSubview(subview)
}
contentView.addSubview(lyricistLabel)
contentView.addSubview(composerLabel)
// Trigger
view.setNeedsUpdateConstraints()
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Code"
}
}
// MARK: UpdateViewConstraints
extension HOCCCodeController {
override func updateViewConstraints() {
if !didSetupConstraints {
// 1. Setup ScrollView constraints
scrollView.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsZero)
// 2. Setup Container constraints
container.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsZero)
/* It is the Key */
container.autoMatchDimension(.Width, toDimension: .Width, ofView: scrollView)
imageView.autoAlignAxisToSuperviewAxis(.Vertical)
imageView.autoPinEdgeToSuperviewEdge(.Top, withInset: 30)
imageView.autoMatchDimension(.Width, toDimension: .Width, ofView: container, withMultiplier: 0.7)
if let image = imageView.image {
let ratio = image.size.height / image.size.width
imageView.autoMatchDimension(.Height, toDimension: .Width, ofView: imageView, withMultiplier: ratio)
}
let views = [songNameLabel, singerLabel, contentView, lyricLabel]
var previousView: UIView?
for view in views {
view.autoAlignAxisToSuperviewAxis(.Vertical)
if let previousView = previousView {
view.autoPinEdge(.Top, toEdge: .Bottom, ofView: previousView, withOffset: 15)
} else {
view.autoPinEdge(.Top, toEdge: .Bottom, ofView: imageView, withOffset: 30)
}
previousView = view
}
lyricLabel.autoSetDimension(.Width, toSize: 230)
lyricLabel.autoPinEdgeToSuperviewEdge(.Bottom, withInset: 80)
// 3. Setup ContentView constraints
lyricistLabel.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsZero, excludingEdge: .Right)
composerLabel.autoPinEdge(.Leading, toEdge: .Trailing, ofView: lyricistLabel, withOffset: 50)
composerLabel.autoAlignAxis(.Horizontal, toSameAxisOfView: lyricistLabel)
composerLabel.autoPinEdgeToSuperviewEdge(.Trailing)
didSetupConstraints = true
}
super.updateViewConstraints()
}
}
总结
如果有一天,你要实现类似的界面,你又刚好想用ScrollView和AutoLayout去实现。
直接上方法:
- 添加scrollView以及其子视图
- 父视图.addSubview(scrollView)
- scrollView.addSubview(container) -- container为一个占位的view
- container.addSubviews(真·想要展示的views)
- 拉(或撸)约束
- scrollView.四边 黏住 父视图.四边
- container.四边 黏住 scrollView.四边
- container.width = scrollView.width (如要实现横向滚动,则是container.height = scrollView.height)
- container.subviews(真·想要展示的views)尽情布局,只需记住一点,
拉(或撸)出来的约束要能确定出container的高度(横向滚动则为宽度)
。
最直白的布局则像我的Demo中一样:"V:|-[imageView]-[label]-[label]--[contentView]-[label]-|",从上往下一个黏住一个,这样就可以确保高度可以算出来。
补充
对于用代码创建view的朋友,需要记得设置这个东东:
(scrollView以及其子视图).translatesAutoresizingMaskIntoConstraints = false
再补充
平时本人如果要在ScrollView里使用AutoLayout,都会按照以上方法布局好scrollView与container,如果还有坑,那也基本只是普通的AutoLayout坑了(AutoLayout普通的坑?坑普通的AutoLayout?。。。)
至于这么做的原理是什么,该类文章网上已经有很多,譬如这篇:
这篇
这篇2:
还有我
再推荐一篇文章,里面提到如果scrollView计算出来的contentSize没有超出其本身的size,可在viewDidLayoutSubviews里将控件居中显示的方法:
我是文章
不知道以上内容有没有在你融合AutoLayout和ScrollView的过程中产生一点帮助呢:)有的话麻烦GitHub上star下。
谨以此文致敬尼古拉斯·谢