1.view分层
(1)View的结构是分层的,一个view只能有一个父view,但可以有多个子view。子view的顺序是相关的,在数组中的位置越高或者说数字越大,就显示在后面,位置低的显示在前面。顶层的在后面,底层的在前面。view可以重叠。
(2)最顶层的view是管理屏幕的controller的view。如果mvc的view是另一个controller,通常会有一个controller的view是整个屏幕,其他细节就交给其他controller。
可以用代码实现view分层,有2个重要代码: (1)addSubview是往一个view里添加子view。 (2)把一个view移除,你不能要求同一个view移出刚刚添加的view,你要去到那个view,让它移除它自己。 总结:通过父view添加子view,但是移除要通过子view自己。
2. view coordinates
view是个坐标系统,
(1)CGFloat,图形化编程时要用CGFloat,不用float或double
CGPoint、CGSize、CGRect定义:
struct CGPoint { float x; float y; };
struct CGSize { float width; float height; };
struct CGRect { CGPoint origin; CGSize size ; };
(2)view的坐标系统原点在左上角,增加x是向右的,增加y是向上的。
(3)坐标单位是点而不是像素。点是图形上的术语,UIView上有个property叫contentScaleFactor,它会返回一个点有多少像素。编程时只要使用点就可以了,通常你不需要知道像素,ios会处理好的,比如字体会自动做平滑处理。
(4)views有3个和位置大小相关的property:bounds、center、frame bounds是个矩形,它是view自己的坐标系统下的绘画区域,所以如果在你自己的绘图实现中,你创建了个自定义的view,当你绘图的时候,只会用到bounds。此时不要在view的实现里面用另外两个property,别管原因只要这么做就行了。
center和frame是在父view坐标系统中你的view的中心点和容纳view的矩形,所以这两个只用来在父view中定位你的view。center和frame是连在一起的,你设置了center会移动frame,设置frame也就设置了center。
永远不要在view的实现里用frame和center,这两个是用来定位的。
4. Custom Views(自定义views)
什么时候需要创建自己的UIView子类?view是用来控制绘图和触摸事件,所以就是当你需要绘制图形或想要控制触摸事件的时候。我可以继承UIButton吗?(因为我主要需要的就是UIButton那样的东西,只是点击事件的处理会不同。)答:通常情况下的建议是不要继承这些内置类型,它们只被优化为拖出来用,而没有优化过继承和重载。我只是建议而不是要求你们不要去这么做,不是做不到,它们也是对象可以被继承,但只是建你不要这么做。
怎么去绘图呢?你有了view的子类,很简单只要重载这一个方法drawRect。注意:永远不要去调用drawRect,因为drawRect不是让你调用的,系统会去调。那么,怎么告诉系统需要重绘呢?你发送这两个消息setNeedsDisplay和setNeedsDisplayInrect。可以认为初始化的时候设置的是一个点(0,0,0,0),然后晚些时候系统查看所有需要重绘的东西,把他们按顺序排列因为有些东西可能会重叠,然后再非常高效地把需要画的东西绘制出来,这样做有两个好处,一是让系统依据层的情况最优化性能,二是如果你的property有一些setter,当你设置的时候需要重绘,这种情况也被最优化了。所以你所有的setter都会调用self的setNeedsDisplay来重绘。如果有人用了你的view,然后调用了好几个这些setter,只要重绘一次,每个setNeedsDisplay都被一起传过去,然后一次性画出来,而不是每次都重绘,导致不停地在绘图,绘图开销非常大。想一下绘图时会发生什么,最起码会把内存中的一些东西移到另一个位置,移动需要执行很多的指令,尽管图形系统已经是不可思议的快了,但是开销仍然非大。举个例子,我们前面做的计算器,注意到当我们执行一个计算的时候,我们对其入栈。对栈做了一个不可变的副本,又再把它变回可变的完成这样一个过程需要执行的指令和高亮一个点中的按钮比起来根本不算什么。强调一下,我不希望你们调入陷阱里,你看到一个东西效率不高,就拼命去优化它,你这么做不是去考虑时间到底花在哪了,就叫做premature optimization,不要这么做,我不希望看到你加了50行代码优化还花不到1%处理器性能的东西。
写代码的信条就是代码行最优化、最少bug、最容易写、最容易读,这种代码就是你不需要写的代码。代码越少越好,设计数据结构实现方法的时候,代码越少越好。
怎么实现自己的drawRect? 答:用Core Graphics Framework,它的API是C语言的,是非面向对象的。过程:创建一个环境(context),然后创建一些轨迹(paths)比如直线和弧线,再设置他们的字体颜色式样等并描边(strock)或填充(fill)到轨迹里。
绘制文字和轨迹是一回事,都是由细线组成的,系统会为我们做好。绘制图片就是复制二进制码。
一.Context
(1)环境(context)决定了你在哪绘图。context种类有:Screen、OffscreenBitmap、PDF、Printer。所以你创建环境的方法决定了要在哪绘图。
(2)对于普通drawing,当你调用drawRect时进到drawRect后会调用一个C函数,UIKit会给你系统准备绘图的环境。 (系统可能是要打印,当打印的时候你会在drawRect发现打印的内容,可能是要打印个大东西,但是得到的环境的方法是一样的。) 注意,每次调用drawRect环境都是不一样的,所以不要它保存起来,而是每次都去获取新的。
(3)drawRect中有下面的C函数得到当前context,这句代码也几乎是每个drawRect的第一行。
CGContextRef context = UIGraphicsGetCurrentContext();//CGContextRef是 //个cookie,不知道到底是什么,它不是个对象,因为没有*号。调用这行代码就会得到context。
二.Define a Path(怎么去绘图,下面演示的只是原理)
在描边或填充之前,还可以设置图形属性,上面用了UIColor设置颜色,注意在用UIColor的时候不需要指出它的环境,当你在使用对象的时候,事实上你只用到UIColor、UIFont、NSString。这里你使用对象的时候不用指出它的环境,它会假设你用的是当前环境,只要在前面的CGContex表面环境就可以了。设置了描边和填充后,还是什么都没画,调用CGContextDrawPath之后就画上了,其中的kCGPathFillStroke是常量标志,表示描边和填充或仅描边或仅填充。上面的代码既描边又填充,所以看到图上是红色描边,填充是绿色。
其实还可以保存轨迹(path),用完之后还能交给其他环境继续使用。和前面讲的类似,只是用CGPath代替CGContext而已。为什么需要保存轨迹呢?可能你建了个星星的轨迹,然后你需要画100个。
三.Graphics State
(1)UIColor
UIColor 有些类方法比如redColor,会返回很多的标准色,当然UIColor还有很多初始方法来创建一个颜色,比如initWithRGB。还可以setFill和前面的填充一样或者就是set,就是既描边又填充。这就是UIColor的用处,设置描边和填充。
(2)透明度(alpha)
UIView可以设置透明的东西(虽然不常用),透明度0是完全透明,1.0是完全不透明。
UI还可以对整个view设置透明度,所以原先不透明的view,设置alpha为0.5,那整个view就变成半透明了。
事实上,还有另一个方法,设置隐藏属性(property hidden).隐藏(hidden)表示view仍在层级结构中,但是不显示也不响应,点击就好像它不在view里面,但它确实在那。hidden挺常用的,比如在一个小屏幕上,如果一个view暂时用不着,那就先隐藏掉,等又要用到时候再取消隐藏。 you can hide a view completely by setting hidden property:
@property (nonatomic) BOOL hidden; myview.hidden = YES;//view will not be on screen and will not handle events
隐藏的开销比透明少一些,但系统很智能。全透明和隐藏是差不多的。view可以重叠,你可以用透明实现特效,特别是对于动画来说,比如渐进渐出。
注意:透明的开销不小。两层的融合比覆盖的开销要大很多。
(3)Graphics State(图形状态),用的最主要的是颜色,但也可以用一些复杂的比如线宽、图案填充等如下图所示,stanford本门课程不讲。
(4)Graphics State有一点要小心,就是subroutines(子程序)。比如我有了一个环境,然后设置它的图形状态,那么这时候我调用的子程序会怎么样呢?子程序里面也有自己的图形状态,这就和我的设置冲突了,所以对此有个机制,叫push和pop环境。push和pop非常高效,所以不要担心性能。
如上图,如果你有个drawRect用了刚刚的drawGreenCircle我画了些红色东西,再画了绿圆,再接着画红色。 如果画绿圆之后我再画的也变成绿色就非常不好了。所以使用push和pop就是为了分隔开来。
四.Drawing Text
(1)用UILable来draw text
(2)如果自定义的view需要自己draw text
如上图所示,方法是首先要有个字体对象UIFont,然后调用NSString的一个方法去绘制,这应该会使你困惑,因为NSString所在的foundation不是一个UI相关的东西,它的foundation和数组字典什么的意义,不应该有绘图的东西。那么它为什么会出现在这里?因为字串是你想要绘制的东西,你有字串你想要把其中的文字画出来,如果只要发个消息给字串就能画出来不是很好吗。那这到底是怎么工作的呢?答案是UIKit,foundation不同的一个东西,它添加了绘制字串的方法,然后通过obj-c的一个叫范畴(category)的机制加回到NSString里去,你可以想象范畴就像个潘多拉盒子,因为UIKit在为另一个framework里的东西添加内容,我们这里不讲继承什么的,它就是把方法添加过去了,这有点怪怪的。范畴不能滥用,它也可能会导致你添加的方法已经被实现了,这就有点竞争的意思了。本门课程会大范围的使用范畴,因为范畴在它擅长的事情上非常好用。如果用在不该用的地方就很糟,用在该用的地方就很好。
当你要画个文字,只要发个消息给NSString,比如drawAtPoint:WithFont:,所以你知道字体知道坐标,就能画出来了。
五.Drawing Images
(1)用UIImageView来draw images。就相当于用于图片的UILable,拖出来之后加入图片,调整大小等属性。
(2)如果你想要在drawRect里画图片,不通过另一个view。即,Create a UIImage object from a file in your Resources folder,最常用的是直接拖图片到Xcode的resource文件夹,在直接通过图片名调用。还可以通过文件系统获取或者通过网络传过来的二进制数据(raw data),还没学到文件系统,学过NSData。
(3)还可以通过context之类的东西自己创建图片,先begin,画好后从context获取出来,最后end 。 上面讲的三条见下图
所以有很多方法去获得UIImage,一旦获得之后,在你的drawRect里可以用这些方法画出来:
drawAtPoint会按原大小绘图,drawInRect会缩放图片,drawAsPatternInRect会重复绘图以填满指定的矩形区域。 你还可以用PNG或者JPG格式的二进制数据来表示图片.