View编程指南(二)


接上篇 View编程指南(一)

三、View的坐标系统

1. 坐标系统基础


UIKit默认的坐标系统是从左上角开始的。坐标系统里的点使用float表示,这样可以更加精确的布局和定位。图1-4显示了屏幕的坐标系统。除了屏幕的坐标系统之外,window和view有自己的本地坐标系统,这样可以允许你相对于view和window来进行定位。

图1-4

因为view和window都有他们自己的本地坐标系统,因此你需要时刻注意哪个坐标系统正在生效。当你进行绘制的时候,你的坐标是相对于view自己的坐标系统。当你进行几何改变时,你的坐标是相对于superview的坐标系统。UIWindow和UIView都有帮助你进行坐标系统变换的方法,所以不要过于担心。

其他的一些黑科技例如Core Graphics和OpenGL ES使用的是左下角为原点的坐标系,因此当你的代码涉及到这两套坐标系时,记得进行相应的变换!

2. Frame、Bounds和Center属性之间的关系


  • frame属性,它指定了当前view在其superview的坐标系统中的位置和大小
  • bounds属性,它指定了当前view在其自身的本地坐标系统中的大小以及view的内容的原点在哪里
  • center属性,它指出了当前view在其superview的坐标系统中的中心点位置

一般而言,你使用center和frame属性来进行view的几何操作。例如:如果你要改变view的位置,一般使用center属性,因为center属性几乎在任何情况下都是可用的,即使view在拉伸、旋转。

在绘制view时,一般会用到bounds属性。默认bounds是以view的本地坐标系的0,0作为原点,大小和frame的大小相同。

图1-5展示了这三者之间的关系

图1-5

尽管你可以分别改变frame、bounds和center属性,但事实上改变其中之一会影响到其他的属性:

  • 当你设置frame属性时,bounds属性中的size值也会随着frame属性的size值改变。center属性的值也会跟着改变以表示新的中心点
  • 当你设置center属性时,你的frame的origin值会跟着改变
  • 当你设置bounds属性的size值时,frame属性的size值也会相应的改变

默认情况下,view的frame没有clip到superview的frame,这样如果subview有部分是绘制到superview的frame外的话,那部分也会进行绘制,虽然那部分不可见。你可以设置superview的clipsToBounds属性为true。不管你有没有设置clip,触摸事件总是只会在可视区域里触发,也就是说如果在subview绘制在superview外的区域里产生点击事件,事件不会被传递到view里。

3. 坐标系统变换


坐标系统变换提供了一个方便快捷的方式来更改你的view或它的内容。仿射变换(affine transform)是指如何将某个坐标系统中的点映射到另一个坐标系统里。你可以用仿射变换来更改整个view的位置、大小或者它的朝向(orientation)。你也可以使用它来对绘制内容中的一部分来进行变换。如何使用仿射变换取决于你使用时的上下文:

  • 应用于整个view,直接设置view的transform属性
  • 应用于绘制内容中的一部分,在你的drawRect方法中,将仿射变换应用于给定的graphices上下文

通常修改view的transform属性是为了实现view的动画。你一般不会使用transform来改变你的view的位置或者大小。为了设置view的位置或大小,我们通常直接修改view的frame属性。

当修改view的transform属性时,所有的变换都是根据view的中心点(center)来进行的。

在你的drawRect方法里,通常根据(0,0)这个原点来对需要绘制的内容进行布局并应用变换。这样做的话,当你的view里的内容位置发生改变时,你只需要修改变换就可以了,这通常比重绘更快、性能更好。你可以通过CGContextGetCTM方法来活的graphics上下文关联的仿射变换对象。

Current transformation matrix(CTM)对象是指你当前正在使用的仿射变换。当你对整个view操作时,CTM就是存储在view的transform属性里的那个值。

每个subview的坐标系统是基于它的superview的,也就是说当你更改一个view的transform属性的时候,会影响到它所有的subview。但是这些改变只会影响到最终在屏幕上的绘制。因为每个view对于它的subview的绘制和布局都是根据它们自己的bounds,因此它会忽略superview的transform。

图1-6展示了两个不同的旋转变换是如何组合在一起的。在view的drawRect方法里我们将图形进行了45°的旋转。在view上我们又进行了45°的旋转。这样图形看上去被旋转了90°。

图1-6

