[Résolu] dessiner dans une vue avec drawRect: lag

LouLou Membre
décembre 2014 modifié dans API UIKit #1

Salut,


 


j'ai ce code pour dessiner dans une vue, le lineArray représente les points déjà  dessinés par d'anciens "touchs" et pointArray est ce qui est dessiné pendant le touchMoved. 


Après quelques secondes, les lignes "laguent" et ne sont plus dessinées de manière fluide, il semble que la boucle soit trop "lourde" pour afficher les nouvelles lignes en suivant le doigt, sauriez-vous comment éviter ça? (Dessiner de manière fluide même après 2,3 secondes) :



if ([self.lineArray count] > 0){
for ( int j=0; j<self.lineArray.count; j++ ){

NSArray *lArray = [self.lineArray objectAtIndex:j];
CGContextBeginPath(context);

//path
CGPoint startPoint=CGPointFromString([lArray objectAtIndex:0]);
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
for ( int i=1; i<[lArray count]; i++){
CGPoint endPoint = CGPointFromString( [lArray objectAtIndex:i] );
CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
}
//color
NSNumber *num = [self.colorArray objectAtIndex:j];
NSUInteger color = [num intValue];
UIColor *theColor = [self.possibleColor objectAtIndex:color];
CGContextSetStrokeColorWithColor(context, [theColor CGColor] );
CGContextSetLineWidth(context, 3);

CGContextStrokePath(context);
}
}
//pointArray
if ([self.pointArray count] > 0){ //quasiment la meme chose

Merci


Réponses

  • AliGatorAliGator Membre, Modérateur
    décembre 2014 modifié #2
    Pourquoi recréer le CGPath à  chaque fois depuis le début ? Pourquoi ne pas le construire au fur et à  mesure de tes Touch et le garder entre chaque appel ?!
  • Peux-tu me donner un exemple?


     


    En mettant une seule fois beginPath au départ de drawRect, et strokePath à  la fin sans les mettre dans les boucles? Après environ 70 points dans l'array, (ça vient assez rapidement, après 3 sec max) les lignes ne suivent pas les points et font des "triangles", comme si la boucle était encore trop lourde :



    - (void)drawRect:(CGRect)rect {
    //context
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextBeginPath(context);
    CGContextSetLineWidth(context, 10);
    CGContextSetLineJoin(context,kCGLineJoinRound);
    CGContextSetLineCap(context, kCGLineCapRound);

    //only if contains something, drawRect
    if ([self.lineArray count] > 0){
    for ( int j=0; j<self.lineArray.count; j++ ){
    NSArray *lArray = [self.lineArray objectAtIndex:j];
    //path
    CGPoint startPoint=CGPointFromString([lArray objectAtIndex:0]);
    CGContextMoveToPoint(context, startPoint.x, startPoint.y);
    for ( int i=1; i<[lArray count]; i++){
    CGPoint endPoint = CGPointFromString( [lArray objectAtIndex:i] );
    CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
    }
    //color
    NSNumber *num = [self.colorArray objectAtIndex:j];
    NSUInteger color = [num intValue];
    UIColor *theColor = [self.possibleColor objectAtIndex:color];
    CGContextSetStrokeColorWithColor(context, [theColor CGColor] );
    CGContextSetLineWidth(context, 3);
    }
    }
    //pointArray
    //only if contains something, drawRect
    if ([self.pointArray count] > 0){

    for ( int j=0; j<self.pointArray.count; j++ ){
    //path
    CGPoint startPoint=CGPointFromString([self.pointArray objectAtIndex:0]);
    CGContextMoveToPoint(context, startPoint.x, startPoint.y);
    for ( int i=1; i<[self.pointArray count]; i++){
    CGPoint endPoint = CGPointFromString( [self.pointArray objectAtIndex:i] );
    CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
    }
    //color
    UIColor *theColor = [self.possibleColor objectAtIndex:currentColor];
    CGContextSetStrokeColorWithColor(context, [theColor CGColor] );
    CGContextSetLineWidth(context, 10);
    }
    }
    CGContextStrokePath(context);
    }
  • CéroceCéroce Membre, Modérateur
    décembre 2014 modifié #4

    Conserve le CGPath en variable d'instance, c'est ça que veut dire Ali.


  • AliGatorAliGator Membre, Modérateur
    Oui et évite de le reconstruire dans le drawRect.


    Tu le gardes dans une @property, tu le construis au fur et à  mesure de tes touchs... et dans ton drawRect tu ne fais que l'ajouter à  ton Context et appeler "stroke" dessus.


    En fait tu n'as même plus besoin de ton "pointsArray" que tu utilises actuellement : au lieu de garder les points dans une @property, remplir ce tableau à  chaque Touch dans ta vue ou e sais pas quand, et finalement reconstruire le path à  partir de ces points à  chaque fois dans drawRect... autant construire directement le CGPath et lui ajouter des points là  où jusqu'à  présent tu ajoutais tes points à  ton pointsArray.


    La methode drawRect est quand même sensés être relativement rapide, elle ne doit faire que dessiner un truc existant normalement.

    C'est un peu le même principe qu'avec la methode cellForRowAtIndexPath quand tu fais une UITableView, où cette methode est sensée retourner rapidement en tout cas be pas porter des opérations longues type calculs ou download d'images ou autre : tu construits tout cela en amont et dans ces méthodes tu ne fais qu'utiliser les objets (ton bezierrPath en l'occurrence) déjà  construit au préalable.
  • Merci Aligator et Céroce, donc je dois mal m'y prendre, j'ai une erreur :


     



     


    <Error>: void CGPathAddLineToPoint(CGMutablePathRef, const CGAffineTransform *, CGFloat, CGFloat): no current point.



     


    Même en mettant un point par défaut au départ : (dans initwithCoder, la vue vient du storyboard)



    myPath = CGPathCreateMutable();
    CGPathAddLineToPoint(myPath, NULL, 0, 0);

    Et le code :



    - (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextAddPath(context, myPath);
    CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
    CGContextDrawPath(context, kCGPathStroke);
    }

    #pragma mark touches

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

    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    CGPoint currentTouch = [[touches anyObject] locationInView:self];
    CGPathAddLineToPoint(myPath, NULL, currentTouch.x, currentTouch.y);
    [self setNeedsDisplay];
    }

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


    //au depart
    @implementation MyView{
    CGMutablePathRef myPath;
    }

  • AliGatorAliGator Membre, Modérateur
    décembre 2014 modifié #7
    Et il est où ton current point (l'erreur indique clairement que c'est ça qui manque en + !) ? Car quand tu demandes de tracer une ligne avec CGPathAddLineToPoint il trace une ligne entre le point courant (le dernier point où il a été deplacé donc) et le point indiqué en paramètre. Encore faut-il avoir placé le point de départ auparavant, par exemple via CGPathMoveToPoint pour au moins placer le tout premier point (pour les suivants ça partira du point précédent donc ça va)


    Donc le point initial que tu essayes de mettre au début après to CGPathCreateMutable il faut le placer avec un CGPathMoveToPoint, on ne peut pas commencer un path avec comme toute première instruction un CGPathAddLineToPoint car il manque le point initial.


    Et sinon, pourquoi ne pas faire le démarrage du path dans le touchBegan pour démarrer une nouvelle ligne a chaque fois que tu commences à  toucher l'écran et la terminer quand tu relâches ? Parce que sinon là  avec ton code si tu relâches le doigt de l'écran et que tu tapes ailleurs ça va continuer le trait !
  • LouLou Membre
    décembre 2014 modifié #8

    Super merci, ça marche très bien,


     


    EDIT: il fallait alloc-init le previousArray au cas où il est nul, sinon on ajoute un objet à  un objet null. Sujet résolu.


     


    ancien message : alors j'aimerais simplement enregistrer les paths (dans une classe avec sa couleur)  dans le NSUserDefaults, mais le loadData ne renvoie rien, previousArray est (null), je ne comprend pas pourquoi? (Aussi, existe-t-il un moyen d'avoir la même chose qu'un "prettyJson" avec NSUserDefault? J'ai trouvé "dictionaryRepresentation" mais j'ai obtenu des chiffres entre ces crochets "<...>").


     


    Voici le code :




    -(id)initWithCoder:(NSCoder *)aDecoder{
    if ( !(self=[super initWithCoder:aDecoder])) return nil;

    CGContextRef context = UIGraphicsGetCurrentContext();
    NSArray *previousArrays = [SaveData loadDict];
    NSLog(@previousArrays : %@", previousArrays);//(null)

    for ( id object in previousArrays){
    //for ( NSDictionary*dict in previousArrays){
    NSLog(@obj %@", [[object class] description]);

    //...

    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:myPath];//nscoding compliant
    DataForPath *firstPath = [[DataForPath alloc] init];
    firstPath.path = bezierPath;
    firstPath.colorInArray = @(currentColor);
    NSLog(@touchEnded, firstPath : %@", firstPath);
    NSDictionary *dict = @{@firstPath:firstPath};

    [SaveData saveDict:dict];
    }




    @implementation SaveData

    static NSString* kMyData = @data1;

    + (void) saveDict:(NSDictionary*) dict{
    NSLog(@saving data...);

    //retrieve previous data
    NSData *previousData = [[NSUserDefaults standardUserDefaults] objectForKey:kMyData];
    NSMutableArray *previousArray = [[NSKeyedUnarchiver unarchiveObjectWithData:previousData] mutableCopy];
    [previousArray addObject:dict];

    //add new data, and save
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:previousArray];
    [[NSUserDefaults standardUserDefaults] setObject:data forKey:kMyData];

    //NSLog(@%@", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
    }

    +(NSArray*) loadDict {
    NSLog(@loading data...);
    NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:kMyData];
    NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    return array;
    }





    #import <Foundation/Foundation.h>
    @import UIKit;

    @interface DataForPath : NSObject <NSCoding>

    @property (nonatomic, strong) UIBezierPath* path;
    @property (nonatomic, strong) NSNumber *colorInArray;

    @end




    @implementation DataForPath

    - (id)initWithCoder:(NSCoder *)decoder{
    if ( !(self = [super init]) ) return nil;
    self.path = [decoder decodeObjectForKey:@path];
    self.colorInArray = [decoder decodeObjectForKey:@colorInArray];
    return self;
    }

    - (void)encodeWithCoder:(NSCoder *)encoder{
    [encoder encodeObject:self.path forKey:@path];
    [encoder encodeObject:self.colorInArray forKey:@colorInArray];
    }

Connectez-vous ou Inscrivez-vous pour répondre.