此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方可以参考我的 Auto Layout 设置哦,下载到本地打开就可以了。

简介

Auto Layout 是苹果在 Xcode 5 (iOS 6) 中新引入的布局方式,旨在解决 3.5 寸和 4 寸屏幕的适配问题。屏幕适配工作在 iPhone 6 及 plus 发布以后变得更加重要,而且以往的“笨办法”的工作量大幅增加,所以很多人开始学习使用 Auto Layout 技术。

初体验

0. 开发环境

本系列文章的开发环境为:

  • OS X 10.10.2

  • Xcode Version 6.2 (6C131e)

1. 新建应用

新建一个 Single View Application,命名为 AutoLayout,如下:

点击选中 Main.storyboard,右侧内容如下:

1、2 两个按钮将会在未来的开发中产生巨大的作用,他们将拥有本系列文章的全局名称:按钮1,按钮2。请先记下他们的位置。

2. 直接上手,开始使用

这也是我对学习新的软件编程技术的基本学习方法:有一个具体客观驱动的目标,例如做一个真正要给客户用的软件,而不是“为了学习新技术提高自己”这类伪目标。

让我们直接上手:绘制一个距离左右边都有一定距离、固定高度、垂直居中的按钮,叫“Swift on iOS”。

1. 第一步,从右侧拖过来一个按钮,置于页面最中间。会有参考线出现,这一步很容易:

2. 选中这个 button,将按钮背景色和前景色进行如下设置:

3. 将按钮左侧边界往左拖动直到自动吸附,留下一定的距离。右侧进行同样操作:

4. 选中这个 button,修改文字为 Swift on iOS:

5. 选中这个 button,点击 按钮2 ,选择这一项:

这时候 button 周围会出现一些蓝色的线条,这些就是 Auto Layout 的约束项。

3. 大功告成,查看效果

3.5:

4:

4.7:

5.5:

4. 分析

选中这个 button,在右侧查看自动生成的约束项:

只有三项,这三项的意思分别是:和父视图纵向居中对齐、右侧和父视图对齐、左侧和父视图对齐。

我们很容易就能理解这样可以定位一个按钮,但是总感觉少了点什么。实际上这三个自动生成的约束项并不能描述一个 button 的位置,因为少了一个关键的属性:button 的高度。以后我们会详细地讨论。

5. 核心思想

本质分析

Auto Layout 的本质是依靠 某几项约束条件 来达到对某一个元素的定位。我们可以在某个地方只使用一个约束,以达到一个小目的,例如防止内容遮盖、防止边界溢出等。但我的最佳实践证明,如果把页面上每一个元素的位置都用 Auto Layout 进行 “严格约束” 的话,那么 Auto Layout 可以帮我们省去非常多的计算 frame 的代码。

“严格约束” 是什么?

简单来说,严格约束就是对某一个元素的绝对定位,让它在任一屏幕尺寸下都有着唯一的位置。这里的绝对定位不是定死的位置,而是对一个元素 完善的约束条件。

让我们看图说话:

  1. 我们要在一个直角坐标系里描述一个矩形。

  2. 那么只需要指定这个矩形的位置和大小。

  3. 那么只要给出上图中的四个值即可:到左边界的距离,到上边界的距离,宽度,高度。

  4. 这四个约束是最简单的情况。在对一个元素进行严格约束时,请直接在脑中构建这个元素,并且加上几条约束条件,如果他无法缩放和动弹,那么严格约束就是成功的!

  5. 必须牢记,使用 Auto Layout 时最重要的是:对页面上每一个元素都进行严格约束,不严格的约束是万恶之源。

Auto Layout 使用心得(二)--实现三等分

此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方可以参考我的 Auto Layout 设置哦,下载到本地打开就可以了。
准备

上一篇文章中,我们共同进行了 Auto Layout 的初体验,在本篇我们将一起尝试用 Auto Layout 实现三等分。

Auto Layout 的本质原理

Auto Layout 的本质是用一些约束条件对元素进行约束,从而让他们显示在我们想让他们显示的地方。

约束主要分为以下几种(欢迎补充):

  1. 相对于父 view 的约束。如:距离上边距 10,左边距 10。

  2. 相对于前一个元素的约束。如:距离上一个元素 20,距离左边的元素 5 等。

  3. 对齐类约束。如:跟父 view 左对齐,跟上一个元素居中对齐等。

  4. 相等约束。如:跟父 view 等宽。