如果一个view的transform属性不是identity transform,view的frame属性必须被忽略。你应当使用view的bounds和center属性来获取size和位置。

4. 点和像素


在iOS里,所有坐标值都使用浮点型的值,单位是。由于不同设备的像素密度是不同的,因此用点可以使用一种坐标值统一所有设备上的内容呈现。

当你确实需要使用图片或者其他基于像素的技术,例如:OpenGL ES。iOS会帮你管理这些点到像素之间的映射(还记得imageset里1x、2x、3x的图片么)。view也提供关于当前像素密度的信息,因此你可以根据这些信息来调整基于像素的绘制代码以适应高分辨率屏幕。

四、View的运行时交互模型

当用户和你的UI进行交互的时候或者当你编程来触发一些事件的时候,UIKit会有一些列复杂的事件序列产生。UIKit在适当的时候会通知你的view来响应这些事件。图1-7展示了从用户点击屏幕到最后图形系统相应点击并重绘屏幕的过程。

图1-7

具体来说,当图1-7的情况发生时,产生了下列的事件序列:

  1. 用户点击屏幕(呵呵)
  2. 硬件向UIKit Framework报告点击事件
  3. UIKit Framework将点击事件打包成UIEvent对象传递给合适的view
  4. 事件处理代码响应事件,例如:
     * 改变view或者subview的属性(frame, bounds, alpha等)
     * 调用setNeedsLayout方法将view或者subview标记为需要更新布局
     * 调用setNeedsDisplay或setNeedsDisplayInRect来标记view或者subview需要重绘
     * 通知controller需要更新一些数据
  5. 如果view的几何外观改变了,UIKit会根据下面的规则去改变它的subview
     a. 如果你有配置autoresizing规则,UIKit根据这些规则来调整view
     b. 如果view实现了layoutSubviews方法,UIKit会调用它
  6. 如果view的任何一部分被标记为需要重绘,UIKit会让view重绘它。自定义view如果重载了drawRect方法,UIKit会调用它来进行重绘。
  7. 更新后的view和其他应用的可视内容会被发送到图形硬件来进行显示
  8. 图形硬件将绘制好的内容发送到屏幕

以上更新模型的步骤主要适用于使用了标准系统view的应用。使用OpenGL ES来绘制的应用通常直接在整个屏幕上进行绘制,并且不需要布局subview。

在上面的步骤中,自定义view的主要可干预点如下:

  • 事件处理:
    -touchesBegin:withEvent
    -touchesMoved:withEvent
    -touchesEnded:withEvent
    -touchesCancelled:withEvent
  • layoutSubviews方法
  • drawRect方法

五、高效使用View的技巧

1. 尽可能少的进行自定义绘制


尽管自定义绘制在有些时候是必须的,但是它也是你应当尽可能避免的。只有当系统的view没法提供你需要的外观或者功能时,你才需要这么做。任何时候,如果你可以通过组合使用系统的view来达到你的目的,那你就应当把它们组合在一个自定义的view层级结构里,而不是自己实现一个自定义view

2. 尽可能的利用content mode


适当的content mode可以减少你重绘所需的时间。你应当尽量避免使用UIViewContentModeRedraw。因为不管你使用什么content mode,你都能通过调用setNeedsDisplay或setNeedsDisplayInRect来强制你的view进行重绘

3. 尽可能将你的view设置成不透明


UIKit使用每个view的opaque属性来决定是否可以优化合成绘制操作。如果将这个值设置成true,UIKit就知道它不需要绘制任何这个view背后的内容(因为他们被你的view遮盖,并且不可见)。

4. 当滚动时调整view的绘制策略


滚动会在短时间内造成多次的view绘制。如果你的绘制代码没有优化过,滚动时就会非常的卡顿。因此不要尝试在滚动时保证你的view的内容都清晰可见,例如:你可以在滚动时将view的内容绘制质量临时降低,当滚动结束时,你再使用高质量重新绘制你的内容。

5. 不要通过嵌入subview来自定义系统控件


尽管技术上可以往标准系统控件(那些从UIControl继承的对象)里插入subview,但是请不要这么干!如果某个控件支持定制,它会在文档里详细说明,通过那些方法来定制是非常靠谱的。但是通过自己插入subview的方式来定制会造成你的应用在将来不可用(如果将来控件的实现机制改变了)。


下篇 View编程指南(三)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容