2010-02-07 6 views
1

Je suis en train de créer une application de dessin avec des fonctions de rétablissement et d'annulation. Mon idée est de dessiner des lignes dans un calque dans "touchMoved", puis de sauvegarder le calque dans "touchEnded".iPhone: Crash lors du dessin CGLayers stockés dans Array

Je ne suis pas sûr que je dessine à la couche correcte, tout fonctionne bien, jusqu'à ce que j'efface l'image que je dessine et essaie de redessiner les couches dans le tableau.

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

Voici la méthode que je suis en train de redessiner la couche. Les application se bloque lorsqu'il atteint le la 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

Salut @Oscar, avez-vous réussi votre approche? – Ranjit

Répondre

2

Je suppose que layerRef est un ivar que vous avez mappé à self.layer? Vous semblez vous déplacer entre les accesseurs et l'accès ivar direct, ce qui est très déroutant et source d'erreurs. Assurez-vous de toujours accéder à vos ivars grâce à des accesseurs. Cela ira un long chemin vers l'enregistrement de vos problèmes de gestion de la mémoire. Vous souhaitez mettre en œuvre quelque chose comme de layerproperty ceci:

@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); 

Le but de cela est de mettre tout votre gestion de la mémoire de l'Ivar intérieur de setLayer:. La cause la plus fréquente de plantages sur l'accès ivar est que vous avez mal géré la gestion de la mémoire. Les accesseurs vous protègent de cela.

A copule d'autres points remarquables:

  • Ne lâchez jamais quelque chose sans mettre immédiatement à zéro si elle va rester dans son contexte. Dans votre cas, vous libérez layerRef, mais vous n'effacez pas l'ivar. Cela signifie que si vous obtenez touchEnded: encore une fois avant d'obtenir un autre touchesMoved:, vous allez libérer deux fois le calque. C'est probablement la cause réelle de votre problème. Les accesseurs vous protègent de cela.

  • Votre touchesMoved: le code semble très faux. Vous créez un nouveau calque chaque fois que vous recevez un mouvement. Vous pouvez obtenir des dizaines de touchesMoved: pour un seul touchesEnd:. Ou vous pourriez obtenir aucun touchesMoved: du tout. Je pense que vous vouliez mettre ce code dans touchesBegan:?

2

Certaines choses au hasard:

Il y a une fuite de mémoire dans touchedEnded:withEvent:, vous ajoutez un objet retenu pour self.llayerArray mais jamais le libérer après que le réseau a également conservé son. Essayez ceci à la place:

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

Un CGLayerRef est un pointeur. Cela signifie que vous pouvez redrawViewButton: simpy faire:

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

explication la plus simple est que soit la couche ou le contexte n'est pas formé correctement. Vous testez les deux pour zéro avant d'utiliser.IIRC, le débogueur peut afficher des valeurs pour les structures Core Graphic si vous utilisez le menu contextuel "Imprimer la description de la console".

Probablement aucun rapport mais je recommanderais de changer ...

CGPointMake(00, 00) 

... à:

CGPointMake(0.0f, 0.0f) 

Juste pour vous assurer.

Dans tous les cas, je pense que vous devez abandonner cette méthode d'implémentation d'annulation. Il semble simple et soigné mais en réalité il va devenir lourd, complexe et peu fiable.

Undo et Redo sont correctement fonctions du modèle de données et non pas une vue ou il est contrôleur. Au lieu de sauvegarder les résultats des entrées de l'utilisateur, c'est-à-dire les dessins, vous devriez sauvegarder les entrées des utilisateurs et ensuite tirer de ces données.

Dans ce cas, vous stockez les points de la touche, le temps/séquences de touches et quelle que soit les opérations pertinentes. La vue et le contrôleur de vue n'auraient aucune "mémoire" du tout. Ils dessineraient simplement ce que le modèle de données indiqué devait dessiner pour le moment. Vous implémenteriez annuler et rétablir dans le modèle de données. Pour annuler vous dessiner toutes les données jusqu'au point d'annulation. Pour refaire vous dessinez les dernières données. Core Data est très bon pour cela, bien que la courbe d'apprentissage est raide. Il implémentera annuler et refaire pour vous automatiquement. Si votre modèle de données est relativement simple, vous pouvez l'implémenter avec juste un tableau qui stocke une classe personnalisée conçue pour stocker les données pour un seul événement de dessin.

Si vous essayez de le faire tous dans la vue ou le contrôleur de vue, vous vous retrouvez avec une boule de monstre de code fragile.