iOS进阶指南试读之UI篇
UI篇
UI是一个iOS开发工程师的基本功。
怎么说?
UI本质上就是你调用苹果提供给你的API来完成设计师的设计。
所以,想提升UI的功力也很简单,没事就看看UIKit里的各个类的头文件。如果能做到烂熟于胸,相信会有很大的提升。
Autolayout
顾名思义,Autolayout = 自动+布局,也就是当你设置好一定的约束之后,系统会帮你处理布局的细节。
那么,在不那么自动的年代,我们用的是什么?
我们用的是Frame布局。
那么,先来讨论一下Frame布局有哪些问题?
举个简单的例子好了。
如图。

代码如下。
- (void)viewDidLoad {
[super viewDidLoad];
redView = [UIView new];
redView.frame = CGRectMake(, , , );
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
yellowView = [UIView new];
yellowView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
yellowView.frame = CGRectInset(redView.bounds, , );
yellowView.backgroundColor = [UIColor yellowColor];
[redView addSubview:yellowView];
// Do any additional setup after loading the view, typically from a nib.
}
图中黄色的View是红色View的子View,那么,如果我期望无论红色View变大还是变小,黄色View距离红色View的边距总是不变的,该怎么做呢?
一般来说有两种做法。
- 设置黄色View的
autoresizingMask属性,设置为UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight,这样设置的结果是黄色View的宽高会随着父View宽高的改变而改变,但是不改变间距。 - 继承一个UIView,在UIView里的
layoutSubviews里设置子View的宽高。如下。
- (void)layoutSubviews
{
yellowView.frame = CGRectInset(self.bounds, 20, 20);
[super layoutSubviews];
}
实际上这两种做法都是很麻烦而且不灵活的。例如autoresizingMask对应的UIViewAutoresizing模式只有6种,我们常常用到的居中对齐等完全没有。二是如果在layoutSubviews里设置布局,又会造成如果父View有动画,那么会出现奇怪的动画效果。
所以,为了解除这些痛点,苹果公司为我们带来了Autolayout。
Autolayout的用法
在这里,我会假设看本书的都是会使用Autolayout基本功能的朋友。所以,我会直接讲解一些较为深入和偏实战的东西。
1. Intrinsic Content Size
思考一下,当你拉约束的时候,为什么UILabel和UIButton只需要拉能够确定坐标的约束即可,而不需要确定宽高的约束?比如说。

这个UILabel就是只确定了左边距和上边距,就可以了。但是如果我们放了一个UIView,只确定坐标而不指定大小,就会出错。例如。

Xcode会提示,这个View还需要指定宽和高。
造成这种情况的原因就是,有些View可以通过自己的内容计算宽高,而有些View不可以。这也是我们要讲的内容,也即我们的标题,Intrinsic Content Size,翻译过来就是,固有内容大小。
对应的系统方法就是- (CGSize)intrinsicContentSize
要知道,Autolayout的本质无非就是系统通过你设置的约束来帮你计算一个控件的位置和大小。
所以,一个UILabel肯定具备的四个条件是,内容、字体、行数、换行模式。也就是说,只要我们赋予了Label内容,那么它的大小也就确定了。所以,我们不需要特意指定一个UIlabel的宽高,除非你有什么特殊要求。
那么UIView为什么不可以自己计算大小?
答案其实也可以猜到,因为他没有内容。
来看一个例子。
首先,我们在storyboard上放置一个UILabel,然后设置Leading和Trailing。如图。

运行模拟器,看一下效果。

我们会发现,这样设置的UILabel,它的大小总是会和内容大小刚好一致,但是如果我们期望UILabel的大小总是比内容宽高都大一些,也就是所谓的留白。比如这样。

那么,我们应该怎么做呢?
首先,我们创建一个继承于UILabel的自定义试图,然后重写
- (CGSize)intrinsicContentSize,这个方法。代码如下。 - (CGSize)intrinsicContentSize
{
CGSize originalSize = [super intrinsicContentSize]; CGSize size = CGSizeMake(originalSize.width+, originalSize.height+); return size;
}
上述代码的意思就是,我们先获取系统通过Label的内容计算出来的宽和高,再分别给他增大再返回新的Size就可以了。再运行一下,你就会发现,Label的大小就会比内容大了。(别忘了,把对齐方式设置为居中)
再回到之前的那个问题,UIView如果只设置坐标,不设置大小会报错的问题。
如果是用代码写约束,如果你只想设置坐标不想设置大小,那么你需要像上面的代码一样,在- (CGSize)intrinsicContentSize为你的UIView指定一个默认大小。
如果是在XIB里,那么你需要在下图这个Instrinsic Size的属性里设置为Placeholder。这样,Xcode就不会报错了。

