iPhone: сбой при рисовании CGLayers хранится в массиве - PullRequest
1 голос
/ 07 февраля 2010

Я пытаюсь создать приложение для рисования с функциями повтора и отмены. Моя идея - нарисовать линии в слое в touchMoved, а затем сохранить слой в touchEnded.

Я не уверен, что рисую на слое правильно, хотя все работает нормально, пока я не очистлю изображение, на котором рисую, и попытаюсь перерисовать слои в массиве.

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [touches anyObject];   
    CGPoint currentPoint = [touch locationInView:self.view];

    UIGraphicsBeginImageContext(self.imageView.frame.size);
    [self.imageView.image drawInRect:self.imageView.frame];

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextRef myContext;

    layerRef = CGLayerCreateWithContext(context, self.imageView.frame.size, NULL);

    if (self.layer == nil) {
        myContext =CGLayerGetContext(layerRef);

        CGContextSetLineCap(myContext, kCGLineCapRound);
        CGContextSetLineWidth(myContext, 5.0);
        CGContextSetLineJoin(myContext,  kCGLineJoinRound);
        CGContextSetRGBStrokeColor(myContext, 1.0, 0.0, 0.0, 1.0);

        CGContextBeginPath(myContext);
        CGContextMoveToPoint(myContext, lastPoint.x, lastPoint.y);
        CGContextAddLineToPoint(myContext, currentPoint.x, currentPoint.y);
        CGContextStrokePath(myContext);

        CGContextDrawLayerAtPoint(context, CGPointMake(00, 00),layerRef); 
        self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();

                UIGraphicsEndImageContext();
        lastPoint = currentPoint;       
    }   
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.layerArray != nil) {
        NSLog(@"Saving layer");
        [self.layerArray addObject:[[NSValue alloc] initWithBytes:layerRef objCType:@encode(CGLayerRef)]];
        CGLayerRelease(layerRef);
    }
    NSLog(@"%d",[layerArray count]);
}

Вот метод, в котором я пытаюсь перерисовать слой. Приложение вылетает при достижении CGContextDrawLayerAtPoint()

- (IBAction)redrawViewButton:(id)sender {
    UIGraphicsBeginImageContext(self.imageView.frame.size);
    [self.imageView.image drawInRect:self.imageView.frame];

    NSValue *val = [layerArray objectAtIndex:0];
    CGLayerRef layerToShow;
    [val getValue:&layerToShow];    

    CGContextRef context = CGLayerGetContext(layerToShow);
    CGContextDrawLayerAtPoint(context, CGPointMake(00, 00),layerToShow);

    self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

Ответы [ 3 ]

2 голосов
/ 07 февраля 2010

Несколько случайных вещей:

В touchedEnded:withEvent: есть утечка памяти, вы добавляете сохраненный объект в self.llayerArray, но никогда не освобождаете его после того, как массив также сохранил его. Попробуйте вместо этого:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.layerArray != nil) {
        NSLog(@"Saving layer");
        [self.layerArray addObject: [NSValue valueWithPointer: layerRef]];
        CGLayerRelease(layerRef);
    }
    NSLog(@"%d",[layerArray count]);
}

A CGLayerRef - указатель. Это означает, что в redrawViewButton: вы можете просто сделать это:

CGLayerRef* layerToShow = (CGLayerRef) [[layerArray objectAtIndex: 0] pointerValue];
2 голосов
/ 07 февраля 2010

Я так понимаю, что layerRef - это ивар, который вы сопоставили с self.layer? Вы, кажется, перемещаетесь между аксессорами и прямым доступом к ivar, что очень запутанно и подвержено ошибкам. Убедитесь, что вы всегда получаете доступ к своим иварам через аксессоры. Это поможет вам избежать проблем с управлением памятью. Вы бы реализовали свойство layer примерно так:

@property (nonatomic, readwrite, retain) CGLayerRef layer;

@synthesize layer = _layer;

- (void)setLayer:(CGLayer)aLayer
{
    CGLayerRetain(aLayer);
    CGLayerRelease(_layer);
    _layer = aLayer;
}

...

CGLayerRef layer = CGLayerCreateWithContext(context, self.imageView.frame.size, NULL);
self.layer = layer;
CGLayerRelease(layer);

Смысл этого в том, чтобы поместить все управление памятью ивара в setLayer:. Самая частая причина сбоев при доступе к ivar - неправильное управление памятью. Аксессоры защищают вас от этого.

Совокупность других заслуживающих внимания очков:

  • Никогда не выпускайте что-либо, не устанавливая его в ноль, если оно остается в контексте. В вашем случае вы выпускаете layerRef, но вы не очищаете ivar. Это означает, что если вы получите touchesEnded: еще раз, прежде чем вы получите еще один touchesMoved:, вы дважды отпустите слой. Это, вероятно, настоящая причина вашей проблемы. Аксессоры защищают вас от этого.

  • Ваши прикосновения перемещены: код кажется очень неправильным. Вы создаете новый слой каждый раз, когда получаете ход. Вы можете получить десятки touchesMoved: за один touchesEnd:. Или вы можете вообще не получить touchesMoved:. Я думаю, что вы хотели поместить этот код в touchesBegan:?

1 голос
/ 07 февраля 2010

Самое простое объяснение состоит в том, что слой или контекст не сформированы должным образом. Вы проверяете оба на ноль перед использованием. IIRC, отладчик может отображать значения для основных графических структур, если вы используете контекстное меню «Печать описания в консоль».

Возможно, не связано, но я бы порекомендовал изменить ...

CGPointMake(00, 00)

... в:

CGPointMake(0.0f, 0.0f)

Просто чтобы убедиться.

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

Отменить и повторить - это должным образом функции модели данных, а не представление или его контроллер. Вместо того, чтобы сохранять результаты пользовательских вводов, то есть чертежей, вы должны сохранять пользовательские вводы и затем рисовать из этих данных.

В этом случае вы сохраняете точки касаний, время / последовательность касаний и все соответствующие операции. Представление и контроллер представления вообще не имеют «памяти». Они просто нарисуют любую модель данных, необходимую для рисования в данный момент. Вы бы реализовали отмену и повтор в модели данных. Для отмены вы должны нарисовать все данные до точки отмены. Для повтора вы рисуете до последних данных.

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

Если вы попытаетесь сделать все это в представлении или в контроллере представления, вы получите огромный шар хрупкого кода.

...