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的边距总是不变的,该怎么做呢?
一般来说有两种做法。

  1. 设置黄色View的autoresizingMask属性,设置为UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight,这样设置的结果是黄色View的宽高会随着父View宽高的改变而改变,但是不改变间距。
  2. 继承一个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 SizePlaceholder,这样,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是一样的,你不能确定viewDidAppearviewWillAppear调用的时机和调用的次序,不信的话,你可以用NSLog打印一下,并且使用手势在NavigationController中不停的左右滑动控制器,看看打印的结果。
    但,viewDidLoad是可以保证在整个生命周期只出现一次的。为了避免约束重复添加,所以你应该在viewDidLoad中添加。
 
 

iOS进阶指南试读之UI篇的更多相关文章

  1. iOS设计指南

    备忘:iOS设计指南:http://www.ui.cn/detail/32167.html

  2. 【读书笔记】读《高性能网站建设指南》及《高性能网站建设进阶指南:Web开发者性能优化最佳实践》

    这两本书就一块儿搞了,大多数已经理解,简单做个标记.主要对自己不太了解的地方,做一些记录.   一.读<高性能网站建设指南> 0> 黄金性能法则:只有10%~20%的最终用户响应时间 ...

  3. iOS开发UI篇—CAlayer(自定义layer)

    iOS开发UI篇—CAlayer(自定义layer) 一.第一种方式 1.简单说明 以前想要在view中画东西,需要自定义view,创建一个类与之关联,让这个类继承自UIView,然后重写它的Draw ...

  4. iOS开发UI篇—UITabBarController简单介绍

    iOS开发UI篇—UITabBarController简单介绍 一.简单介绍 UITabBarController和UINavigationController类似,UITabBarControlle ...

  5. iOS开发UI篇—懒加载

    iOS开发UI篇—懒加载 1.懒加载基本 懒加载——也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...

  6. iOS开发UI篇—CAlayer层的属性

    iOS开发UI篇—CAlayer层的属性 一.position和anchorPoint 1.简单介绍 CALayer有2个非常重要的属性:position和anchorPoint @property ...

  7. iOS开发UI篇—CAlayer(创建图层)

    iOS开发UI篇—CAlayer(创建图层) 一.添加一个图层 添加图层的步骤: 1.创建layer 2.设置layer的属性(设置了颜色,bounds才能显示出来) 3.将layer添加到界面上(控 ...

  8. iOS开发UI篇—CALayer简介

    iOS开发UI篇—CALayer简介   一.简单介绍 在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮.一个文本标签.一个文本输入框.一个图标等等,这些都是UIView. 其实 ...

  9. iOS开发UI篇—核心动画(UIView封装动画)

    iOS开发UI篇—核心动画(UIView封装动画) 一.UIView动画(首尾) 1.简单说明 UIKit直接将动画集成到UIView类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画 ...

随机推荐

  1. python正则检测密码合法性

    客户系统升级,要求用户密码符合一定的规则,即:包含大小写字母.数字.符号,长度不小于8,于是先用python写了个简单的测试程序: #encoding=utf-8 #----------------- ...

  2. excel中的数据粘贴不全到plsql中,excel 粘贴后空白,Excel复制粘贴内容不全

    http://zhidao.baidu.com/link?url=pHZQvfWJzI-lQjl4uP86q4GLcpYHu4o-fdjiYegJS0Cy5HEq5oz0YrUye3iHjmv5CJ3 ...

  3. C/C++函数指针(typedef简化定义)

    学习要点:        1,函数地址的一般定义和typedef简化定义;        2,函数地址的获取;        3,A函数地址作为B函数参数的传递;    函数存放在内存的代码区域内,它 ...

  4. B. Eight Point Sets

    B. Eight Point Sets http://codeforces.com/contest/334/problem/B   time limit per test 1 second memor ...

  5. HDU 4602 Partition (矩阵乘法)

    Partition Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total S ...

  6. HDU 4607 Park Visit (树的最长链)

    Park Visit Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  7. 新浪安装weiphp2.0的方法

    请安装此网页的方法来安装: https://coding.net/u/idoubi666/p/weiphp-sae/git

  8. linux下开启https

    1.在线安装mod_ssl yum -y install mod_ssl 查看openssl 是否安装成功 rpm -qa |grep openssl 2.建立服务器密钥 openssl genrsa ...

  9. Git Note

    Git 参考 http://chengshiwen.com/article/head-first-git/ 文件状态 Git目录: (git directory),亦即Git仓库,一般对应项目根目录下 ...

  10. Android 6.0+ RecyclerView嵌套在ScrollView中显示不全

    ScrollView嵌套RecyclerView在Android6.0以下能正常显示,但是在6.0以上就会出现RecyclerView显示不全的bug.尝试多种方法之后终于找到解决办法,特在此记录下. ...