在UIView(iPhone)中逐步绘制

11

据我目前了解,每当我在UIView的drawRect:方法中绘制任何内容时,整个上下文都会被擦除然后重新绘制。

因此,我必须像这样做才能绘制一系列点:

方法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);
}
这意味着我必须存储所有的点(这是可以接受的),并每次想添加一个新点时重新绘制它们。不幸的是,这会导致我的性能非常差,我相信有其他更有效的方法来完成这个任务。
编辑:使用MrMage的代码,我尝试了以下方法,但速度仍然很慢,颜色混合也无效。有没有其他方法可以尝试? 方法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
}

编辑: 最终,我将下面提到的方法之一与仅在新矩形中重绘相结合。结果是: 快速方法:

- (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);
}

谢谢大家!

5个回答

6
如果您每次绘制时只更改UIView的一小部分内容(而其余内容通常保持不变),则可以使用此方法。与每次重新绘制UIView的所有内容相比,您可以使用-[UIView setNeedsDisplayInRect:]标记仅需要重绘的视图区域,而不是-[UIView setNeedsDisplay]。您还需要确保在绘制之前不清除图形内容,方法是设置view.clearsContextBeforeDrawing = YES; 当然,这也意味着您的drawRect:实现需要尊重矩形参数,该参数应该是完整视图矩形的一个小子集(除非其他内容污染了整个矩形),并且仅在该部分绘制。

如果“*view.clearsContextBeforeDrawing = NO;*”按照文档所说的那样工作,所有的绘图都应该是增量的,对吗?但事实并非如此... - Dimitris
1
除非您确保drawRect:实现仅绘制更新的部分,否则您的绘图不是增量式的。标记“更新部分”(脏部分)的标准方法是使用setNeedsDisplayInRect:。如果您设置clearsContentBeforeDrawing = NO,然后仍然使用内容填充整个上下文,则性能收益很小。 - Bryan Henry

2

您可以将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;
}

刚刚也试过了。可能比其他方法快一点,但仍然相当慢。我将尝试将其与drawInRect:方法结合使用,看看是否可以解决问题。谢谢。 - Dimitris
Objective-C类没有“成员”,它们有实例变量或“ivars”。 - NSResponder

1
如果我正确理解了您的问题,我建议尝试先绘制到一个CGBitmapContext而不是直接绘制到屏幕上。然后在drawRect中,只绘制从rect参数中所需的预渲染位图部分。

我还没有尝试过这样的事情。我可能会试一试。如果您有任何展示如何完成此操作的代码,我将不胜感激。 - Dimitris
++ 这是我选择的方法。我只需“blt”整个内容即可。它消除了闪烁,并产生了即时绘制的错觉。 - Mike Dunlavey

0
你打算画多少个椭圆?通常情况下,Core Graphics 应该能够快速绘制大量的椭圆。
然而,你可以将以前的绘图缓存到图片中(不过我不知道这种解决方案是否更加高效):
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];

我不会画很多,比如说100个左右。 但是由于额外的工作(在每个drawRect:上绘制图像并对其上的椭圆进行遮罩),即使在3GS上也会变得非常缓慢。 - Dimitris
你也可以尝试在绘制椭圆形之后进行遮罩(例如,先将内容绘制到缓冲区,然后以遮罩方式将其绘制到视图中)。 - MrMage
在上面的代码中,你说“ctx”,但是实际上是指“context”,对吧?我现在正在尝试你的方法,看看是否有所不同。 - Dimitris
不,你的代码不能直接工作。我会用UIImage稍微调整一下,来存储我的上下文内容。 - Dimitris
我编辑了我的原始问题,包括使用您的建议进行的实现。不幸的是,它仍然很慢... - Dimitris

0

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接