2010-08-02 21 views
6

Tengo una aplicación de dibujo en la que me gustaría crear un método de deshacer. El sorteo tiene lugar dentro del método TouchesMoved:Guardando CGContextRef

Estoy tratando de crear un CGContextRef y llevarlo a la pila O guardarlo en una propiedad de contexto que se puede restaurar más tarde, pero no estoy teniendo suerte. Cualquier consejo sería genial. Aquí es lo que tengo ...

UIImageView  *drawingSurface; 
CGContextRef  undoContext; 


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 
UIGraphicsBeginImageContext(self.view.frame.size); 
CGContextRef context = UIGraphicsGetCurrentContext(); 
[drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; 
UIGraphicsPushContext(context); 

     // also tried but cant figure how to restore it 
     undoContext = context; 

UIGraphicsEndImageContext(); 
} 

Luego he un método provocada por mi botón de deshacer ...

- (IBAction)restoreUndoImage { 
UIGraphicsBeginImageContext(self.view.frame.size); 
UIGraphicsPopContext(); 
drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext(); 
} 

Cuando ejecuto esto, creo que mi drawingSurface se está asignando nula porque simplemente borra todo en la imagen.

Supongo que no puedo usar pop y presionar de esta manera. Pero parece que no puedo imaginar cómo guardar el contexto y luego volver a presionarlo en la superficie de dibujo. Hmmmm Cualquier ayuda sería ... bueno ... útil. Gracias de antemano -

Y, solo como referencia, esto es lo que estoy haciendo para dibujar en la pantalla, que está funcionando muy bien. Esto está dentro de mis TouchesMoved:

UIGraphicsBeginImageContext(self.view.frame.size); 
CGContextRef context = UIGraphicsGetCurrentContext(); 
[drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; 

CGContextSetLineCap(context, kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound 
CGContextSetLineWidth(context, self.brush.size); // for size 

CGContextSetStrokeColorWithColor (context,[currentColor CGColor]); 

CGContextBeginPath(context); 
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y); 
CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y); 
CGContextStrokePath(context); 
drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext(); 

Respuesta

1

Creo que estás abordando el problema de la manera incorrecta y en contextos confusos.

En una API de modo inmediato se guarda el 'estado' de los objetos con push/pop, no la representación gráfica. El estado consiste en cosas como anchos de línea, colores y posiciones. La representación gráfica es el resultado de una operación de pintura (un mapa de bits) y generalmente algo que no desea guardar.

En su lugar, intente guardar la "información" que utiliza para crear el dibujo.

Mi sugerencia inicial sería desacoplar la creación de su forma y la pintura. En OSX puedes usar NSBezierPath, pero para iOS tenemos que usar una matriz de puntos.

Por ejemplo dado este protocolo:

// ViewController.h 
@protocol DrawSourceProtocol <NSObject> 
- (NSArray*)pathsToDraw; 
@end 

@interface ViewController : UIViewController<DrawSourceProtocol> 
@end 

que se pueden implementar estas funciones:

// ViewController.m 
@interface ViewController() { 
    NSMutableArray *currentPath; 
    NSMutableArray *allPaths; 
    MyView *view_; 
} 
@end 

... 

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    currentPath = [[NSMutableArray alloc] init]; 
    allPaths = [[NSMutableArray alloc] init];  
    view_ = (MyView*)self.view; 
    view_.delegate = self; 
} 

- (NSArray*)pathsToDraw { 
    // Return the currently draw path too 
    if (currentPath && currentPath.count) { 
    NSMutableArray *allPathsPlusCurrent = [[NSMutableArray alloc] initWithArray:allPaths]; 
    [allPathsPlusCurrent addObject:currentPath]; 
    return allPathsPlusCurrent; 
    } 
    return allPaths; 
} 

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 
    currentPath = [[NSMutableArray alloc] init]; 
} 

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 
    // When a touch ends, save the current path 
    [allPaths addObject:currentPath]; 
    currentPath = [[NSMutableArray alloc] init]; 
    [view_ setNeedsDisplay];  
} 

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 
    UITouch *touch = [touches anyObject]; 
    CGPoint currentPoint = [touch locationInView:self.view]; 

    // We store the point with the help of NSValue 
    [currentPath addObject:[NSValue valueWithCGPoint:currentPoint]]; 

    // Update the view 
    [view_ setNeedsDisplay]; 
} 

Ahora subclase su punto de vista (que llamo mío MyView aquí) y poner en práctica algo como esto:

// MyView.h 
#import "ViewController.h" 

@protocol DrawSourceProtocol; 

@interface MyView : UIView { 
    __weak id<DrawSourceProtocol> delegate_; 
} 
@property (weak) id<DrawSourceProtocol> delegate; 
@end 

// MyView.m 

@synthesize delegate = delegate_; 

... 

- (void)drawRect:(CGRect)rect { 
    NSLog(@"Drawing!"); 

    // Setup a context 
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor); 
    CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0); 
    CGContextSetLineWidth(context, 2.0); 

    // Get the paths 
    NSArray *paths = [delegate_ pathsToDraw]; 

    for (NSArray *aPath in paths) { 
    BOOL firstPoint = TRUE; 
    for (NSValue *pointValue in aPath) { 
     CGPoint point = [pointValue CGPointValue]; 

     // Always move to the first point 
     if (firstPoint) { 
     CGContextMoveToPoint(context, point.x, point.y); 
     firstPoint = FALSE; 
     continue; 
     } 

     // Draw a point 
     CGContextAddLineToPoint(context, point.x, point.y); 
    } 
    } 

    // Stroke! 
    CGContextStrokePath(context); 
} 

El único cavet aquí es que setNeedsDisplay no es muy eficiente. Es mejor usar setNeedsDisplayInRect :, ver mi última publicación con respecto a an efficient way of determining the 'drawn' rect.

¿En cuanto a deshacer? Su operación de deshacer es simplemente mostrar el último objeto de la matriz allPaths. Este ejercicio lo dejo a usted :)

Espero que esto ayude!

+1

Respuesta fantástica y bien pensada. No estoy seguro de por qué OP no aceptó esto. – Tim