UIScrollViewj尽管继承于UIView,但它是一个相对比较特殊的视图,特别是当它遇到了AutoLayout之后。在UIScrollView中使用AutoLayout的目的除了使用相对约束确定子控件的位置和大小外,更重要的是如何自动计算出UIScrollView的contentSize(关于使用UIScrollView并且最终手动指定contentSize的AutoLayout用法不再今天讨论之列,严格意义上来说这也不是一种真正的UIScrollView的AutoLayout应用)。

UIScrollView的特殊之处

所谓UIScrollView的特殊之处就在于当它遇到了AutoLayout之后其contentSize的计算规则有些特殊。首先contentSize是根据子视图的leading/trailing/top/bottom进行确定的,而子视图的位置约束又必须依赖于UIScrollView来确定。这就有点类似于前面UICollectionView自适应高度文章中提到的:UICollectionViewCell的大小计算就是计算contentView的大小,而contentView的大小计算依赖于子视图的leading/trailing/top/bottom,子视图的位置约束又依赖于contentView,此时只要子视图存在固有尺寸(intrinsicContentSize)或者指定了尺寸又设置了leading/trailing/top/bottom,AutoLayout布局引擎即可计算出contentView的大小。

再回到AutoLayout,其实它的contentSize计算原理和UICollectionViewCell自适应很是类似,只是UIScrollView内部并没有一个contentView的东西(但是可以想象其存在,方便后面的理解,不过要清楚UIScrollView滚动的本质并非包含一个contentView而是通过bounds和frame坐标体系转换来实现的),只要设置子视图的leading/trailing/top/bottom(通常是通过edges=0让子视图上下左右间距都为0保证整个视图都在UIScrollView可视范围之内),然后通过设置size(width/height)约束确定子视图大小进而由AutoLayout反向计算出UIScrollView的contentSize。

假设A是UIScrollView(蓝色)、B是子视图1(绿色)、C是子视图2(绿色)、D是contentSize的计算区域(灰色,事实上它不存在),要想让cotentSize可以自动计算只需要确定B、C上下左右布局间距,然后再指定B、C间距和尺寸之后AutLayout既可以自动推断出contentSize的大小,原理如下图(下图布局类似于下面Demo3):

demo

demo1 单个子视图布局

对于单个子视图布局比较简单,只要设置leading/trailing/top/bottom,再设置子视图的size(width/height)即可,当然如果子视图存在固有尺寸并且想要使用固有尺寸的话,则这一步也可以省略。例如下面demo中演示了一个UIScrollView包含一个UIImageView子视图的图片查看界面。在下面的布局中仅仅设置了UIImageView上下左右边距,而UIImageView存在固有尺寸,因此整个布局就相当简单了(AutoLayout布局使用SnapKit库)。

	class ImageViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad() self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.imageView)
self.scrollView.snp.makeConstraints { (make) in
make.edges.equalTo(0.0)
} self.imageView.snp.makeConstraints { (make) in
// 下面的约束用于确定contentSize的边距约束(leading/trailing/top/bottom)
// 而由于UIImageView和UILabel、UIButton一样存在固有尺寸(intrinsicContentSize),因此不需要其他size约束就可以计算出contentSize大小
make.edges.equalTo(0.0)
} } // MARK: - 私有属性
private lazy var scrollView:UIScrollView = {
let temp = UIScrollView()
return temp
}() private lazy var imageView:UIImageView = {
let image = UIImage(named: "img")
let temp = UIImageView(image:image)
return temp
}() }

demo2 多个子视图布局使用containerView

