在UIView(iPhone)中逐步绘制

时间:2022-11-26 14:58:12

As far as I have understood so far, every time I draw something in the drawRect: of a UIView, the whole context is erased and then redrawn.

据我所知,到目前为止,每次我在drawRect:UIView中绘制一些内容时,整个上下文都会被删除然后重新绘制。

So I have to do something like this to draw a series of dots:

所以我必须做这样的事情来绘制一系列点:

Method A: drawing everything on every call

方法A:在每次通话时绘制所有内容

- (void)drawRect:(CGRect)rect { 

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextDrawImage(context, self.bounds, maskRef);      //draw the mask
    CGContextClipToMask(context, self.bounds, maskRef);     //respect alpha mask
    CGContextSetBlendMode(context, kCGBlendModeColorBurn);  //set blending mode

    for (Drop *drop in myPoints) {
        CGContextAddEllipseInRect(context, CGRectMake(drop.point.x - drop.size/2, drop.point.y - drop.size/2, drop.size, drop.size));
    }

    CGContextSetRGBFillColor(context, 0.5, 0.0, 0.0, 0.8);
    CGContextFillPath(context);
}

Which means, I have to store all my Dots (that's fine) and re-draw them all, one by one, every time I want to add a new one. Unfortunately this gives my terrible performance and I am sure there is some other way of doing this, more efficiently.

这意味着,每次我想添加一个新点时,我必须逐个存储我的所有点(这很好)并重新绘制它们。不幸的是,这给了我糟糕的表现,我相信还有其他方法可以做到这一点,更有效率。

EDIT: Using MrMage's code I did the following, which unfortunately is just as slow and the color blending doesn't work. Any other method I could try?

编辑:使用MrMage的代码,我做了以下,不幸的是,它同样慢,颜色混合不起作用。我可以尝试其他任何方法吗?

Method B: saving the previous draws in a UIImage and only drawing the new stuff and this image

方法B:将先前的绘制保存在UIImage中,仅绘制新的东西和此图像

- (void)drawRect:(CGRect)rect
{
    //draw on top of the previous stuff
    UIGraphicsBeginImageContext(self.frame.size);
    CGContextRef ctx = UIGraphicsGetCurrentContext(); // ctx is now the image's context
    [cachedImage drawAtPoint:CGPointZero];
    if ([myPoints count] > 0)
    {
        Drop *drop = [myPoints objectAtIndex:[myPoints count]-1];
        CGContextClipToMask(ctx, self.bounds, maskRef);         //respect alpha mask
        CGContextAddEllipseInRect(ctx, CGRectMake(drop.point.x - drop.dropSize/2, drop.point.y - drop.dropSize/2, drop.dropSize, drop.dropSize));
        CGContextSetRGBFillColor(ctx, 0.5, 0.0, 0.0, 1.0);
        CGContextFillPath(ctx);
    }
    [cachedImage release];
    cachedImage = [UIGraphicsGetImageFromCurrentImageContext() retain];
    UIGraphicsEndImageContext();

    //draw on the current context   
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, self.bounds, maskRef);          //draw the mask
    CGContextSetBlendMode(context, kCGBlendModeColorBurn);      //set blending mode
    [cachedImage drawAtPoint:CGPointZero];                      //draw the cached image
}

EDIT: After all I combined one of the methods mention below with redrawing only in the new rect. The result is: FAST METHOD:

编辑:毕竟我结合下面提到的方法之一与仅在新的rect重绘。结果是:快速方法:

- (void)addDotAt:(CGPoint)point
{
    if ([myPoints count] < kMaxPoints) {
        Drop *drop = [[[Drop alloc] init] autorelease];
        drop.point = point;
        [myPoints addObject:drop];
        [self setNeedsDisplayInRect:CGRectMake(drop.point.x - drop.dropSize/2, drop.point.y - drop.dropSize/2, drop.dropSize, drop.dropSize)];      //redraw
    }
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextDrawImage(context, self.bounds, maskRef);                                              //draw the mask
    CGContextClipToMask(context, self.bounds, maskRef);                                             //respect alpha mask
    CGContextSetBlendMode(context, kCGBlendModeColorBurn);                                          //set blending mode

    if ([myPoints count] > 0)
    {
        Drop *drop = [myPoints objectAtIndex:[myPoints count]-1];
        CGPathAddEllipseInRect (dotsPath, NULL, CGRectMake(drop.point.x - drop.dropSize/2, drop.point.y - drop.dropSize/2, drop.dropSize, drop.dropSize));
    }
    CGContextAddPath(context, dotsPath);

    CGContextSetRGBFillColor(context, 0.5, 0.0, 0.0, 1.0);
    CGContextFillPath(context);
}