例子
其实看完上面的叙述,你会思考,到底什么情况下,一个UIView需要只设置坐标不设置大小呢?
其实这种场景相当普遍。比如,我们常常会碰到,一个View中有两个Label,两个Label的高度均和内容有关,这时候,你的View的高度就必须由两个Label的高度有关,而不能一开始就定死。例如。
一个已知宽度的UIView中,有两个UILabel,我希望这个UIView的高度由两个UIlabel的高度来确定。效果如下图。

也就是说,图中红色view的高度是和两个UIlabel相关联的。我们尝试来实现它。
首先,在storyboard上拉取一个UIView。设置背景色为红色。如图。

我们为这个红色View设置了3个约束,分别是。
- Leading space to SuperView:8
- Trailing space to SuperView:8
- Top Space to SuperView:8
也就是分别设置了View的左边距,右边距和上边距,熟悉约束的人应该知道,这时候View的约束是不够的。为什么?
因为,左边距和上边距确定了View的(x,y),然后左边距和右边距确定了这个View的宽度,我们缺少了Height。
但是这个Height,要由View内部的两个label来确定,为了让Xcode不再认为我们拉的约束有问题,再结合我们上面讲的Intrinsic Content Size,我们可以在Xib的这个位置设置Intrinsic Size为Placeholder,这样,Xcode就认为这个View有默认的大小,所以就不会报错了。

然后,自然是放入两个UILabel了。如图。


为了label能够多行显示,别忘记设置lineofnumber为0.
还有一点需要注意的是,两个UILabel之间肯定是需要一个设置一个垂直约束的,否则整个View就没有办法确定自己的高度,思考一下这是为什么?
运行一下,看看结果。

成功了。
2. Content Hugging Priority和Content Compression Resistance
这两个概念需要结合我们上面讲的Intrinsic Content Size来理解,每一个控件都有一个系统计算的最佳大小。
所以,Content Hugging Priority这个属性就代表着,一个控件拒绝本身size大于InstrinsicSize的优先级。
那么Content Compression Resistance,这个属性代表着,一个控件拒绝本身size小于InstrinsicSize的优先级。
这样子说还是有点抽象。那么我们来看一个例子好了。
我们在storyboard中创建了一个UITableView,并在其中创建了一个自定义的UITableViewCell。
这个自定义的UITableviewCell中有两个Label,都是多行显示的Label。然后给他们设定约束。
第一个Label设定的约束为:
- Leading Space:8
- Trailing Sace:8
- Top Space:8
- Bottom Space(距离下面的Label的间距): 9
第二个Label设定的约束为:
- Leading Space:8
- Trailing Sace:8
- Bottom Space:8
- Top Space(距离上面面的Label的间距): 9
设定好了,ok,好像没什么问题。如图。

但是,如果这时候你把UITableViewCell的高度扩大。看看是怎样的结果。如图。

Xcode报错了。为什么?
因为Cell的高度扩大,势必会影响两个Label的位置和大小,所以,现在Label面临着一个问题,在保持和父View(也就是UITableViewCell的contentView)间距不变的情况下,必须有一个Label是需要妥协的,怎么个妥协法呢?就是要扩大高度,扩大高度,就意味着比label本身的文字内容的高要大了。
那么,到底是两个Label中的哪一个label做出这个妥协呢?
Xcode并不知道,因为这个不知道,所以Xcode报错了。
话说回来了,Xcode为什么会不知道,就是因为两个Label的Content Hugging Priority里面的Vertical这个属性的值是一样的。因为两个Label拒绝变高的优先级相同,以至于Xcode不知道到底该拉伸哪个Label。所以,解决方案就是,把其中一个的改小。如图。

这样就可以了。那么,这样就结束了么?
如果这时候,你再把UITableViewCell的高度减小,会发生什么情况呢?Xcode又报错了。那么,这次的原因又是什么?

其实和上一个原因较为类似。
Cell的高度减小导致UILabel为了保持和父View间距不变,所以面临一个高度压缩的情况,那么到底谁压缩,Xcode依然不知道,因为两个Label的Content Compression Resistance这个属性里的Vertical优先级一样,和上面的解决办法一样只需要改小一个即可。

