如何将AutoLayout和ScrollView很好地融合在一起

字数 695阅读 2778

前因

据Nic说现场是大便,陈坤是马桶,所以能很好地融合在一起。

然而在日常的开发工作中,我们遇到AutoLayout和ScrollView,未必就能将它们很好地融合在一起。

今天Q群里也有朋友在踩AutoLayout和ScrollView的坑,过程中两者融合得不是很好,于是便在群里声泪俱下。为了让这类惨剧少那么一点天空蓝那么一点。

Demo

I'm Demo Address

后果

为了让这类惨剧少那么一点天空蓝那么一点。Demo地址已经在上面了,我分别用StoryBoard和代码(我使用的是PureLayout)示范了一个例子。

完成后的界面是这样的:


图是HOCC拿新秀大赛冠军时候梅姐颁奖的现场
这首歌很好听,欢迎听XD

其中代码的实现也很简单,一并附上。示例中将写约束的方法写在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下。

谨以此文致敬尼古拉斯·谢

推荐阅读更多精彩内容