Thanks everyone!

感谢大家!

5 个解决方案

#1


6  

If you are only actually changing a small portion of the UIView's content every time you draw (and the rest of the content generally stays the same), you can use this. Rather than redraw all the content of the UIView every single time, you can mark only the areas of the view that need redrawing using -[UIView setNeedsDisplayInRect:] instead of -[UIView setNeedsDisplay]. You also need to make sure that the graphics content is not cleared before drawing by setting view.clearsContextBeforeDrawing = YES;

如果您每次绘制时实际上只更改了UIView内容的一小部分(并且其余内容通常保持不变),您可以使用它。不是每次都重绘UIView的所有内容,而是只使用 - [UIView setNeedsDisplayInRect:]而不是 - [UIView setNeedsDisplay]来标记需要重绘的视图区域。您还需要确保在绘制之前未通过设置view.clearsContextBeforeDrawing = YES清除图形内容;

Of course, all this also means that your drawRect: implementation needs to respect the rect parameter, which should then be a small subsection of your full view's rect (unless something else dirtied the entire rect), and only draw in that portion.

当然,所有这些也意味着你的drawRect:实现需要尊重rect参数,然后应该是你的完整视图的rect的一小部分(除非其他东西弄脏整个矩形),并且只绘制那部分。

#2


2  

You can save your CGPath as a member of your class. And use that in the draw method, you will only need to create the path when the dots change but not every time the view is redraw, if the dots are incremental, just keep adding the ellipses to the path. In the drawRect method you will only need to add the path

您可以将CGPath保存为班级成员。并且在绘制方法中使用它,您只需要在点改变时创建路径,但不是每次重绘视图时都创建路径,如果点是增量的,只需继续将椭圆添加到路径中。在drawRect方法中,您只需要添加路径

CGContextAddPath(context,dotsPath);

-(CGMutablePathRef)createPath
{
    CGMutablePathRef dotsPath =  CGPathCreateMutable();

    for (Drop *drop in myPoints) {
        CGPathAddEllipseInRect ( dotsPath,NULL,
            CGRectMake(drop.point.x - drop.size/2, drop.point.y - drop.size/2, drop.size, drop.size));
    }

return dotsPath;
}

#3


1  

If I understand your problem correctly, I would try drawing to a CGBitmapContext instead of the screen directly. Then in the drawRect, draw only the portion of the pre-rendered bitmap that is necessary from the rect parameter.

如果我正确理解你的问题,我会尝试直接绘制到CGBitmapContext而不是屏幕。然后在drawRect中,仅绘制rect参数所需的预渲染位图部分。

#4


0  

How many ellipses are you going to draw? In general, Core Graphics should be able to draw a lot of ellipses quickly.

你要画多少个椭圆?通常,Core Graphics应该能够快速绘制很多省略号。