很多UIScrollView的AutoLayout的布局文章中都会提到使用一个容器视图包含多个子视图,然后分别完成子视图布局和容器视图在UIScrollView中的布局,以此来简化布局过程。下面的Demo中演示了一个图片分页查看的布局情况,containerView作为容器布局时设置上下左右间距,然后设置其高度等于UIScrollView高度(因为要实现左右滚动),而此时并不需要设置宽度,因为宽度的计算依赖于子视图。在containerView的子视图中只要设置子视图与containerView的边距及各自间距和宽度,之后AutoLayout就可以计算出containerView的宽度。如此一来containerView已经设置完了四周间距和尺寸就可以计算出contentSize。

	class SlideViewController: UIViewController {

	    override func viewDidLoad() {
super.viewDidLoad() self.automaticallyAdjustsScrollViewInsets = false self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.containerView)
self.containerView.addSubview(self.firstImageView)
self.containerView.addSubview(self.secondImageView)
self.containerView.addSubview(self.thirthImageView) self.scrollView.snp.makeConstraints { (make) in
make.top.equalTo(self.topLayoutGuide.snp.bottom)
make.left.bottom.right.equalTo(0.0)
} // 下面的约束确定了containerView的高度,相当于contentSize.height已经确定,width通过cotnentView的子视图确定即可
self.containerView.snp.makeConstraints { (make) in
make.edges.equalTo(0.0)
make.height.equalTo(self.scrollView.snp.height)
} self.firstImageView.snp.makeConstraints { (make) in
make.top.left.bottom.equalTo(0.0)
make.width.equalTo(self.scrollView.snp.width)
} self.secondImageView.snp.makeConstraints { (make) in
make.top.bottom.equalTo(0.0)
make.left.equalTo(self.firstImageView.snp.right)
make.width.equalTo(self.scrollView.snp.width)
} self.thirthImageView.snp.makeConstraints { (make) in
make.top.bottom.equalTo(0.0)
make.left.equalTo(self.secondImageView.snp.right)
make.width.equalTo(self.scrollView.snp.width)
make.right.equalTo(0.0) // 确定右边距
} } // MARK: - 私有属性
private lazy var scrollView:UIScrollView = {
let temp = UIScrollView()
temp.isPagingEnabled = true
return temp
}() private lazy var containerView:UIView = {
let temp = UIView()
return temp
}() private lazy var firstImageView:UIImageView = {
let image = UIImage(named: "1")
let temp = UIImageView(image:image)
temp.contentMode = .scaleAspectFill
temp.clipsToBounds = true
return temp
}() private lazy var secondImageView:UIImageView = {
let image = UIImage(named: "2")
let temp = UIImageView(image:image)
temp.contentMode = .scaleAspectFill
temp.clipsToBounds = true
return temp
}() private lazy var thirthImageView:UIImageView = {
let image = UIImage(named: "3")
let temp = UIImageView(image:image)
temp.contentMode = .scaleAspectFill
temp.clipsToBounds = true
return temp
}() }

demo3 多个子视图不使用containerView布局

demo2的containerView包含多个子视图的布局方式相对来说好像使用要多一些,但是其实布局原理并没有任何变化,如果熟悉了UIScrollView的AutoLayout布局原理,用不用containerView大家可以根据情况自行决定,如果仅仅是简单的几个子视图布局没有特殊的需求那么直接布局可能会更简单,但是如果子视图相对较多并且可能所有子视图有公共的操作需求(例如所有子视图在键盘弹出后需要改变其位置)则更适合使用containerView布局。下面代码中去掉containerView完成demo2的需求,原理相同,代码也不难理解。

	class SlideViewController2: UIViewController {

	    override func viewDidLoad() {
super.viewDidLoad() self.automaticallyAdjustsScrollViewInsets = false self.view.addSubview(self.scrollView)
self.scrollView.addSubview(self.firstImageView)
self.scrollView.addSubview(self.secondImageView)
self.scrollView.addSubview(self.thirthImageView) self.scrollView.snp.makeConstraints { (make) in
make.top.equalTo(self.topLayoutGuide.snp.bottom)
make.left.bottom.right.equalTo(0.0)
} self.firstImageView.snp.makeConstraints { (make) in
make.top.left.bottom.equalTo(0.0)
make.size.equalTo(self.scrollView.snp.size)
} self.secondImageView.snp.makeConstraints { (make) in
make.top.bottom.equalTo(0.0)
make.left.equalTo(self.firstImageView.snp.right)
make.size.equalTo(self.scrollView.snp.size)
} self.thirthImageView.snp.makeConstraints { (make) in
make.top.bottom.equalTo(0.0)
make.left.equalTo(self.secondImageView.snp.right)
make.size.equalTo(self.scrollView.snp.size)
make.right.equalTo(0.0) // 确定右边距
} } // MARK: - 私有属性
private lazy var scrollView:UIScrollView = {
let temp = UIScrollView()
temp.isPagingEnabled = true
return temp
}() private lazy var firstImageView:UIImageView = {
let image = UIImage(named: "1")
let temp = UIImageView(image:image)
temp.contentMode = .scaleAspectFill
temp.clipsToBounds = true
return temp
}() private lazy var secondImageView:UIImageView = {
let image = UIImage(named: "2")
let temp = UIImageView(image:image)
temp.contentMode = .scaleAspectFill
temp.clipsToBounds = true
return temp
}() private lazy var thirthImageView:UIImageView = {
let image = UIImage(named: "3")
let temp = UIImageView(image:image)
temp.contentMode = .scaleAspectFill
temp.clipsToBounds = true
return temp
}() }

最终效果

总结

其实概括起来UIScrollView的布局最主要的问题就是解决contentSize的计算问题。而根据UIScrollView的特点contentSize的计算最终就是根据上下左右边距和子控件自身尺寸来反向推导出来的。在遇到多个子视图的情况下具体用不用容器视图根据情况而定,容器视图仅仅起到辅助作用,整个布局原理是完全相同的。使用UIScrollView的AutoLayout布局优点自不必多说,除了从frame计算中摆脱出来之外(绝对布局和相对布局的区别),天生支持屏幕旋转(屏幕的旋转适配只需要在布局时稍加注意即可),例如上面三个demo均支持竖屏和横屏查看,相对于frame布局代码简化了很多。