如果是手写Autolayout,在哪里写最好?
其实这个东西我在Reviewcode.cn里讨论过。但是鉴于可能会有人没有看过,并且对这个问题存疑,还是在这里说一下。
结论如下:
- 如果是在自定义view中,写在init方法中。
- 如果是在ViewController中,写在
- (void)viewDidLoad()中。
为什么不能写在viewDidAppear或者viewWillAppear中?
因为这个东西和NSNotification是一样的,你不能确定viewDidAppear和viewWillAppear调用的时机和调用的次序,不信的话,你可以用NSLog打印一下,并且使用手势在NavigationController中不停的左右滑动控制器,看看打印的结果。
但,viewDidLoad是可以保证在整个生命周期只出现一次的。为了避免约束重复添加,所以你应该在viewDidLoad中添加。
iOS进阶指南试读之UI篇的更多相关文章
- iOS设计指南
备忘:iOS设计指南:http://www.ui.cn/detail/32167.html
- 【读书笔记】读《高性能网站建设指南》及《高性能网站建设进阶指南:Web开发者性能优化最佳实践》
这两本书就一块儿搞了,大多数已经理解,简单做个标记.主要对自己不太了解的地方,做一些记录. 一.读<高性能网站建设指南> 0> 黄金性能法则:只有10%~20%的最终用户响应时间 ...
- iOS开发UI篇—CAlayer(自定义layer)
iOS开发UI篇—CAlayer(自定义layer) 一.第一种方式 1.简单说明 以前想要在view中画东西,需要自定义view,创建一个类与之关联,让这个类继承自UIView,然后重写它的Draw ...
- iOS开发UI篇—UITabBarController简单介绍
iOS开发UI篇—UITabBarController简单介绍 一.简单介绍 UITabBarController和UINavigationController类似,UITabBarControlle ...
- iOS开发UI篇—懒加载
iOS开发UI篇—懒加载 1.懒加载基本 懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...
- iOS开发UI篇—CAlayer层的属性
iOS开发UI篇—CAlayer层的属性 一.position和anchorPoint 1.简单介绍 CALayer有2个非常重要的属性:position和anchorPoint @property ...
- iOS开发UI篇—CAlayer(创建图层)
iOS开发UI篇—CAlayer(创建图层) 一.添加一个图层 添加图层的步骤: 1.创建layer 2.设置layer的属性(设置了颜色,bounds才能显示出来) 3.将layer添加到界面上(控 ...
- iOS开发UI篇—CALayer简介
iOS开发UI篇—CALayer简介 一.简单介绍 在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮.一个文本标签.一个文本输入框.一个图标等等,这些都是UIView. 其实 ...
- iOS开发UI篇—核心动画(UIView封装动画)
iOS开发UI篇—核心动画(UIView封装动画) 一.UIView动画(首尾) 1.简单说明 UIKit直接将动画集成到UIView类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画 ...
随机推荐
- 对Java的常用对象(POJO、DTO、PO、BO、VO、DAO)详细解释及应用场景
首先这些对象都应用都是一些单词的简称,也是一种应用思想,故其他语言也可以使用,在Java里比较常见这些对象吧.下面来一一解释. 一.POJO(Plain Ordinary Java Object). ...
- eclipse的jdk版本和spring冲突问题WARN XmlWebApplicationContext:1060 - Exception thrown from LifecycleProcessor on context close
项目环境: jdk1.8 tomcat7 问题:eclipse启动tomcat后控制台报如下错误: WARN XmlWebApplicationContext:1060 - Exception thr ...
- windows修改环境变量
windows的环境变量有两套: 系统的 当前用户的 不同的用户可以拥有不同的环境变量,当前用户的环境变量优先级比系统的环境变量优先级高,PATH环境变量比较特殊,它不是替换而是拼接. 在命令行下也可 ...
- 浅析iOS中的触摸事件
一.什么是响应者对象? 在 iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件.我们称之为“响应者对象”.UIApplication.UIViewControl ...
- 【LeetCode】234. Palindrome Linked List (2 solutions)
Palindrome Linked List Given a singly linked list, determine if it is a palindrome. Follow up:Could ...
- apache配置中ProxyPassReverse指令的含义
apache中的mod_proxy模块主要作用就是进行url的转发,即具有代理的功能.应用此功能,可以很方便的实现同tomcat等应用服务器的整合,甚者可以很方便的实现web集群的功能. 例如使用ap ...
- [转]expect实现ssh自动交互
shell脚本实现ssh自动登录远程服务器示例: #!/usr/bin/expect spawn ssh root@192.168.22.194 expect "*password:&quo ...
- Html插入Flash.object.embed.swf各个参数值详解介绍[等比例缩放]
http://www.dayku.cn/space-3-do-blog-id-544.html 目也在此列出. Internet Explorer 可识别用于 object 标签的参数:Netscap ...
- 代码管理(二)sourcetree 安装与使用
一 .SourceTree简介 SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端,拥有可视化界面,容易上手操作.同时它也是Mercurial和Subve ...
- Swift 构造与析构
前言 与 OC 一样,Swift 中也存在构造和析构过程.不同的是,OC 中的构造方法和析构方法只是普通的方法,而 Swift 中构造器和析构器是一种特殊的结构. 1.构造器 在 Swift 中,类或 ...