在iOS开发中,视图的绘制无处不在,例如使用UILable组件显示文本标签,使用UIImageView组件显示图片视图等。在日常开发中我们使用的大多数视图组件都是基于UIKit框架的,其实关于图像绘制,CoreGraphics是一个更加底层、也更加强大的图形绘制框架。
1、CGPath路径绘制
CoreGraphics核心图形框架相较于UIKit框架更加偏向底层。在OC中,CoreGraphics中的方法都是采用的C语言风格进行编写的,同时他并不支持OC的自动引用计数功能,在使用这个框架时,开发者要手动对内存进行管理。
CGPath可以理解为图形的路径,在OC工程中,他是系统定义的一个内部结构体,开发者不可以直接使用。可以使用CGPathRef和CGMutablePathRef别名作为对CGPath的引用。
下面使用代码来演示自定义绘制的视图,绘制一个闭合的圆角矩形。
- (void)drawRect:(CGRect)rect {
//获取当前绘图上下文
CGContextRef contentextRef = UIGraphicsGetCurrentContext();
CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0);
//创建圆角矩形路径,30-横向的圆角尺寸 10-纵向的圆角尺寸
CGPathRef pathRef = CGPathCreateWithRoundedRect(CGRectMake(center.x - 50, center.y - 50, 50*2, 50*2), 30, 10, nil);
//将路线虚化
CGFloat floats[] = {10,5};
//创建虚线路径-path:虚线的逻辑,phase:从lengths数组的第几部分开始绘制虚线,lengths:表示每段虚线的长度,例如传入的是{10,5}则表示虚线先绘制长度为10的实现,再绘制长度为5的空白,如此循环;count:设置为lengths数组的长度
// CGPathCreateCopyByDashingPath(<#CGPathRef _Nullable path#>, <#const CGAffineTransform * _Nullable transform#>, <#CGFloat phase#>, <#const CGFloat * _Nullable lengths#>, <#size_t count#>)
pathRef = CGPathCreateCopyByDashingPath(pathRef, nil, 0, floats, 2);
//设置绘制颜色
[[UIColor blackColor] setStroke];
//将路径添加到绘图上下文中
CGContextAddPath(contentextRef, pathRef);
//进行绘制
CGContextDrawPath(contentextRef, kCGPathStroke);
//释放内存
CGPathRelease(pathRef);
}
效果图如下:
image.png
CGPath除了上面的简单的绘制方法外,还有如下的更多复杂的方法:
//将路径移动到一个点作为起始
CG_EXTERN void CGPathMoveToPoint(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
CG_AVAILABLE_STARTING(10.2, 2.0);
//将路径移到某个点画出一条线
CG_EXTERN void CGPathAddLineToPoint(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
CG_AVAILABLE_STARTING(10.2, 2.0);
//向路径中添加一段二次贝济埃曲线
/*
cpx:控制点的X坐标
cpy:控制点的Y坐标
*/
CG_EXTERN void CGPathAddQuadCurveToPoint(CGMutablePathRef cg_nullable path,
const CGAffineTransform *__nullable m, CGFloat cpx, CGFloat cpy,
CGFloat x, CGFloat y)
CG_AVAILABLE_STARTING(10.2, 2.0);
//添加一段三次贝济埃曲线
CG_EXTERN void CGPathAddCurveToPoint(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, CGFloat cp1x, CGFloat cp1y,
CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)
CG_AVAILABLE_STARTING(10.2, 2.0);
//闭合路径,在调用这个方法后,路径最后的端点讲和起点闭合
CG_EXTERN void CGPathCloseSubpath(CGMutablePathRef cg_nullable path)
CG_AVAILABLE_STARTING(10.2, 2.0);
//向路径中追加一个矩形
CG_EXTERN void CGPathAddRect(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, CGRect rect)
CG_AVAILABLE_STARTING(10.2, 2.0);
//向路径中追加一组矩形
CG_EXTERN void CGPathAddRects(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, const CGRect * __nullable rects,
size_t count)
CG_AVAILABLE_STARTING(10.2, 2.0);
//向路径中追加一条线
CG_EXTERN void CGPathAddLines(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, const CGPoint * __nullable points,
size_t count)
CG_AVAILABLE_STARTING(10.2, 2.0);
//添加椭圆
CG_EXTERN void CGPathAddEllipseInRect(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, CGRect rect)
CG_AVAILABLE_STARTING(10.4, 2.0);
//向路径中追加一组圆弧
/*
X:圆心x坐标
y:圆心y坐标
radius:弧线半径
startAngle:起始角度
delta:圆弧绘制的长度为弧度制,2π位整个圆
*/
CG_EXTERN void CGPathAddRelativeArc(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable matrix, CGFloat x, CGFloat y,
CGFloat radius, CGFloat startAngle, CGFloat delta)
CG_AVAILABLE_STARTING(10.7, 5.0);
//向路径中追加一组圆弧
/*
X:圆心x坐标
y:圆心y坐标
radius:弧线半径
startAngle:起始角度
endAngle:终止角度
clockwise:是否沿顺时针方向绘制
*/
CG_EXTERN void CGPathAddArc(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m,
CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle,
bool clockwise)
CG_AVAILABLE_STARTING(10.2, 2.0);
//向路径中追加一段圆弧,弧线是以(x1,y1)到(x2,y2)为切线的弧线
CG_EXTERN void CGPathAddArcToPoint(CGMutablePathRef cg_nullable path,
const CGAffineTransform * __nullable m, CGFloat x1, CGFloat y1,
CGFloat x2, CGFloat y2, CGFloat radius)
CG_AVAILABLE_STARTING(10.2, 2.0);
//向路径中追加一段路径
CG_EXTERN void CGPathAddPath(CGMutablePathRef cg_nullable path1,
const CGAffineTransform * __nullable m, CGPathRef cg_nullable path2)
CG_AVAILABLE_STARTING(10.2, 2.0);
例如我们用下面的代码来绘制更具复杂的图形:
- (void)drawRect:(CGRect)rect {
//获取当前绘图上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGPoint center = CGPointMake(rect.size.width/2.0, rect.size.height/2.0);
//创建可变路径
CGMutablePathRef pathRef = CGPathCreateMutable();
//创建起始点
CGPathMoveToPoint(pathRef, nil, center.x, center.y-50);
//将路径移到某个点画出一条线
CGPathAddLineToPoint(pathRef, nil, center.x+100, center.y-20);
// //向路径中添加一段二次贝济埃曲线
CGPathAddQuadCurveToPoint(pathRef, nil, 0, 0, center.x+100, center.y - 100);
// //向路径中追加一组圆弧
CGPathAddRelativeArc(pathRef, nil, 100, 100, 50, 0, M_PI);
// 闭合路径
CGPathCloseSubpath(pathRef);
[[UIColor blackColor] setStroke];
//将路径添加到绘图上下文中
CGContextAddPath(contextRef, pathRef);
//进行绘制
CGContextDrawPath(contextRef, kCGPathStroke);
//释放内存
CGPathRelease(pathRef);
}
效果如下图:
image.png
下面为一些CGPath相关的工具方法:
//判断某个路径是否为空
bool CGPathIsEmpty(CGPathRef cg_nullable path);
//判断某个路径是否为某个矩形
bool CGPathIsRect(CGPathRef cg_nullable path, CGRect * __nullable rect);
//获取某个路径当前绘制的所在的点
CGPoint CGPathGetCurrentPoint(CGPathRef cg_nullable path);
//获取某个路径包含的所有点的尺寸
CGRect CGPathGetBoundingBox(CGPathRef cg_nullable path);
//获取某个路径的尺寸
CGRect CGPathGetPathBoundingBox(CGPathRef cg_nullable path);
//判断路径是否包含某个点
bool CGPathContainsPoint(CGPathRef cg_nullable path,
const CGAffineTransform * __nullable m, CGPoint point, bool eoFill);
2、理解图形上下文
GraphicsContext对于开发者来说是完全透明的,我们不需要关系他的实现,也不需要关心其绘制方式,而只需要将要绘制的内容船体给图形上下文,由图形上下文来讲内容绘制到对应的目标上。这里需要注意的是,绘制的顺序十分重要!如果后绘制的内容和先绘制的内容有位置冲突,则后绘制的内容会覆盖先绘制的内容。
特定的上下文用于将内容绘制到特定的输出源上,在CoreGraphics中提供如下几种图形上下文:
*位图图形上下文:用于将RGB图像、GMYK图像或黑白图像会知道一个位图对象中。
*PDF图形上下文:可以帮助开发者创建PDF文件,将内容绘制进PDF文件中。与位图上下文最大的区别在于其PDF数据可以保存多页数据。
*窗口上下文:用于iOS系统中的窗口绘制。
*图层上下文:用于将内容绘制在Layer图层上。
*打印上下文:当使用Mac打印功能时,此上下文用于将内容绘制在打印输出源上。
3、关于层聚合
在正常情况下,当使用CoreGraphics框架中的方法进行图形绘制时,每一闭合的图形都是一个独立的层,如果在绘制时添加了阴影效果,则可以很明显的看到图形的分层情况:
- (void)drawRect:(CGRect)rect {
float width = rect.size.width/2.0;
CGPoint center = CGPointMake(width, rect.size.height/2.0);
CGSize myShadowOffset = CGSizeMake(10, -20);
//获取上下文
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextSetShadow(myContext, myShadowOffset, 10);
//绘制三个圆
CGContextSetRGBFillColor(myContext, 0, 1, 0, 1);
CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/2, center.y-width/4*3, width, width));
CGContextSetRGBFillColor(myContext, 0, 0, 1, 1);
CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4, center.y-width/3, width, width));
CGContextSetRGBFillColor(myContext, 1, 0, 0, 1);
CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4*3, center.y-width/4, width, width));
}
效果如下图:
image.png
从图中可以发现,所绘制的3个圆并非位于同一层级上。那么如果我们有这样的需求:在园内的阴影不应该存在,只有圆外的阴影才可以有。这样的需求应该是很普遍的。在CoreGraphics中也提供了进行图形聚合绘制的方法:
- (void)drawRect:(CGRect)rect {
float width = rect.size.width/2.0;
CGPoint center = CGPointMake(width, rect.size.height/2.0);
CGSize myShadowOffset = CGSizeMake(10, -20);
//获取上下文
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextSetShadow(myContext, myShadowOffset, 10);
CGContextBeginTransparencyLayer(myContext, NULL);
//开启图形聚合绘制
//后续的绘制代码都将绘制到统一的图层上
//绘制三个圆
CGContextSetRGBFillColor(myContext, 0, 1, 0, 1);
CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/2, center.y-width/4*3, width, width));
CGContextSetRGBFillColor(myContext, 0, 0, 1, 1);
CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4, center.y-width/3, width, width));
CGContextSetRGBFillColor(myContext, 1, 0, 0, 1);
CGContextFillEllipseInRect(myContext, CGRectMake(center.x-width/4*3, center.y-width/4, width, width));
//结束聚合绘制
CGContextEndTransparencyLayer(myContext);
}
效果图如下:
image.png
网友评论