2010-08-02 8 views
6

Ho un'app di disegno in cui mi piacerebbe creare un metodo di annullamento. Il disegno si svolge all'interno del metodo TouchesMoved:Salvataggio di CGContextRef

Sto provando a creare un CGContextRef e lo spingo allo stack OPPURE salvarlo in una proprietà di contesto che può essere ripristinata in seguito, ma non sto avendo fortuna. Qualsiasi consiglio sarebbe grande. Ecco quello che ho ...

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

Poi ho un metodo innescato dal mio pulsante Annulla ...

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

Quando eseguo questo, credo che il mio drawingSurface viene assegnato nil perché cancella tutto nell'immagine.

La mia ipotesi è che non posso usare pop e spingere in questo modo. Ma non riesco a capire come salvare solo il contesto e poi spingerlo nuovamente sul disegnoSuzza. Hmmmm. Qualsiasi aiuto sarebbe ... beh ... utile. Grazie in anticipo -

E, solo per riferimento, ecco cosa sto facendo per disegnare sullo schermo, che funziona benissimo. Questo è dentro la mia 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(); 

risposta

1

Credo che tu stia avvicinando il problema nel modo sbagliato e contesti confuse.

In una modalità immediata API si salva lo 'stato' degli oggetti con push/pop, non la rappresentazione grafica. Lo stato consiste in cose come le larghezze delle linee, i colori e le posizioni. La rappresentazione grafica è il risultato di un'operazione di disegno (una bitmap) e generalmente qualcosa che non si desidera salvare.

Cerca invece di salvare le "informazioni" che utilizzi per creare il disegno.

Il mio suggerimento iniziale sarebbe quello di disaccoppiare la creazione della forma e la pittura. Su OSX puoi usare NSBezierPath, ma per iOS dobbiamo usare una serie di punti.

Per esempio dato questo protocollo:

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

@interface ViewController : UIViewController<DrawSourceProtocol> 
@end 

È possibile implementare queste funzioni:

// 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]; 
} 

Ora sottoclasse la visualizzazione (che io chiamo il mio MyView qui) e realizzare qualcosa di simile:

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

L'unico cavetto qui è che setNeedsDisplay non è molto performante. È preferibile utilizzare setNeedsDisplayInRect :, vedere il mio ultimo post relativo a an efficient way of determining the 'drawn' rect.

Come annullare? L'operazione di annullamento fa semplicemente scattare l'ultimo oggetto dalla matrice allPaths. Questo esercizio ti lascio a :)

Spero che questo aiuti!

+1

Risposta fantastica e ben pensata. Non sono sicuro del motivo per cui OP non ha accettato questo. – Tim