You could, however, cache your old drawings to an image (I don't know if this solution is more performant, however):

但是,您可以将旧图形缓存到图像中(但我不知道此解决方案是否更具性能):

UIGraphicsBeginImageContext(self.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext(); // ctx is now the image's context

[cachedImage drawAtPoint:CGPointZero];
// only plot new ellipses here...

[cachedImage release];
cachedImage = [UIGraphicsGetImageFromCurrentImageContext() retain];
UIGraphicsEndImageContext();

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextDrawImage(context, self.bounds, maskRef);          //draw the mask
CGContextClipToMask(context, self.bounds, maskRef);         //respect alpha mask
CGContextSetBlendMode(context, kCGBlendModeColorBurn);      //set blending mode

[cachedImage drawAtPoint:CGPointZero];

#5


0  

If you are able to cache the drawing as an image, you can take advantage of UIView's CoreAnimation backing. This will be much faster than using Quartz, as Quartz does its drawing in software.

如果您能够将图形缓存为图像,则可以利用UIView的CoreAnimation支持。这将比使用Quartz快得多,因为Quartz在软件中绘图。

- (CGImageRef)cachedImage {
    /// Draw to an image, return that
}
- (void)refreshCache {
    myView.layer.contents = [self cachedImage];
}
- (void)actionThatChangesWhatNeedsToBeDrawn {
    [self refreshCache];
}

#1


6  

If you are only actually changing a small portion of the UIView's content every time you draw (and the rest of the content generally stays the same), you can use this. Rather than redraw all the content of the UIView every single time, you can mark only the areas of the view that need redrawing using -[UIView setNeedsDisplayInRect:] instead of -[UIView setNeedsDisplay]. You also need to make sure that the graphics content is not cleared before drawing by setting view.clearsContextBeforeDrawing = YES;

如果您每次绘制时实际上只更改了UIView内容的一小部分(并且其余内容通常保持不变),您可以使用它。不是每次都重绘UIView的所有内容,而是只使用 - [UIView setNeedsDisplayInRect:]而不是 - [UIView setNeedsDisplay]来标记需要重绘的视图区域。您还需要确保在绘制之前未通过设置view.clearsContextBeforeDrawing = YES清除图形内容;

Of course, all this also means that your drawRect: implementation needs to respect the rect parameter, which should then be a small subsection of your full view's rect (unless something else dirtied the entire rect), and only draw in that portion.

当然,所有这些也意味着你的drawRect:实现需要尊重rect参数,然后应该是你的完整视图的rect的一小部分(除非其他东西弄脏整个矩形),并且只绘制那部分。

#2


2  

You can save your CGPath as a member of your class. And use that in the draw method, you will only need to create the path when the dots change but not every time the view is redraw, if the dots are incremental, just keep adding the ellipses to the path. In the drawRect method you will only need to add the path

您可以将CGPath保存为班级成员。并且在绘制方法中使用它,您只需要在点改变时创建路径,但不是每次重绘视图时都创建路径,如果点是增量的,只需继续将椭圆添加到路径中。在drawRect方法中,您只需要添加路径

CGContextAddPath(context,dotsPath);

-(CGMutablePathRef)createPath
{
    CGMutablePathRef dotsPath =  CGPathCreateMutable();

    for (Drop *drop in myPoints) {
        CGPathAddEllipseInRect ( dotsPath,NULL,
            CGRectMake(drop.point.x - drop.size/2, drop.point.y - drop.size/2, drop.size, drop.size));
    }

return dotsPath;
}

#3


1  

If I understand your problem correctly, I would try drawing to a CGBitmapContext instead of the screen directly. Then in the drawRect, draw only the portion of the pre-rendered bitmap that is necessary from the rect parameter.

如果我正确理解你的问题,我会尝试直接绘制到CGBitmapContext而不是屏幕。然后在drawRect中,仅绘制rect参数所需的预渲染位图部分。

#4


0  

How many ellipses are you going to draw? In general, Core Graphics should be able to draw a lot of ellipses quickly.

你要画多少个椭圆?通常,Core Graphics应该能够快速绘制很多省略号。

You could, however, cache your old drawings to an image (I don't know if this solution is more performant, however):

但是,您可以将旧图形缓存到图像中(但我不知道此解决方案是否更具性能):

UIGraphicsBeginImageContext(self.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext(); // ctx is now the image's context

[cachedImage drawAtPoint:CGPointZero];
// only plot new ellipses here...

[cachedImage release];
cachedImage = [UIGraphicsGetImageFromCurrentImageContext() retain];
UIGraphicsEndImageContext();

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextDrawImage(context, self.bounds, maskRef);          //draw the mask
CGContextClipToMask(context, self.bounds, maskRef);         //respect alpha mask
CGContextSetBlendMode(context, kCGBlendModeColorBurn);      //set blending mode

[cachedImage drawAtPoint:CGPointZero];

#5


0  

If you are able to cache the drawing as an image, you can take advantage of UIView's CoreAnimation backing. This will be much faster than using Quartz, as Quartz does its drawing in software.

如果您能够将图形缓存为图像,则可以利用UIView的CoreAnimation支持。这将比使用Quartz快得多,因为Quartz在软件中绘图。

- (CGImageRef)cachedImage {
    /// Draw to an image, return that
}
- (void)refreshCache {
    myView.layer.contents = [self cachedImage];
}
- (void)actionThatChangesWhatNeedsToBeDrawn {
    [self refreshCache];
}