iOS开发tips-UIScrollView的Autlayout布局
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布局的更多相关文章
- iOS开发tips总结
tip 1 : 给UIImage添加毛玻璃效果 func blurImage(value:NSNumber) -> UIImage { let context = CIContext(opti ...
- iOS开发基础-UIScrollView实现图片缩放
当用户在 UIScrollView 上使用捏合手势时, UIScrollView 会给 UIScrollViewDelegate 协议发送一条消息,并调用代理的 viewForZoomingInScr ...
- iOS开发基础-UIScrollView基础
普通的 UIView 不具备滚动功能,不能显示过多的内容.UIScrollView 是一个能够滚动的视图控件,可用来展示大量的内容. UIScrollView 的简单使用: 1)将需要展示的内容添 ...
- iOS开发:解决UIScrollView不滚动的问题
照着书上的Demo(iOS 5.0的教程),在- (void)viewDidLoad里设置scrollView的contentsize,让它大于屏幕的高度,却发现在模拟器中没用,还是不能滚.经过 一翻 ...
- iOS 开发 Tips
1.MVVM 的优点 MVVM 兼容 MVC,可以先创建一个简单的 View Model,再慢慢迁移. MVVM 使得 app 更容易测试,因为 View Model 部分不涉及 UI. MVVM 最 ...
- iOS开发使用UIScrollView随笔
1.scrollview滚动到固定偏移量contenOffset - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)anim ...
- iOS 开发 ZFUI framework控件,使布局更简单
来自:http://www.jianshu.com/p/bcf86b170d9c 前言 为什么会写这个?因为在iOS开发中,界面的布局一直没有Android布局有那么多的方法和优势,我个人开发都是纯代 ...
- IOS开发-提升app性能的25条建议和技巧
前言 这篇文章介绍了作者开发工作中总结的25个iOS开发tips, 多年之前读过这篇文章.收益良多,基本每一个tips在我的应用开发过程中都使用过.今天把这篇文章又一次整理转发下,与大家一起学习,不论 ...
- iOS开发——UI篇&ScrollView详解
创建方式 1:StoryBoard/Xib 这里StoarBoard就不多说,直接拖就可以,说太多没意思,如果连这个都不会我只能先给你跪了! 2:代码: CGRect bounds = [ [ UIS ...
随机推荐
- bzoj3932
3932: [CQOI2015]任务查询系统 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1326 Solved: 480[Submit][Sta ...
- Python3基础 使用 in notin 查询一个字符是否指定字典的键或者值
镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...
- UIStackView属性解释
Distribution 分布: Fill:填充,会根据优先级来压缩或伸长元素 Fill Equal:全都相等,并且都填充满 Fill Proportionally:按比例填充,根据元素的内容多少的比 ...
- 在asp.net中使用ajax记录
一.问题描述 ajax在mvc中使用频繁,比如cms中的评论功能,但由于涉及到前后端开发,日久容易忘,在此做下记录. 二.内容 控制器中代码示例: /// <summary> /// 在文 ...
- TCMalloc
一. 原理 tcmalloc就是一个内存分配器,管理堆内存,主要影响malloc和free,用于降低频繁分配.释放内存造成的性能损耗,并且有效地控制内存碎片.glibc中的内存分配器是ptmalloc ...
- 如何测试LBS功能
在LBS功能的开发中,为了保证通用性,服务器存在的坐标是基于wgs84的,这个通常由GPS设备传过来,对于PC来说,如何获得这个值呢?可以利用Google Earth来获得,并修改显示的坐标系统,“工 ...
- 游戏开发之在UE4中编写C++代码控制角色
当你运行我们上次做完的项目,你可能会意识到我们移动的摄像机还是默认的那个摄像机,这个默认的摄像机可以自由飞翔.这一节,我们要使得开始的角色是我们的一个Avatar类的实例对象,并且使用键盘控制我们的角 ...
- paramiko库安装
python的paramiko库用于执行ssh2连接(client和server).安装方式如下: 硬件环境:Raspberry 2B,arm,1GB RAM,16GB TF卡; 系统环境:Linux ...
- ubuntu服务器远程连接xshell,putty,xftp的简单使用教程
当你自己千辛万苦终于搞到一个服务器(ubuntu(linux)系统的)之后,却不知道怎么进行时,xshell,putty,xftp是个很不错的选择 xshell和xftp是win下访问ubuntu(l ...
- 游戏音频技术备忘 (五)Wwise Unreal Engine 集成代码浅析 二
AkAmbientSound类的实现 Unreal Engine提供了一个基本对象的构造器ObjectInitializer,一般来说用户创建的类总是拥有很多变量,因此 AkAmbientSound ...