iOS视图编程指南官方文档笔记 View Programming Guide For iOS
在iOS中,使用窗口window和视图view来表达屏幕上的内容。windows只给app的视图们提供一个基本的容器container,自身并没有任何可见的内容。视图定义了你想在window里填充的部分内容。
任何程序都有至少一个窗口和一个表达内容的视图。UIKit或者其他系统提供预先定义好的视图view,你可以用这些view表达内容。这些视图包含按钮,label,更复杂一些的油table,collectionview,picker等。如果这些预定义的视图不满足你的要求,你可以自己定义custom驶入,自己管理绘制drawing和事件处理。
在iOS中,一个视图是一个UIview的实例。管理了app window的一个矩形区域。驶入的职责是绘制内容,处理多种点击事件和管理view的字视图subviews的布局layout。绘制drawing这个操作调用了很多图形技术,例如core graphics,openGL ES。drawing也使用uikit在view的矩形区域内来绘制图形,图片和文字。一个view负责响应矩形区域内的点击事件(通过使用手势识别或者直接处理点击事件)。在视图的层级中,parentview负责处理字视图的大小和位置,这个操作可以动态完成的。这种操作子视图的能力能够让view很好的适配一些变化的情况,比如设备终端的物理旋转和动画等。
可以把view当成构建用户界面的block。常见做法是使用不同的view创建层级关系,而不是在一个视图里表示所有内容。处于层级关系里的view只表达界面的部分内容(view负责自己最擅长,最优化的内容),比如,UIkit有专门用于显示文字,图片等内容的view。
VIEW的基本结构View Architecture Fundamentals
视图与核心动画层一起工作,以处理视图内容的呈现和动画。每个UIkit的视图背后是一个layer object(一个CAlayer的实例),负责管理视图的存储处理view相关动画。大部分的操作应该通过UIview借口。但是,在某些情况,比如需要对绘制和动画行为有更多控制的时候,可以对 layer进行操作。
视图对象的实际绘图代码尽可能少地调用, 当调用代码时, 结果将被核心动画缓存并在以后尽可能多地重用。重用已呈现的内容可以消除更新视图时通常需要的昂贵的绘图周期。在动画中重用此内容尤其重要, 在这里可以对现有内容进行操作。这种重用比创建新内容要便宜得多。
View的绘制周期
uiview使用“按需on-demand”绘制模式来表达内容。当一个view首次在屏幕上展示的时候,系统让它绘制自己的内容。系统创建一个内容的快照,使用这个快照作为这个视图的图形化表达。如果不需要改标这个视图的内容,这个视图的绘制代码就再也不会被调用。这个快照图片在大多数创建这个视图的操作里重用。如果内容确实修改了,你负责通知系统,告诉他这个视图改变了。这是视图重复绘制过程,船舰一个新的快照结果。
当view的内容变化时,你不必直接重画这些变化。使用setNeedsDisplay or setNeedsDisplayInRect: 方法来让视图失效。这些方法通知系统视图的内容改变了,需要在下次机会重新绘制。系统等待当前住线程runloop完成一个loop,然后初始化任何绘制的操作。这个延迟给了我们让多国视图失效的机会,在层级里一次性的执行添加、移除视图,隐藏,调整大小、位置等操作。所有的变化都在同时反应结果。
Note: 更改视图的几何图形不会自动导致系统重新绘制视图的内容。视图的 contentMode 属性确定如何解释对视图几何的更改。大多数contentMode在视图的边界内拉伸或重新定位现有快照, 而不是创建新的。
当绘制视图内容的时机到来时,实际的绘制过程根据视图和驶入的配置而有所不同。系统视图通常实现私有的绘制方法来绘制内容。这些同样的系统视图通常暴露接口给你涌来配置视图的实际显示。那一个UIview的subclass距离,你通常只需要override drawRect方法,用这个方法绘制视图的内容。当然也有许多别的方法提供视图内容,比如直接设置视图的底层内容(文档这里没展开讲啊),但是使用drawrect是常用技术。
Content Modes
每个视图都有一个content modes 控制视图如何回收其内容,以响应视图的几何变化,以及它是否会重新回收其内容。当一个视图首次显示的时候,视图像往常一样绘制自己的内容,绘制的结果被转化进一个底层的bitmap。这个操作后,改变这个视图的集合图形不会每次触发这个bitmap的重新建立。想法,contentmode属性里的value决定这个bitmap是应该被缩放来适应新的范围还是简单的古锭刀视图的某个角落点。
Contentmode 绝对不会搞遍驶入的frame和bounds
contentmode 绝不会指派一个包含缩放等操作的变换transform给视图的transform属性
内容模式很好地回收了视图的内容, 但当您特别希望自定义视图在缩放和调整大小操作期间重新绘制时, 也可以将内容模式设置为 UIViewContentModeRedraw 值。将视图的内容模式设置为此值会强制系统调用视图的 drawRect: 方法以响应几何更改。一般情况下, 应尽可能避免使用此值, 并且不应该将其与标准系统视图一起使用。
可伸展的View Stretchable Views
您可以将视图的一部分指定为可拉伸的,这样当视图的大小改变时,可拉伸部分的内容就会受到影响。
使用 contentStretch 属性指定视图的可伸缩区域。此属性接受一个矩形, 其值被规范化为范围0.0 到1.0。拉伸视图时, 系统将这些规范化值乘以视图的当前的边界和缩放因子, 以确定需要拉伸的像素或像素。每当视图范围发生变化时,使用规范化值就可以减少更新contentStretch属性的必要性。
视图的内容模式在确定如何使用视图的可伸缩区域方面也起着作用。可伸缩区域仅在内容模式导致视图内容缩放时使用。这意味着只能使用 UIViewContentModeScaleToFill、UIViewContentModeScaleAspectFit 和 UIViewContentModeScaleAspectFill 内容模式支持伸缩视图。如果指定的内容模式将内容引脚到边缘或拐角 (因而不实际缩放内容), 则视图将忽略可伸缩区域。
Frame,bounds,center关系
frame 是用于在super view 坐标中表示位置大小的矩形
bouds是在自身坐标体系中表示位置大小的矩形
center是在super view坐标体系中记录的中心点
当用来调整view位置是,使用frame,center是个好方法。 bounds属性主要用于绘制。
设置frame属性时, “bouds” 属性中的大小值将更改为与frame矩形的新大小相匹配。center属性中的值类似地更改, 以匹配frame矩形的新中心点。
设置center属性时, frame中的原值将相应更改。
设置 “bounds” 属性的大小时, “frame” 属性中的大小值将更改为与bounds矩形的新大小相匹配。
默认情况下,一个view的frame不会被superview 的frame裁切。因此,任何子视图漏在父视图外的frame都会被完整的绘制。你可以通过设置superview的 clopsToBounds 来修改。但是点击事件永远响应的区域是视图的有效bounds。被裁切的不响应。
坐标系统变换 Coordinate System Transformations
坐标系转换提供了一种快速方便地更改视图(或其内容)的方法。affin transform是一个数学矩阵,用于指定一个坐标系中的点如何映射到不同坐标系中的点。您可以将affin transform应用于整个视图,以更改视图相对于其super视图的大小、位置或方向。您还可以在绘图代码中使用affin transform对各个渲染内容执行相同类型的操作。您如何应用仿射变换取决于上下文:
- 如果用于修改整个视图,修改视图的transform属性来修改 affin transform。
- 如果在drawRect里指定部分视图的内容,通过制定当前活跃的图形上下文来修改affine transform。
在您的视图的drawRect:方法中,您使用affine transform 来定位您计划绘制的项目。 不是在视图的某个位置固定对象的位置,而是通过创建每个对象相对于固定点(通常为(0,0))更简单,并且在绘制之前使用变换来立即定位对象。 这样,如果对象的位置在您的视图中发生变化,您所要做的就是修改变换,这比在其新位置重新创建对象要快得多,成本也更低。您可以使用CGContextGetCTM函数检索与图形上下文关联的affine transform,并且可以使用相关的核心图形函数在绘制期间设置或修改此变换。
current transformation matrix (CTM) 是在任何给定时间使用的affine transform。当操作整个view的几何图形时,CTM 存储在view的 transform 属性中。在 drawRect方法中,CTM是指定给活动图形上下文的 affine transform。
每个子视图的坐标系建立在其祖先的坐标系上,所以当修改一个view的transform属性时,这个变化影响视图和她的子视图。但是,这些变化在最终绘制是才在屏幕上产生效果。因为每个view绘制自己的内容,布局自己相关范围的subviews,绘制和布局期间,view 可以忽略 superview的变换。
Importent: 如果一个view的transform属性不是标识transform,那么该视图的frame属性值是未定义的,必须忽略。 变换应用于视图时,必须使用视图的bounds和center属性来获取视图的大小和位置。 任何子视图的frame矩形仍然有效,因为它们相对于视图的边界。
像素VS点
在iOS中,所有坐标值和距离均使用以点为单位的浮点值指定。点的可测量的大小因设备而异,并且在很大程度上不相关。要了解点的主要内容是它们为绘图提供了一个固定的参考框架。
用于每种类型设备的基于点的测量系统定义了所谓的用户坐标空间。 这是几乎所有代码使用的标准坐标空间。 例如,在处理视图的几何图形或调用Core Graphics函数绘制视图的内容时,可以使用点和用户坐标空间。 虽然用户坐标空间中的坐标有时会直接映射到设备屏幕上的像素,但您绝对不应该认为这是事实。 相反,你应该永远记住以下几点:
一点不一定对应于屏幕上的一个像素。
在设备级别,您在视图中指定的所有坐标必须在某个点转换为像素。但是,用户坐标空间中的点到设备坐标空间中的像素的映射通常由系统处理。 UIKit和Core Graphics都使用主要基于矢量的绘图模型,其中所有坐标值均使用点来指定。因此,如果使用Core Graphics绘制曲线,则无论基础屏幕的分辨率如何,都可以使用相同的值指定曲线。
当您需要使用图像或其他基于像素的技术(如OpenGL ES)时,iOS可以帮助您管理这些像素。对于作为资源存储在应用程序包中的静态图像文件,iOS定义了用于以不同像素密度指定图像的惯例以及用于加载与当前屏幕分辨率最匹配的图像的约定。视图还提供有关当前比例因子的信息,以便您可以手动调整任何基于像素的绘图代码以适应更高分辨率的屏幕。
视图的运行时交互模型
screen【处理点击–》点击framework-》UIKit】–》MyApplication【touches(setneedslayoyout,setneedsdisplay,frame,alpha,etc)–》layoutsubviews(setneedsdisplay,frame,alpha,etc)–》draw rect(Draw images,text,etc)】–》(transfer Buffers,images,Attributes,Geometry,Animations to iOS)–》iOS【Compositor(排版)–》Graphics hardware】–》screen
- 用户触摸屏幕。
- 硬件将触摸事件报告给UIKit框架。
- UIKit框架将触摸包装到UIEvent对象中并将其分派到适当的视图。 (有关UIKit如何将事件传递到视图的详细说明,请参阅iOS事件处理指南。)
- 视图的事件处理代码响应该事件。例如,您的代码可能会:
-
- 更改视图或其子视图的属性(框架,边界,alpha等)。
- 调用setNeedsLayout方法将视图(或其子视图)标记为需要布局更新。
- 调用setNeedsDisplay或setNeedsDisplayInRect:方法将视图(或其子视图)标记为需要重绘。
- 通知控制器有关某些数据的更改。当然,由您来决定视图应该做哪些事情以及应该调用哪些方法。
- 如果视图的几何图形因任何原因而更改,则UIKit将根据以下规则更新其子视图:
-
- 如果您已为视图配置了自动调整规则,则UIKit会根据这些规则调整每个视图。有关自动调整规则如何工作的更多信息,请参阅自动调整规则自动处理布局更改。
- 如果视图实现了layoutSubviews方法,UIKit会调用它。您可以在自定义视图中重写此方法,并使用它来调整任何子视图的位置和大小。例如,提供大型可滚动区域的视图需要使用多个子视图作为“图块”,而不是创建一个大视图,而该视图无论如何都不太适合内存。在实现这种方法时,视图会隐藏现在不在屏幕上的任何子视图或重新定位它们并使用它们来绘制新公开的内容。作为此过程的一部分,视图的布局代码也可以使任何需要重绘的视图失效。
- 如果任何视图的任何部分被标记为需要重绘,UIKit会要求视图重绘自己。
-
- 对于显式定义drawRect:方法的自定义视图,UIKit会调用该方法。你的这个方法的实现应该尽可能快地重绘视图的指定区域,而不是其他的。此时不要进行额外的布局更改,也不要对应用程序的数据模型进行其他更改。此方法的目的是更新视图的可视内容。 标准系统视图通常不会实现drawRect:方法,而是在此时管理其绘图。
- 任何更新的视图都会与应用程序的其余可见内容合成,并发送到图形硬件进行显示。
- 图形硬件将渲染的内容传输到屏幕上。
注意:上述更新模型主要适用于使用标准系统视图和绘图技术的应用程序。使用OpenGL ES进行绘图的应用程序通常会配置单个全屏视图并直接绘制到关联的OpenGL ES图形上下文中。在这种情况下,视图可能仍然会处理触摸事件,但由于它是全屏的,因此不需要布置子视图。有关使用OpenGL ES的更多信息,请参阅“OpenGL ES编程指南”。
上述的步骤中,主要交互点是:
事件处理方法:
layoutSubviews 方法
draw rect方法
这些是视图中最常用的重写方法,但您可能不需要覆盖所有视图。如果使用手势识别器来处理事件,则不需要重写任何事件处理方法。同样,如果您的视图不包含子视图或其大小不会更改,则没有理由覆盖layoutSubviews方法。最后,只有在视图的内容可以在运行时更改并且使用UIKit或Core Graphics等本机技术执行绘制时,才需要drawRect:方法。记住这些是主要的整合点,但不是唯一的整合点。
有效使用视图小贴士
1,处理好和viewcontroller的关系,不是所有view都有VC,
2;最小化绘制,只在最有用的地方绘制。但是按照现在流行的趋势,是能放入后台的都放入后台绘制。
3,使用content mode的优点,避免使用redraw,
4,如果可能,让视图保持不透明(opaque),UIKit使用每个视图的opaque属性来确定视图是否可以优化合成操作。设置view的opaque属性为YES,这个行为通知了UIKIT,它不需要绘制人后他后面的视图。较少的渲染可以提高您的绘图代码的性能,并且通常会受到鼓励。当然,如果将opaque属性设置为YES,则视图必须用完全不透明的内容完全填充其边界矩形。
5,在滚动时调整视图的绘图行为。滚动可以在很短的时间内产生大量的视图更新。如果您的视图的绘制代码未适当调整,则视图的滚动性能可能会很低。在开始滚动操作时,不要试图确保视图的内容始终处于原始状态,而应考虑更改视图的行为。例如,您可以暂时降低渲染内容的质量,或在滚动正在进行时更改content mode。当滚动停止时,您可以将视图恢复到之前的状态并根据需要更新内容。
6,不要通过嵌入子视图来定制控件。虽然在技术上可以将子视图添加到标准系统控件 – 从UIControl继承的对象 – 您不应该以这种方式定制它们。支持自定义的控件通过控件类本身的明确和详细记录的接口来实现。例如,UIButton类包含用于设置按钮的标题和背景图像的方法。使用定义的定制点意味着您的代码将始终正常工作。通过在按钮中嵌入自定义图像视图或标签来限制这些方法,如果按钮的实现发生更改,则可能会导致应用程序现在或未来某个时刻的行为不正确。
关于Windows
Windows是专门用于显示内容的对象。一个app有1个或者多个窗口。window的主要职责:1,包含app的显示内容;2,传递点击事件给viewhe其他app对象的关键角色;3,和app的view controller一起对应屏幕方向变化。
关于Views
View是用于显示内容的主体。职责只要是layout subview,drawing and animation and event handling。
需要注意的是,在add / remove view from super view的时候,UIKit 会向parent 和child view发送变动的消息通知。如果你实现了自定义的views,你可以通过override willMoveToSuperview:, willMoveToWindow:, willRemoveSubview:, didAddSubview:, didMoveToSuperview, or didMoveToWindow 等方法来在这些通知中处理你的业务逻辑。
另一点注意的是,隐藏一个view的时候,如果这个view是first responder,那么隐藏view这个操作并不会自动的让这个view resign first responder。原有的事件还是会传递给这个隐藏的view。
关于强制 Layout changes
在以下情况中,一个view的layout将会变化。:
1,视图的 bounds 改变的时候
2,界面的方向旋转,会出发root view的bounds 改变
3,指定驶入的 layer 变化给Core Animation sublayers集合是,需要重新 layout
4,App调用视图的 setNeedsLayout/layoutIfNeeded方法
5,App调用视图底层 layout对象的setNeedsLayout方法时
关于手动微调视图的布局
只要视图的大小发生变化,UIKit就会应用该视图子视图的自动调整行为,然后调用视图的layoutSubviews方法以使其进行手动更改。您可以在自定义视图中自定义视图中实现layoutSubviews方法,而这些方法本身不会产生所需的结果。此方法的实现可以执行以下任何操作:
1,调整任何直接子视图的大小和位置。
2, 添加和删除子view或者 Core Anination Layers
3,调用setNeedsDisplay or setNeedsDisplayInRect 强制 子view redraw
应用程序通常手动布置子视图的一个地方是实现大型可滚动区域时。因为对于其可滚动内容拥有单个大视图是不切实际的,所以应用程序通常实现包含许多较小视图的根视图。每个图块代表可滚动内容的一部分。当发生滚动事件时,根视图会调用其setNeedsLayout方法来启动布局更改。其layoutSubviews方法然后根据发生的滚动量重新定位图块视图。随着切片从视图的可见区域滚出,layoutSubviews方法将切片移动到传入边缘,替换进程中的内容。
编写布局代码时,请务必以下列方式测试您的代码:
1,更改视图的方向,以确保布局在所有支持的接口方向上看起来正确。
2,确保代码正确响应状态栏高度的变化。打电话时,状态栏高度会增加,当用户结束通话时,状态栏的大小会减小。
关于处理 core animation layers Interacting with Core Animation Layers
每个视图对象都有一个专用的核心动画层,用于管理屏幕上视图内容的显示和动画。layer 对象在view的layer属性里。
每个layer在创建后,她的layer的类型class就确定了而且不能更改。所以,每个view使用 layerClass 类方法确定一个layer对象的类型。此方法默认返回 CALayer,只有在子类重写此方法。layer里又一个指向view的delegate。
如果想主要使用layer而不是view,你可以自定一些layer对象加入到视图层级关系里。一个自定义的layer时calayer的实例,不被任何view拥有。通常使用代码创建layer对象,并使用core animation 方法合并她们。自定义layer不接受事件或者参与响应链,只绘制自己,响应parent view的大小变化或者醉醺core animation 规则。可以自定义layer,加入到view的layer实例的sublayer中。
关于drawRect方法
drawRect方法里只能做一件事,就是绘制内容,不应该有其他任何不相关的任务。在调用视图的drawRect:方法之前,UIKit为您的视图配置基本绘图环境。具体来说,它会创建一个图形上下文并调整坐标系和裁剪区域以匹配视图的坐标系和可见边界。因此,在调用drawRect:方法时,您可以使用本机绘图技术(如UIKit和Core Graphics)开始绘制内容。您可以使用UIGraphicsGetCurrentContext函数获取指向当前图形上下文的指针。
当前图形上下文仅在对视图的drawRect:方法进行一次调用期间有效。 UIKit可能为每次后续调用此方法创建一个不同的图形上下文,因此您不应该尝试缓存该对象并稍后使用它。
如果您知道视图的绘图代码始终覆盖具有不透明内容的视图的整个表面,那么可以通过将视图的opacity属性设置为YES来提高系统性能。当您将视图标记为不透明时,UIKit会避免绘制位于视图后面的内容。这不仅减少了绘图时间,而且还最大限度地减少了将视图与其他内容进行合成所必须完成的工作。但是,只有当您知道视图的内容完全不透明时,才应将此属性设置为YES。如果您的视图无法保证其内容始终不透明,则应将该属性设置为NO。
另一种提高绘图性能的方法,特别是在滚动期间,将视图的clearsContextBeforeDrawing属性设置为NO。因为当此属性设置为YES时,UIKit会在调用方法之前用透明的黑色区域填充 drawRect:需要更新的区域。将此属性设置为NO将消除该填充操作的开销。但是增加app 的drawRect方法填充更新矩形内容的负担。
关于事件响应
view对象都是 UIResponder的实例,用于接受事件。当有一个touch 时间是,window调度当前时间到发生touch的view,如果view对此touch不感兴趣,将会忽略此事件、在responder chain上传递此事件到一个能处理的对象。
除了直接处理event,views还可以使用gesture recognizers来检测tap swip pinches或者其他类型的常见手势。
关于Animations
Animation可以响应的属性有frame ,bounds,center,transform,alpha,background,contentStretch等。
animation代码可以嵌套,也可以实现自己reverse,在创建可重复计数的可逆动画时,请考虑为重复计数指定非整数值。对于自动对换动画,动画的每个完整周期都包含从原始值到新值的动画并再次返回。如果您希望动画以新值结束,则将0.5加入重复计数会导致动画完成需要以新值结束的额外半周期。如果您不包含这一半步骤,您的动画将动画为原始值,然后快速捕捉到新值,这可能不是您想要的视觉效果
可以在views见创建动画的转场 Creating Animated Transitions Between Views
视图转换可帮助您隐藏与在视图层次结构中添加,删除,隐藏或显示视图相关的突然变化。您使用视图转换来实现以下类型的更改: 更改现有视图的可见子视图。当您想对现有视图进行相对较小的更改时,通常会选择此选项。 用不同视图替换视图层次结构中的一个视图。当您想要替换跨越全部或大部分屏幕的视图层次结构时,通常选择此选项。 视图Transitions不应与view controller的Transitions 相混淆,例如model view contrlller的呈现或将新View Controller推入navigation stack。视图Transitions仅影响视图层次结构,而View Controller Transitions也会更改当前活动的View Controller。因此,对于视图Transitions,Transitions完成时、启动转换时,处于活动状态的View Controller始终保持活动状态。
使用 transitionWithView:duration:options:animations:completion: 方法完成 view的subviews之间的转换。
使用 transitionFromView:toView:duration:options:completion: 方法完成不同view间的replace。
关于正在animating的View和layer 一起变化 Animating View and Layer Changes Together
应用程序可以根据需要自由混合基于View和基于layer的动画代码,但配置动画参数的过程取决于谁拥有Layer。更改View拥有的Layer与更改View本身相同,并且应用于图View属性的任何动画都尊重当前基于View的动画block的动画参数。对于您自己创建的Layer也是如此。自定义Layer对象会忽略基于视图的动画block参数,使用默认的Core animation参数。
如果要为所创建的View 自定义动画参数,则必须直接使用Core Animation。通常,使用Core Animation动画化View包括:创建CABasicAnimation对象;创建CAAnimation的其他具体子类。然后,您将该动画添加到相应的layer。您可以从基于视图的动画block内部或外部应用此动画。
end