Рисование поэтапно в UIView (iPhone)

Насколько я понял до сих пор, каждый раз, когда я рисую что-то в drawRect: UIView, весь контекст стирается, а затем перерисовывается.

Поэтому я должен сделать что-то подобное, чтобы нарисовать серию точек:

Метод 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);
}

Это означает, что я должен хранить все мои точки (это прекрасно) и повторно рисовать их все по одному, каждый раз, когда я хочу добавить новый. К сожалению, это дает мое ужасное представление, и я уверен, что есть еще один способ сделать это, более эффективно.

EDIT: Используя код 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 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: В конце концов я объединил один из методов, упомянутых ниже, с перерисовкой только в новом прямоугольнике. Результат: БЫСТРЫЙ МЕТОД:

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

Спасибо всем!

Ответ 1

Если вы фактически изменяете небольшую часть содержимого UIView каждый раз, когда вы рисуете (а остальная часть контента обычно остается той же), вы можете использовать это. Вместо того, чтобы перерисовывать все содержимое UIView каждый раз, вы можете отмечать только те области представления, которые нужно перерисовывать, используя -[UIView setNeedsDisplayInRect:] вместо -[UIView setNeedsDisplay]. Вы также должны убедиться, что графический контент не очищается до рисования, установив view.clearsContextBeforeDrawing = YES;

Конечно, все это также означает, что ваша реализация drawRect: должна уважать параметр rect, который затем должен быть небольшим подразделением вашего полного вида rect (если только что-то не загрязняет весь прямоугольник) и только рисует часть.

Ответ 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;
}

Ответ 3

Если я правильно понимаю вашу проблему, я бы попробовал напрямую рисовать на CGBitmapContext вместо экрана. Затем в drawRect нарисуйте только часть предварительно обработанного растрового изображения, которое необходимо из параметра rect.

Ответ 4

Сколько эллипсов вы собираетесь нарисовать? В общем, Core Graphics должна быстро нарисовать много эллипсов.

Однако вы можете кэшировать свои старые рисунки на изображение (я не знаю, является ли это решение более эффективным):

UIGraphicsBeginImageContext(self.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext(); // ctx is now the image 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

Если вы можете кэшировать рисунок как образ, вы можете воспользоваться поддержкой UIView CoreAnimation. Это будет намного быстрее, чем использование кварца, поскольку Quartz делает свой рисунок в программном обеспечении.

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