Views: 怎样绘制自定义内容到屏幕上?
一、视图定义
一个view代表一个矩形区域,定义了一个坐标空间。view会绘制这个矩形区域并响应事件。
一个view只有一个父view,可以有0或多个子view,一个view可以选择是否裁剪子view。
UIWindow
UIWindow是位于view层次结构中最顶层的view,一般在一个iOS应用中只有一个UIWindow.
UIWindow是view,不是窗口。
view层次结构一般都是以可视的方式在Xcode中构建,甚至自定义view也是如此。
但是也可以在代码中构建:
-(void)addSubview:(UIView *)aView;
-(void)removeFromSuperview;
在你的MVC中的视图结构中,最顶层(root)的view是 @property view!
即:UIViewController的 @property (strong, nonatomic) UIView * view;
理解这个简单的属性非常重要,比如说当自动旋转发生时,这个view的边界会发生变化。
你可以编程往这个view中增加子view, MVC中的view最终都以这个view作为父view,
即MVC中最顶层的view(root),区别于UIWindow,它是整个iOS 应用的最顶层view。
二. 坐标:View Coordinate
数据类型说明:
CGFloat: 浮点数,根据系统来决定是否是64位的,在图形编程中必须要使用CGFloat.
CGPoint: 有2个CGFloat的C struct:x和y。
CGSize: 有2个CGFloat的C struct: width和height.
CGRect: 包括一个CGPoint origin和一个CGSize size.
坐标:
1.左上角是坐标原点。
2.单位是“点”(points)而不是像素(pixel).
一般情况下你不需要关心你绘制的屏幕一个点有多少个像素
字体和图形会根据高度来自动适配。
但是如果你绘制一些表现很详细的图像,你可能想知道这个。
有一个UIView的属性会告诉你:
@property CGFloat contentScaleFactor; //returns pixels per point on the screen this view is on.
这个属性不是只读的。
3.视图(view)有3个属性与它的位置、大小相关:
@property CGRect bounds; //视图的绘制区域起点和大小
@property CGPoint center; //在父视图坐标空间中,你视图的中心点。
@preperty CGRect frame;
//在父视图坐标空间中,一个能包括你的视图的bounds.size的矩形。
4.使用frame和center来确定视图的位置:
这2个属性是由父视图使用的,绝对不要把它们用在你的子类实现中。
你可能觉得frame.size一定一直等于bounds.size,但是你错了,
因为视图是可以旋转(缩放、平移)的,frame定义旋转、缩放、平移后视图所占矩形的大小。
以上视图中,
view B的bounds = ((0,0), (200, 250))
View B 的frame = ((140, 65), (320,320))
View B center = (300, 225)
三. 创建视图
1.一般情况下使用Xcode创建视图,如果是自定义视图,那么拖拽一个UIView到场景中,
在Identity Inspector中把类属性UIView更换为你的自定义类。
2.怎样在代码中创建视图:alloc然后initWithFrame:(UIView的指定初始化器).
也可以只使用init,这时frame是CGRectZero.
3.代码示例:
CGRect labelRect = CGRectMake(20, 20, 50, 30);
UILabel * label = [[UILable allc] initWithFrame:labelRect];
label.text = @"Hello!";
[self.view addSubview:label];
四. 自定义视图
1.什么情况下需要创建自定义视图:
A.需要自定义绘制图形;
B.需要以特殊的方式处理触摸事件;
2.实现自定义绘制非常简单,只需要创建一个UIView的子类,重写:
-(void)drawRect:(CGRect)aRect;
你可以选择是否需要绘制到aRect外的区域。
3.绝对不要自己调用drawRect!
相应的,使用以下方法,让iOS知道你的视图需要刷新了:
-(void)setNeedDisplay;
-(void)setNeedDisplayInRect:(CGRect);
4.怎样实现drawRect:方法
直接使用Core Graphics framework(C API,非面向对象的).
可以使用面向对象的UIBezierPath类。
5.Core Graphics 基本概念
A.获取一个内容(context)用来把图形绘制到里面去;
B.建立路径(paths);
C.设置颜色、字体、纹理、线宽、线距等;
D.描边或者填充刚刚创建的路径(path).
6.UIBezierPath
可以实现上面所有的功能
五. 内容(上下文? context)
1.内容决定你绘制图形的目标地点,如屏幕、不在屏幕上的Bitmat、PDF、打印机等。
2.一般正常的绘制,UIKit会给你建立当前内容,但是它只在drawRect调用期间有效。
每次调用drawRect都会新建一个内容,所以不要缓存drawRect中当前的图形内容。
3.怎么得到这个神奇的内容:
UIBezierPath绘制到当前内容,所以你不需要为它获取内容;
但是如果你直接使用Core Graphics C 函数,你需要指定内容,
调用以下函数获取当前图形内容:
CGContextRef context = UIGraphicsGetCurrentContext();
六. 定义路径(path)
开始定义路径
UIBezierPath * path = [[UIBezierPath alloc] init];
移动到指定点,增加直线或弧线
[path moveToPoint:CGPointMake(75, 10)];
[path addLineToPoint:CGPointMake(160, 150)];
[path addLineToPoint:CGPointMake(10, 150)];
关闭路径
[path closePath];
路径已经被创建,我们可以描边或者填充它,事实上这个时候还没有开始绘制。
[[UIColor greenColor] setFill];
[[UIColor redColor] setStroke];
[path fill];
[path stroke];
七. 图形状态
1.可以设置绘制状态(属性),如:
path.lineWidth = 2.0; //line width in points(not pixels)
2.也可以绘制圆角矩形、椭圆等。
UIBezierPath * roundedRect
= [[UIBizerPath bezierPathWithRoundRect:(CGRect)bounds
cornerRadius:(CGFloat)radius];
UIBezierPath * oval = [UIBezierPath bezierPathWithOvalInRect:(CGRect)bounds];
[roundedRect stroke];
[oval fill];
3.可以使用贝塞尔路径来裁剪你的绘制
[roundedRect addClip]; //this would clip all drawing to be inside the roundedRect
4.在UIView中使用透明、
UIColor可以使用alpha,这就是在drawRect中使用透明绘制的方式
UIView的backgroundColor属性也可以设置成透明值。
如果你的视图需要部分透明,确保@porperty BOOL opaque为NO。
UIView的@property CGFloat alpha可以使整个view部分透明。
5.为特殊处理定义绘制子函数(subroutine)
如果你想定义一个基础函数来绘制一些事物,又不想跟调用函数的图形状态混淆,
你可以使用保存和恢复内容(context)函数。
-(void)drawGreenCircle:(CGContextRef)ctxt
{
CGContextSaveGSState(ctxt);
[[UIColor greenColor] setFill];
//draw my circle
CGContextRestoreGState(ctxt);
}
-(void)drawRect:(CGRect)aRect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor redColor] setFill];
//do some stuff
[self drawGreenCircle:context];
//do more stuff and expect fill color to be red.
}
八. 绘制文字
1.一般情况下我们使用UILabel作为一个子视图来在我们的视图中绘制文字,
但是有时候我们需要在drawRect:中绘制文字。
2.使用NSAttributedString在drawRect:中绘制文字,
NSAttributedString * text = ...;
[text drawAtPoint:(CGPoint)p]; //NSAttrbutedString instance method added by UIKit.
绘制的时候,文字会占用多大的空间呢?
CGSize textSize = [text size]; //another UIKit NSAttributedString instance method.
你可能会困惑为什么Foundation(一个非UI框架)中会有绘制函数,
这些NSAttributedString方法通过一个叫做"categories"的机制定义在UIKit中。
"Categories"是一种Objective-C方式,用来给现有类增加方法却不需要派生子类。
九. 绘制图片
1.UIImageView就像绘制文字时的UILabel,它可以绘制图片,
但是有时候你也需要在drawRect中绘制图片。
2.从资源(Resources)目录的文件中创建一个UIImage:
UIImage * image = [UIImage imageNamed:@"foo.jpg"];
也可以从文件路径或raw data中创建:
UIImage * image = [[UIImage alloc] initWithContentOfFile:(NSString *)fullPath];
UIImage * image = [[UIImage alloc] initWithData:(NSData *)imageData];
甚至你也可以用CGContext函数绘制一个UIImage:
UiGraphicsBeginContext(CGSize);
// draw with CGContext functions
UIImage * myImage = UIGraphicsGetImageFromCurrentContext();
UIGraphicsEndImageContext();
3.创建完UIImage后,可以把UIImage绘制到当前图形内容(graphics context)中:
UIImage * image = ...;
[image drawAtPoint:(CGPoint)p]; // p is upper left corner of the image
[image drawInRect:(CGRect)r]; // scales the image to fit in r
[image drawAsPatternInRect:(CGRect)patRect]; // tiles the image into patRect
4.另外,你可以从UIImage中得到PNG或JPG格式的数据:
NSData * jpgData = UIImageJPEGRepresentation((UIImage *)myImage, (CGFloat)quality);
NSData * pngData = UIImagePNGRepresentation((UIImage *)myImage);
十. 边框变化时重绘:
1.默认情况下当你的视图边框变化时,不会重绘,
只是相应的拉伸或压缩或移动你的视图中的位图。通常这不是你想要的,
有一个UIView的@property控制这个,可以在Xcode中设置:
@property (nonatomic) UIViewContentMode contentMode;
以下content modes移动你的视图中的位图:
UIViewContentMode{Left, Right, Top, BottomLeft, BottomRigth, TopLeft, TopRight}
以下content modes拉伸或压缩你视图中的位图:
UIViewContentModeScale{ToFill, AspectFill, AspectFit}
以下content mode 调用drawRect函数,边框变化是重绘你视图中的所有元素:
UIViewContentModeRedraw //it is quite often that this is what you want
默认是UIViewContentModeScaleToFill