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. FlexGrid简单demo

    1.首先加入以下代码 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <hea ...

  2. .NET Core installation guide

      .NET Core installation guide 1.Download Visual Studio 2015 Make sure you have Visual Studio 2015 U ...

  3. 1.1.3.托管对象上下文(Core Data 应用程序实践指南)

    管理托管对象的生命周期(lifecycle).还有其它功能:faulting.变更追踪(change tracking).验证(validation)等. faulting:只把用到的那一部分数据从持 ...

  4. IndexAction.java (Java之负基础实战)

    生成Get and Set 方法: 例如:public String view; 右击view > Source > Generate Getters and Setters...

  5. --@angularJS--指令与控制器之间的交互demo

    1.index.html: <!DOCTYPE HTML><html ng-app="app"><head>    <title>c ...

  6. 专注手机端前端界面开发的ui组件和js组合

    frozenui一款腾讯开发的简化版Bootstrap,只用于手机端 http://frozenui.github.io/ https://github.com/frozenui/frozenui z ...

  7. 【续】强行在C# Winform中渲染Cocos2d-x 3.6

    [前言] 上一篇讲了怎么把Cocos2d-x 3.6渲染进MFC窗体,这里来讲一下怎么在C# Winform中做到同样的功能.如果你不熟悉MFC的使用但对C# Winform比较在行,请往下看. 这一 ...

  8. 通过RMAN克隆11g数据库(基于active database)

    11g的RMAN duplicate 可以通过Active databaseduplicate和Backup-based duplicate两种方法实现.这里的测试使用的是Active databas ...

  9. MySQL密码丢失,解决方法

    我的MySQ安装路径是:D:\Program Files\MySQL 1.所以先cmd下切入盘 输入-> D: 输入->cd "D:\Program Files\MySQL\My ...

  10. SoapUI:使用Excel进行参数化

    本章中学习如下内容: 1)         使用DataSource调用Excel中的数据给接口参数化: 2)         使用DataSource Loop使得测试用例根据Excel中的取值循环 ...