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. AutoMapper使用说明

    1.引用命名空间 using AutoMapper;using AutoMapper.Mappers; 2.实体类和dto public class Order { public int orderi ...

  2. js jqery判断checkbox是否选中,全选,取消全选,反选,选择奇数偶数项

    // 一,判断选中 // js var ischecked2 = function(){ // this.checked == true $(document.getElementsByTagName ...

  3. java数据结构整理(二)

    一.List接口,有序的Collection接口,能够精确地控制每个元素插入的位置,允许有相同的元素 1.链表,LinkedList实现了List接口,允许null元素,提供了get().remove ...

  4. 阿里云安装wordpress遇到的问题

    在阿里云服务器上安装Nginx,php5.3.3环境,使用阿里云的RDS数据库 1,安装wordpress,提示您的PHP似乎没有安装运行WordPress所必需的MySQL扩展 解决方案:移除已经安 ...

  5. IOS Cell 重影

    效果:重影 原因: 多次创建控件元素 解决:在initWithStyle中进行初始化元素

  6. gec210 NAND BOOT与SD BOOT启动原理

    CPU上电后,此时SP指针指向0x0000_0000,从这个地址取第一条指令.但此时:PLL没有启动,CPU工作频率为外部输入晶振频率,非常低(S5PV210中晶振在CPU旁边,两颗24MHz,一颗2 ...

  7. [ Android 五种数据存储方式之二 ] —— 文件存储数据

    关于文件存储,Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的实现过程与在J2SE环境中保存数据到文件中是一样的. 文件可用来存放大量数据,如文本.图片.音 ...

  8. swift版 关于微信支付的那点事

    今天心情那真是想要强奸吉娃娃的冲动  说白了就是不想做和工作沾边的任何事  但是也不能闲着啊  时间那么贵 之前就想把微信支付做一下  主要就是怕自己忘记了 今天难得有时间  就来简单的记录一下  旨 ...

  9. php判断IE浏览器

    <?php/** * 检测用户当前浏览器 * @return boolean 是否ie浏览器 */ function chk_ie_browser() { $userbrowser = $_SE ...

  10. 界面编程与视图(View)组件

    一.视图组件与容器组件 Android应用的绝大部分UI组件都放在android.widget包及其子包.android.view包及其子包中,Android应用的所有UI组件都继承了View类. V ...