本系列文章将从一个慢慢摸索中的新手的角度介绍 Auto Layout,讲述我在这两个月的学习中对它一点一滴的感觉,最终目的是让你们在阅读完以后可以本身上手使用,并完成绝大多数简单的布局约束。html
此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方能够参考个人 Auto Layout 设置哦,下载到本地打开就能够了。ios
Auto Layout 是苹果在 Xcode 5 (iOS 6) 中新引入的布局方式,旨在解决 3.5 寸和 4 寸屏幕的适配问题。屏幕适配工做在 iPhone 6 及 plus 发布之后变得更加剧要,并且以往的“笨办法”的工做量大幅增长,因此不少人开始学习使用 Auto Layout 技术。c++
本系列文章的开发环境为:git
OS X 10.10.3
github
Xcode Version 6.3.1 (6D1002)编程
新建一个 Single View Application,命名为 AutoLayout,以下:swift
点击选中 Main.storyboard,右侧内容以下:app
一、2 两个按钮将会在将来的开发中产生巨大的做用,他们将拥有本系列文章的全局名称:按钮1,按钮2。请先记下他们的位置。ide
这也是我对学习新的软件编程技术的基本学习方法:有一个具体客观驱动的目标,例如作一个真正要给客户用的软件,而不是“为了学习新技术提升本身”这类伪目标。函数
让咱们直接上手:绘制一个距离左右边都有必定距离、固定高度、垂直居中的按钮,叫“Swift on iOS”。
1. 第一步,从右侧拖过来一个按钮,置于页面最中间。会有参考线出现,这一步很容易:
2. 选中这个 button,将按钮背景色和前景色进行以下设置:
3. 将按钮左侧边界往左拖动直到自动吸附,留下必定的距离。右侧进行一样操做:
4. 选中这个 button,修改文字为 Swift on iOS:
5. 选中这个 button,点击 按钮2 ,选择这一项:
这时候 button 周围会出现一些蓝色的线条,这些就是 Auto Layout 的约束项。
3.5:
4:
4.7:
5.5:
选中这个 button,在右侧查看自动生成的约束项:
只有三项,这三项的意思分别是:和父视图纵向居中对齐、右侧和父视图对齐、左侧和父视图对齐。
咱们很容易就能理解这样能够定位一个按钮,可是总感受少了点什么。实际上这三个自动生成的约束项并不能描述一个 button 的位置,由于少了一个关键的属性:button 的高度。之后咱们会详细地讨论。
Auto Layout 的本质是依靠 某几项约束条件 来达到对某一个元素的定位。咱们能够在某个地方只使用一个约束,以达到一个小目的,例如防止内容遮盖、防止边界溢出等。但个人最佳实践证实,若是把页面上每个元素的位置都用 Auto Layout 进行 “严格约束” 的话,那么 Auto Layout 能够帮咱们省去很是多的计算 frame 的代码。
简单来讲,严格约束就是对某一个元素的绝对定位,让它在任一屏幕尺寸下都有着惟一的位置。这里的绝对定位不是定死的位置,而是对一个元素 完善的约束条件。
让咱们看图说话:
咱们要在一个直角坐标系里描述一个矩形。
那么只须要指定这个矩形的位置和大小。
那么只要给出上图中的四个值便可:到左边界的距离,到上边界的距离,宽度,高度。
这四个约束是最简单的状况。在对一个元素进行严格约束时,请直接在脑中构建这个元素,而且加上几条约束条件,若是他没法缩放和动弹,那么严格约束就是成功的!
必须牢记,使用 Auto Layout 时最重要的是:对页面上每个元素都进行严格约束,不严格的约束是万恶之源。
此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方能够参考个人 Auto Layout 设置哦,下载到本地打开就能够了。
上一篇文章中,咱们共同进行了 Auto Layout 的初体验,在本篇咱们将一块儿尝试用 Auto Layout 实现三等分。
Auto Layout 的本质是用一些约束条件对元素进行约束,从而让他们显示在咱们想让他们显示的地方。
约束主要分为如下几种(欢迎补充):
相对于父 view 的约束。如:距离上边距 10,左边距 10。
相对于前一个元素的约束。如:距离上一个元素 20,距离左边的元素 5 等。
对齐类约束。如:跟父 view 左对齐,跟上一个元素居中对齐等。
相等约束。如:跟父 view 等宽。
许多人刚开始接触 Auto Layout,可能会觉得它只能实现上面的一、2功能,其实后面三、4两个功能才是强大、特别的地方。接下来咱们将尝试设计横向三等分:
第一个元素距离左边必定距离。
最后一个元素距离右边必定距离。
三者高度恒定,宽度相等。(此处咱们设置为高度恒定(height 属性),若是你须要的是固定长宽比,则须要设定 Aspect Ratio 属性)
1和二、2和3的横向间距固定。
4 寸:
4.7 寸:
此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方能够参考个人 Auto Layout 设置哦,下载到本地打开就能够了。
本篇中咱们将尝试自定义一个 UITableViewCell,并使用 Auto Layout 对其进行约束。
在前面的项目中,咱们采用 StoryBoard 来组织页面,StoryBoard 能够视为许多个 xib 的集合,因此咱们能够获得两个信息:
这个项目经过初始化主 StoryBoard 文件来展示 APP,而 UIViewController 类文件是经过 StoryBoard 文件的绑定来初始化并完成功能的。
咱们能够建立新的 StoryBoard 文件或者新的 xib 文件来构造 UI,而后动态地加载进页面。
咱们能够一次性建立 xib 文件和类的代码文件。
新建 Cocoa Touch Class:
设置和下图相同便可:
分别选中上图中的 一、2 两处,检查 3 处是否已经自动绑定为 firstTableViewCell,若是没有绑定,请先检查选中的元素确实是 2,而后手动绑定便可。
切换一页,以下图进行 Identifier 设置:
新建一个 Table View Controller 页面,并把咱们以前建立的 Swift on iOS 那个按钮的点击事件绑定过去,咱们获得:
而后建立一个名为 firstTableViewController 的 UITableViewController 类,建立流程跟前面基本一致。不要建立 xib。而后选中 StoryBoard 中的 Table View Controller(选中以后有蓝色边框包裹),在右侧对它和 firstTableViewController 类进行绑定:
修改 firstTableViewController 类中的有效代码以下:
import UIKit class firstTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() var nib = UINib(nibName: "firstTableViewCell", bundle: nil) self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell") } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: - Table view data source override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as firstTableViewCell cell.textLabel?.text = indexPath.row.description return cell } }
viewDidLoad() 中添加的两行代码是载入 xib 的操做。最下面的三个 func 分别是定义:
self.tableView 中有多少个 section
每一个 section 中分别有多少个条目
实例化每一个条目,提供内容
首先向 Images.xcassets 中随意加入一张图片。
而后在左侧文件树中选中 firstTableViewCell.xib,从右侧组件库中拖进去一个 Image View,而且在右侧将其尺寸设置以下图右侧:
给 ImageView 添加约束:
选中该 ImageView(左箭头所示),点击自动 Auto Layout(右箭头所示),便可。
给 ImageView 设置图片:
再从右侧组件库中拖入一个 UILabel,吸附到最右侧,垂直居中,为其添加自动约束,这一步再也不赘述。
选中 firstTableViewCell.xib,切换到双视图,直接进行拖动绑定:
绑定完成!
在 firstTableViewController 中添加如下方法:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 50 }
修改 firstTableViewController 中如下函数为:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as firstTableViewCell cell.firstLabel.text = indexPath.row.description return cell }
4.0 寸:
4.7 寸:
此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方能够参考个人 Auto Layout 设置哦,下载到本地打开就能够了。
本文中,咱们将一块儿使用 UIPanGestureRecognizer 和 Auto Layout,经过 22 行代码实现拖动回弹效果。
删除首页中间的按钮,添加一个 View ,设置一种背景色便于辨认,而后对其进行绝对约束:
拖动一个 UIPanGestureRecognizer 到该 View 上:
界面搭建完成。
切换到双向视图,分别右键拖动 UIPanGestureRecognizer 和该 View 的 Top Space 的 Auto Layout 属性到 ViewController 中绑定:
而后将 UIPanGestureRecognizer 右键拖动绑定:
class ViewController: UIViewController { var middleViewTopSpaceLayoutConstant: CGFloat! var middleViewOriginY: CGFloat! @IBOutlet weak var middleView: UIView! @IBOutlet weak var middleViewTopSpaceLayout: NSLayoutConstraint! @IBOutlet var panGesture: UIPanGestureRecognizer! override func viewDidLoad() { super.viewDidLoad() panGesture.addTarget(self, action: Selector("pan")) middleViewTopSpaceLayoutConstant = middleViewTopSpaceLayout.constant middleViewOriginY = middleView.frame.origin.y } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func pan() { if panGesture.state == UIGestureRecognizerState.Ended { UIView.animateWithDuration(0.4, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in self.middleView.frame.origin.y = self.middleViewOriginY }, completion: { (success) -> Void in if success { self.middleViewTopSpaceLayout.constant = self.middleViewTopSpaceLayoutConstant } }) return } let y = panGesture.translationInView(self.view).y middleViewTopSpaceLayout.constant = middleViewTopSpaceLayoutConstant + y } }
此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方能够参考个人 Auto Layout 设置哦,下载到本地打开就能够了。
本文中,咱们将一块儿使用 Auto Layout 技术,让 UITableViewCell 的高度随其内部的 UILabel 和 UIImageView 的内容自动变化。
放置一个按钮,恢复到 firstTableViewController 的链接:
别忘了添加约束让他居中哦。
将 firstTableViewCell 的尺寸设置为 600 * 81,将 logo 的尺寸设置为 80 * 80。将 logo 的约束修改成以下图所示:
修改 label 的尺寸和位置,添加约束以下图:
为了便于返回。操做以下图:
选中 label,设置 lines 行数为 0,表示不限长度自动折行:
修改 label 的文字内容让其超出一行:
import UIKit class firstTableViewController: UITableViewController { var labelArray = Array<String>() // 用于存储 label 文字内容 override func viewDidLoad() { super.viewDidLoad() var nib = UINib(nibName: "firstTableViewCell", bundle: nil) self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell") // 循环生成 label 文字内容 for i in 1...10 { var text = "" for j in 1...i { text += "Auto Layout" } labelArray.append(text) } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: - Table view data source override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 50 } override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return labelArray.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as! firstTableViewCell cell.firstLabel.text = labelArray[indexPath.row] return cell } }
estimatedHeightForRowAtIndexPath 是 iOS 7 推出的新 API。若是列表行数有一万行,那么 heightForRowAtIndexPath 就会在列表显示以前计算一万次,而 estimatedHeightForRowAtIndexPath 只会计算当前屏幕中显示着的几行,会大大提升数据量很大时候的性能。
class firstTableViewController: UITableViewController { var labelArray = Array<String>() // 用于存储 label 文字内容 var prototypeCell: firstTableViewCell! override func viewDidLoad() { super.viewDidLoad() var nib = UINib(nibName: "firstTableViewCell", bundle: nil) self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell") // 初始化 prototypeCell 以便复用 prototypeCell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell") as! firstTableViewCell ......
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let cell = prototypeCell cell.firstLabel.text = labelArray[indexPath.row] return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1 }
上面让 firstTableViewCell 根据 label 自动计算高度的过程当中,有一个超级大坑:若是给左侧 UIImageView 赋的图片较大(大于 80px),将看到以下奇怪的结果:
首先,把图片的渲染模式改为 Aspect Fit:
给 Images.xcassets 增长三张图片,命名为 0、一、2,尺寸从小到大:
给 cellForRowAtIndexPath 增长代码:
if indexPath.row < 3 { cell.logoImageView.image = UIImage(named: indexPath.row.description) }
查看效果:
新建一个 Group(虚拟文件夹),叫 Extensions,并在其内部新建 UIImage.swift 文件,内容以下:
import UIKit extension UIImage { func resizeToSize(size: CGSize) -> UIImage { UIGraphicsBeginImageContext(size) self.drawInRect(CGRectMake(0, 0, size.width, size.height)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } }
给 UIImage 类扩展了一个名为 resizeToSize 的方法,返回一个按照要求的大小重绘过的 UIImage 对象。修改 cellForRowAtIndexPath 的代码为:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as! firstTableViewCell cell.firstLabel.text = labelArray[indexPath.row] var image = UIImage(named: (indexPath.row % 3).description)! if image.size.width > 80 { image = image.resizeToSize(CGSizeMake(80, image.size.height * (80 / image.size.width))) } cell.logoImageView.image = image return cell }
搞定!
从上图能够看出,cell 已经能够根据图片和文字中比较高的一个彻底自适应。
感谢 《动态计算UITableViewCell高度详解》,给我提供了许多基础知识和灵感。
此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方能够参考个人 Auto Layout 设置哦,下载到本地打开就能够了。
本文中,咱们将一块儿使用 Auto Layout 技术制造一个炫酷的下拉刷新动画。Auto Layout 除了在布局的时候比较繁琐之外,还有一个常常被人吐槽的点:让许多 UIView.animateWithDuration 失效,甚至在界面上出现 “反方向动画” 的视觉效果。本文中咱们将主要讲述制造下拉刷新动画的过程,关于 Auto Layout 与动画的详细配合咱们之后再一块儿仔细探究。
使用一个跟窗体同样很大的 mainView 把目前首页的五个元素包括,并补全 Auto Layout 布局。层次结构改变会让除尺寸约束以外的全部约束消失。
UI 和代码的绑定修改为以下:
从新给 mainView 绑定上 panGesture,从 mainView 向 panGesture 拖动:
修改存储值的变量名称和初始化代码:
... var mainViewTopSpaceLayoutConstraintValue: CGFloat! var mainViewOriginY: CGFloat! ... mainViewTopSpaceLayoutConstraintValue = topLayoutConstraintOfMainView.constant mainViewOriginY = mainView.frame.origin.y ...
修改手势触发的目标函数(增长冒号):
panGesture.addTarget(self, action: Selector("pan:"))
改写动画目标函数:
func pan(recongnizer: UIPanGestureRecognizer) { if panGesture.state == UIGestureRecognizerState.Ended { UIView.animateWithDuration(0.4, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in recongnizer.view?.frame.origin.y = self.mainViewOriginY }, completion: { (success) -> Void in if success { self.topLayoutConstraintOfMainView.constant = self.mainViewTopSpaceLayoutConstraintValue } }) return } let y = panGesture.translationInView(self.view).y topLayoutConstraintOfMainView.constant = mainViewTopSpaceLayoutConstraintValue + y }
拖动一个 View 到 mainView 上面,目的是置于 mainView 图层之下。在 Xcode 里,最上面的元素位于 UI 的最底层,这个顺序跟 PS 正好相反。修改其 frame,添加 Auto Layout 约束,以下图:
将 UI 和代码绑定:
动画的时候咱们须要该主视图进行必定程度的下移,故将其 “到父视图顶部的距离” 的约束也进行绑定。(此图为后期补充,请自动忽略其子元素)
将动画主视图更名为 HiddenTopView。为了方便的填充动画元素,须要先将 HiddenTopView 移动到 mainView 之下,以便将图层显示在 mainView 之上,不被其遮挡,没法编辑。我填充了三个图片资源,所有加上约束。具体约束你们能够自由发挥。效果以下:
因为我使用了白云元素,故将最大的 view 的背景色填充为灰色,将 HiddenTopView 背景色设置为透明。
咱们计划整个下拉刷新动画分为四个部分。下拉时:
整个 HiddenTopView 下移
小云彩从屏幕右侧飞入
大云彩持续作往复运动
下拉距离超过必定值时,中间子元素进行必定程度的放大
“动画规划”中,1 上面已经准备过,二、3 须要将小云、大云的横向定位参数向代码绑定,4 只需绑定中间元素便可。
因为实现动画效果的代码描述起来过于麻烦,请直接看代码:https://github.com/johnlui/AutoLayout/blob/1420fddee57d22ebd443656fb3158c7dede84b56/AutoLayout/ViewController.swift