iOS开发tips-UIScrollView的Autlayout布局的更多相关文章

  1. iOS开发tips总结

    tip 1 :  给UIImage添加毛玻璃效果 func blurImage(value:NSNumber) -> UIImage { let context = CIContext(opti ...

  2. iOS开发基础-UIScrollView实现图片缩放

    当用户在 UIScrollView 上使用捏合手势时, UIScrollView 会给 UIScrollViewDelegate 协议发送一条消息,并调用代理的 viewForZoomingInScr ...

  3. iOS开发基础-UIScrollView基础

     普通的 UIView 不具备滚动功能,不能显示过多的内容.UIScrollView 是一个能够滚动的视图控件,可用来展示大量的内容.  UIScrollView 的简单使用: 1)将需要展示的内容添 ...

  4. iOS开发:解决UIScrollView不滚动的问题

    照着书上的Demo(iOS 5.0的教程),在- (void)viewDidLoad里设置scrollView的contentsize,让它大于屏幕的高度,却发现在模拟器中没用,还是不能滚.经过 一翻 ...

  5. iOS 开发 Tips

    1.MVVM 的优点 MVVM 兼容 MVC,可以先创建一个简单的 View Model,再慢慢迁移. MVVM 使得 app 更容易测试,因为 View Model 部分不涉及 UI. MVVM 最 ...

  6. iOS开发使用UIScrollView随笔

    1.scrollview滚动到固定偏移量contenOffset - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)anim ...

  7. iOS 开发 ZFUI framework控件,使布局更简单

    来自:http://www.jianshu.com/p/bcf86b170d9c 前言 为什么会写这个?因为在iOS开发中,界面的布局一直没有Android布局有那么多的方法和优势,我个人开发都是纯代 ...

  8. IOS开发-提升app性能的25条建议和技巧

    前言 这篇文章介绍了作者开发工作中总结的25个iOS开发tips, 多年之前读过这篇文章.收益良多,基本每一个tips在我的应用开发过程中都使用过.今天把这篇文章又一次整理转发下,与大家一起学习,不论 ...

  9. iOS开发——UI篇&ScrollView详解

    创建方式 1:StoryBoard/Xib 这里StoarBoard就不多说,直接拖就可以,说太多没意思,如果连这个都不会我只能先给你跪了! 2:代码: CGRect bounds = [ [ UIS ...

随机推荐

  1. ASP.NET异步处理

    前一篇:详解 .NET 异步 在前文中,介绍了.NET下的多种异步的形式,在WEB程序中,天生就是多线程的,因此使用异步应该更为谨慎.本文将着重展开ASP.NET中的异步. [注意]本文中提到的异步指 ...

  2. linux在线预览pdf文件开发思路

    准备:swftools,flexpaper 基本思路: 1,将pdf文件转化成swf文件 2,使用flexpaper预览swf文件 主要代码: 1,在linux中安装swftools.官网下载swft ...

  3. setTimeout,setInterval 最短触发时间

    <!DOCTYPE html> <html> <head> <title></title> </head> <body&g ...

  4. Jenkins SSH timeout

    问题如下: Started by user carzone Building -test SSH: Connecting from host [jenkins232] SSH: Connecting ...

  5. Mybatis学习(8)逆向工程

    什么是逆向工程: mybaits需要程序员自己编写sql语句,mybatis官方提供逆向工程 可以针对单表自动生成mybatis执行所需要的代码(mapper.java,mapper.xml.po.. ...

  6. webx--petstore

    配置对应环境,运行petstore 通过官网给的命令行方法,来运行petstore petstore是java ee的经典学习案例,下载链接 如何运行呢? 参见官网给的指导:webx官网 git cl ...

  7. Delphi 中的常用事件

    OnActive 焦点称到窗体或控件时发生 OnClick 鼠标单击事件 OnDbClick 鼠标双击事件 OnClose和OnCloseQuery 当关闭一个窗体时就会响应OnClose和OnClo ...

  8. Target runtime Apache Tomcat v8.0 is not defined.

    Target runtime Apache Tomcat v8.0 is not defined. Window-Preference-MyEclipse-Targeted Runtimes,选择存在 ...

  9. Word常用实用知识1

    Word常用实用知识1 纯手打,可能有错别字,使用的版本是office Word 2013 转载请注明出处,谢谢. 快速输入日期(含格式) [插入]--[日期]   快速输入日期和时间(快捷键) 快速 ...

  10. Android系列一、创建项目

    本文是在MAC下的Android Studio操作的. 一.Android入门 1.打开Android Studio,界面如下: 几个选项的意思: 创建一个新的项目 打开一个已经存在的项目 从版本管理 ...