三等分设计思路

许多人刚开始接触 Auto Layout,可能会以为它只能实现上面的1、2功能,其实后面3、4两个功能才是强大、特别的地方。接下来我们将尝试设计横向三等分:

  1. 第一个元素距离左边一定距离。

  2. 最后一个元素距离右边一定距离。

  3. 三者高度恒定,宽度相等。

  4. 1和2、2和3的横向间距固定。

干货,实现过程的动图:

运行结果

4 寸:

4.7 寸:

纵向三等分实现方式类似,大家可以自己尝试一下哦~

Auto Layout 使用心得(三)—— 自定义 cell 并使用 Auto Layout

此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方可以参考我的 Auto Layout 设置哦,下载到本地打开就可以了。
简介

本篇中我们将尝试自定义一个 UITableViewCell,并使用 Auto Layout 对其进行约束。

自定义 cell 基础

在前面的项目中,我们采用 StoryBoard 来组织页面,StoryBoard 可以视为许多个 xib 的集合,所以我们可以得到两个信息:

  1. 这个项目通过初始化主 StoryBoard 文件来展现 APP,而 UIViewController 类文件是通过 StoryBoard 文件的绑定来初始化并完成功能的。

  2. 我们可以创建新的 StoryBoard 文件或者新的 xib 文件来构造 UI,然后动态地加载进页面。

创建文件

我们可以一次性创建 xib 文件和类的代码文件。

新建 Cocoa Touch Class:

设置和下图相同即可:

检查成果

分别选中上图中的 1、2 两处,检查 3 处是否已经自动绑定为 firstTableViewCell,如果没有绑定,请先检查选中的元素确实是 2,然后手动绑定即可。

完成绑定工作

切换一页,如下图进行 Identifier 设置:

新建 Table View Controller 页面

新建一个 Table View Controller 页面,并把我们之前创建的 Swift on iOS 那个按钮的点击事件绑定过去,我们得到:

然后创建一个名为 firstTableViewController 的 UITableViewController 类,创建流程跟前面基本一致。不要创建 xib。然后选中 StoryBoard 中的 Table View Controller(选中之后有蓝色边框包裹),在右侧对它和 firstTableViewController 类进行绑定:

调用自定义 cell

修改 firstTableViewController 类中的有效代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 分别是定义:

  1. self.tableView 中有多少个 section

  2. 每个 section 中分别有多少个条目

  3. 实例化每个条目,提供内容

如果你得到以下页面,说明你调用自定义 cell 成功了!

给自定义 cell 添加元素并使用 Auto Layout 约束

首先向 Images.xcassets 中随意加入一张图片。

然后在左侧文件树中选中 firstTableViewCell.xib,从右侧组件库中拖进去一个 Image View,并且在右侧将其尺寸设置如下图右侧:

给 ImageView 添加约束:

选中该 ImageView(左箭头所示),点击自动 Auto Layout(右箭头所示),即可。

给 ImageView 设置图片:

再从右侧组件库中拖入一个 UILabel,吸附到最右侧,垂直居中,为其添加自动约束,这一步不再赘述。

在 firstTableViewCell 类中绑定 xib 中拖进去的元素

选中 firstTableViewCell.xib,切换到双视图,直接进行拖动绑定:

绑定完成!

约束 cell 的高度

在 firstTableViewController 中添加以下方法:

1
2
3
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 50
}

给自定义的 UILabel 添加内容

修改 firstTableViewController 中以下函数为:

1
2
3
4
5
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 寸:

如果你得到以上结果,那么恭喜你自定义 cell 并使用 Auto Layout 成功!

Auto Layout 使用心得(四)—— 22 行代码实现拖动回弹

此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方可以参考我的 Auto Layout 设置哦,下载到本地打开就可以了。
简介

本文中,我们将一起使用 UIPanGestureRecognizer 和 Auto Layout,通过 22 行代码实现拖动回弹效果。

搭建界面

删除首页中间的按钮,添加一个 View ,设置一种背景色便于辨认,然后对其进行绝对约束:

拖动一个 UIPanGestureRecognizer 到该 View 上:

界面搭建完成。

属性绑定

切换到双向视图,分别右键拖动 UIPanGestureRecognizer 和该 View 的 Top Space 的 Auto Layout 属性到 ViewController 中绑定:

