2010-02-07 2 views
1

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

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

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

+0

Привет @ Оскар, вам удалось добиться успеха в вашем подходе? – Ranjit

ответ

2

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

@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 является то, что вы неправильно управляете этим управлением памятью. Аксессуры защищают вас от этого.

copule других примечательных пунктов:

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

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

2

Некоторые случайные вещи:

Существует утечка памяти в 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: вы можете Simpy это сделать:

CGLayerRef* layerToShow = (CGLayerRef) [[layerArray objectAtIndex: 0] pointerValue]; 
1

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

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

CGPointMake(00, 00) 

... чтобы:

CGPointMake(0.0f, 0.0f) 

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

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

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

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

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

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