然后将 UIPanGestureRecognizer 右键拖动绑定:

编写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
    }
}

查看效果

22 行代码,拖动回弹效果完成!

Auto Layout 使用心得(五)--根据文字、图片自动计算 UITableViewCell 高度

此系列文章代码仓库在 https://github.com/johnlui/AutoLayout ,有不明白的地方可以参考我的 Auto Layout 设置哦,下载到本地打开就可以了。
简介

本文中,我们将一起使用 Auto Layout 技术,让 UITableViewCell 的高度随其内部的 UILabel 和 UIImageView 的内容自动变化。

搭建界面

恢复之前删除的按钮

放置一个按钮,恢复到 firstTableViewController 的连接:

别忘了添加约束让他居中哦。

修改 firstTableViewCell

将 firstTableViewCell 的尺寸设置为 600 * 81,将 logo 的尺寸设置为 80 * 80。将 logo 的约束修改为如下图所示:

修改 label 的尺寸和位置,添加约束如下图:

给 ViewController 增加 UINavigationController 嵌套

为了便于返回。操作如下图:

查看结果

根据 label 自动计算 firstTableViewCell 高度

选中 label,设置 lines 行数为 0,表示不限长度自动折行:

修改 label 的文字内容让其超出一行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import UIKit
class firstTableViewController: UITableViewController {
     
    var labelArray = Array() // 用于存储 label 文字内容
    override func viewDidLoad() {
        super.viewDidLoad()
        var nib = UINib(nibName: "firstTableViewCell", bundle: nil)
        self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell")
         
        // 循环生成 label 文字内容
        for in 1...10 {
            var text = ""
            for 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
    }
}

现在到了最关键的时刻,驱动 UITableViewCell 适应 Label 内容:

1. 使用 estimatedHeightForRowAtIndexPath 替代 heightForRowAtIndexPath

estimatedHeightForRowAtIndexPath 是 iOS 7 推出的新 API。如果列表行数有一万行,那么 heightForRowAtIndexPath 就会在列表显示之前计算一万次,而 estimatedHeightForRowAtIndexPath 只会计算当前屏幕中显示着的几行,会大大提高数据量很大时候的性能。

2. 新建一个 prototypeCell 成员变量以复用,并在 viewDidLoad 中初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class firstTableViewController: UITableViewController {
     
    var labelArray = Array() // 用于存储 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
         
......

3. 计算出高度

1
2
3
4
5
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
}

4. 查看效果

超级大坑

上面让 firstTableViewCell 根据 label 自动计算高度的过程中,有一个超级大坑:如果给左侧 UIImageView 赋的图片较大(大于 80px),将看到如下奇怪的结果:

这只是因为图片把 UITableViewCell 撑大了,并不是我们的计算没有效果。

解决大坑:进攻是最好的防守!根据图片自动计算 firstTableViewCell 高度

首先,把图片的渲染模式改成 Aspect Fit:

给 Images.xcassets 增加三张图片,命名为 0、1、2,尺寸从小到大:

给 cellForRowAtIndexPath 增加代码:

1
2
3
if indexPath.row < 3 {
    cell.logoImageView.image = UIImage(named: indexPath.row.description)
}

查看效果:

前两个 cell 看起来比较正常,第三个为什么多出了那么多空白?这就是使用 Auto Layout 限制图片宽度为 80px 的原生问题:宽度虽然限制了,高度却依然是原图的高度。解决办法也很简单:如果图片宽度大于 80px,就重绘一张 80px 宽度的图片填充进去。

新建一个 Group(虚拟文件夹),叫 Extensions,并在其内部新建 UIImage.swift 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
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 的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as! firstTableViewCell
    cell.firstLabel.text = labelArray[indexPath.row]
     
    if indexPath.row < 3 {
        var image = UIImage(named: indexPath.row.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 已经可以根据图片和文字中比较高的一个完全自适应。

Auto Layout 使用心得的更多相关文章

  1. 【转】使用 Auto Layout 的典型痛点和技巧

    layoutIfNeeded()强制立刻更新布局 原文网址:http://www.jianshu.com/p/0f031606e5f2 官方文档:Auto Layout Guide 加上去年WWDC上 ...

  2. 【Auto Layout】Xcode6及以上版本,创建Auto Layout 约束时产生的一些变化【iOS开发教程】

    [#Auto Layout#]Xcode6创建Auto Layout 约束时产生的一些变化     通过两个小Demo来展示下变化: Demo1需求: 为控制器的根视图(图中的“控制器View”)的子 ...

  3. iOS 8 Auto Layout界面自动布局系列2-使用Xcode的Interface Builder添加布局约束

    http://blog.csdn.net/pucker/article/details/41843511 上一篇文章<iOS 8界面自动布局系列-1>简要介绍了iOS界面布局方式的前世今生 ...

  4. 手写代码自动实现自动布局,即Auto Layout的使用

    手写代码自动实现自动布局,即Auto Layout的使用,有需要的朋友可以参考下. 这里要注意几点: 对子视图的约束,若是基于父视图,要通过父视图去添加约束. 对子视图进行自动布局调整,首先对UIVi ...

  5. Auto Layout

    Auto Layout XCode5+ Auto Layout Concepts 核心的概念是约束. Constraint Basics Constant value Relation Priorit ...

  6. 使用Auto Layout中的VFL(Visual format language)--代码实现自动布局【转】

    本文将通过简单的UI来说明如何用VFL来实现自动布局.在自动布局的时候避免不了使用代码来加以优化以及根据内容来实现不同的UI. 一:API介绍 NSLayoutConstraint API 1 2 3 ...

  7. 转载自@机智的新手:使用Auto Layout中的VFL(Visual format language)--代码实现自动布局

    本文将通过简单的UI来说明如何用VFL来实现自动布局.在自动布局的时候避免不了使用代码来加以优化以及根据内容来实现不同的UI. 一:API介绍 NSLayoutConstraint API 1 2 3 ...

  8. ios auto layout demystified (二)

    Constraints Constraint Types Layout constraints (NSLayoutConstraint class, public)—这些规则指定了view的几何学.他 ...

  9. ios auto layout demystified (一)

    Ambiguous Layout 在开发过程中,你可以通过调用hasAmbiguousLayout 来测试你的view约束是否足够的.这个会返回boolean值.如果有一个不同的frame就会返回ye ...

随机推荐

  1. FocusWriter

    2. FocusWriter 如果你正在从事某种写作——小说.博客.文档等——你绝对希望认识一下FocusWriter.它已经有近十年的发布历史了,但是一直是我们最喜欢的无分心写作应用之一.如果你希望 ...

  2. 219. Contains Duplicate II

    题目: Given an array of integers and an integer k, find out whether there are two distinct indices i a ...

  3. 97. Interleaving String

    题目: Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. For example,Given: ...

  4. MapReduce编程系列 — 3:数据去重

    1.项目名称: 2.程序代码: package com.dedup; import java.io.IOException; import org.apache.hadoop.conf.Configu ...

  5. WCF 下的windows服务的安装卸载

    安装:启动vs2010(如果是win2008要以管理员来启动)命令:installutil demo.exe 卸载:先在服务里停止这个服务,然后启动vs2010(如果是win2008要以管理员来启动) ...

  6. init进程解析rc文件的相关函数分析

    init进程的源码文件位于system/core/init,其中解析rc文件语法的代码放在五个函数中, init_parse_config_file (init_parser.c), read_fil ...

  7. 大四实习准备6_android服务

    2015-5-9 1.服务是什么 android四大组件之一,有一些特点: 1)服务的运行不依赖于用户界面,即使程序被切换到后台.或者用户打开了另外一个应用程序,服务仍然能够保持正常运行.(当对应的程 ...

  8. bzoj3774

    这算是最小割中比较难的吧 看到选取显然最小割 看到上下左右四个点我感觉肯定和染色相关 注意每个点的收益获得条件是[或],因此我们考虑拆点i', i,分别表示通过四周控制和控制本身的代价 连边s--&g ...

  9. bzoj2668

    对于这种题很容易看出是费用流吧…… 但这道题不容易建模: 首先是怎么表示目标状态和其实状态,看起来有黑有白很复杂 但实际上,不难发现,白色格子没什么用,起决定作用的是黑格子 也就是我们可以把问题简化: ...

  10. 使用Unity3D自带动画系统制作下雨效果

    之前看了以前版本的unity3d demo AngryBots ,觉得里面的下雨效果不错,刚好前段时间学习了,写出来跟大家分享下,直接开始. 使用自带动画系统制作下雨效果. 先制作下雨的雨滴涟漪 步骤 ...