我正在使用绘图应用程序,我使用CGlayers进行绘图。在触摸结束时,我从图层中取出图像并将其存储在数组中,我用它来撤消操作。
我的触摸结束了功能
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Touches ended");
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawLayerInRect(context, self.bounds, self.drawingLayer);
m_curImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[m_undoArray addObject: m_curImage];
}
我的绘图视图根据用户需求动态扩展,因此假设用户可以绘制say,一条drawView大小为200 * 200的行,然后将其展开为200 * 300并再绘制一行,然后将其展开为200 * 300并绘制还有一行。
以下是该应用的图片
所以现在我在UndoArray中有3个不同大小的图像。
每当我增加/减少画布大小时。我写了这段代码
在drawingView的增加和减少时,我正在写这个函数
(void)increaseDecreaseDrawingView
{
self.currentDrawingLayer = nil;
if(self.permanentDrawingLayer)
{
rectSize = self.bounds;
NSLog(@"Size%@", NSStringFromCGRect(self.bounds));
CGContextRef context = UIGraphicsGetCurrentContext();
//self.newDrawingLayer = CGLayerCreateWithContext(context, self.bounds.size, NULL);
CGFloat scale = self.contentScaleFactor;
CGRect bounds = CGRectMake(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale);
CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL);
CGContextRef layerContext = CGLayerGetContext(layer);
CGContextScaleCTM(layerContext, scale, scale);
self.newDrawingLayer = layer;
CGContextDrawLayerInRect(layerContext, self.bounds, self.permanentDrawingLayer );
self.permanentDrawingLayer = nil;
}
为了做撤消,我已经编写了这段代码
- (void)Undo
{
//Destroy the layer and create it once again with the image you get from undoArray.
self.currentDrawingLayer = Nil;
CGContextRef layerContext1 = CGLayerGetContext(self.permanentDrawingLayer );
CGContextClearRect(layerContext1, self.bounds);
CGContextRef context = UIGraphicsGetCurrentContext();
for(int i =0; i<[m_rectArrayUndo count];i++)
{
CGRect rect = [[m_rectArrayUndo objectAtIndex:i]CGRectValue];
CGLayerRef undoLayer = CGLayerCreateWithContext(context, rect.size, NULL);
CGContextRef layerContext = CGLayerGetContext(undoLayer );
CGContextTranslateCTM(layerContext, 0.0, rect.size.height);
CGContextScaleCTM(layerContext, 1.0, -1.0);
CGRect imageFrame;
NSDictionary *lineInfo = [m_undoArray objectAtIndex:i];
m_curImage = [lineInfo valueForKey:@"IMAGE"];
imageFrame = CGRectMake(0 ,0,m_curImage.size.width,m_curImage.size.height);
CGContextDrawImage(layerContext, imageFrame, m_curImage.CGImage);
CGContextDrawLayerInRect(context, rect, undoLayer );
CGContextDrawLayerInRect(layerContext1, rect, undoLayer);
}
}
在我的drawRect函数中,我编写了这段代码
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();//Get a reference to current context(The context to draw)
if(self.currentDrawingLayer == nil)
{
CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL);
self.currentDrawingLayer = layer;
}
if(self.permanentDrawingLayer == nil)
{
CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL);
self.permanentDrawingLayer = layer;
}
if(self.newDrawingLayer == nil)
{
CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL);
self.newDrawingLayer = layer;
}
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
CGContextRef layerContext = CGLayerGetContext(self.currentDrawingLayer);
CGContextSetLineCap(layerContext, kCGLineCapRound);
CGContextSetBlendMode(layerContext, kCGBlendModeNormal);
CGContextSetLineJoin(layerContext, kCGLineJoinRound);
CGContextSetLineWidth(layerContext, self.lineWidth);
CGContextSetStrokeColorWithColor(layerContext, self.lineColor.CGColor);
CGContextSetShouldAntialias(layerContext, YES);
CGContextSetAllowsAntialiasing(layerContext, YES);
CGContextSetAlpha(layerContext, self.lineAlpha);
CGContextSetFlatness(layerContext, 1.0f);
CGContextBeginPath(layerContext);
CGContextMoveToPoint(layerContext, mid1.x, mid1.y);//Position the current point
CGContextAddQuadCurveToPoint(layerContext, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGContextStrokePath(layerContext);//paints(fills) the line along the current path.
CGContextDrawLayerInRect(context, self.bounds, self.newDrawingLayer);
CGContextDrawLayerInRect(context, self.bounds, self.permanentDrawingLayer);
CGContextDrawLayerInRect(context, self.bounds, self.currentDrawingLayer);
[super drawRect:rect];
}
我很怀疑
这是正确的方法吗?或者是他们更好的方法。
这里发生的事情是,我的撤消数组中的图像不尊重rects,而是在新图层的任意位置绘制。
所以我想知道我们如何正确地绘制它们,以便在特定位置的CGlayers上正确绘制图像。
答案 0 :(得分:4)
首先,由于您正在使用图层,因此我建议您放弃drawRect:
并使用CALayer
转换。
其次,在我看来,实现undo-redo操作的最佳方法始终是基于命令的。作为一个非常简单的示例,您可以为每个命令创建单独的方法:
- (void)scaleLayerBy:(CGFloat)scale;
- (void)moveLayerByX:(CGFloat)x Y:(CGFloat)y;
// etc
然后每次用户执行操作时,您都会添加NSMutableArray
操作ID和参数:
[self.actionHistory addObject:@{ @"action": @"move", @"args": @[@10.0f, @20.0f] }];
相反,如果用户调用 undo ,则删除该数组中的最后一个对象。
然后,当您需要重新加载显示时,只需重新评估数组中的所有命令。
[self resetLayers]; // reset CALayers to their initial state
for (NSDictionary *command in self.actionHistory) {
NSArray *arguments = command[@"args"];
if ([command[@"action"] isEqualToString:@"move"]) {
[self moveLayerByX:[arguments[0] floatValue] Y:[arguments[1] floatValue]];
}
// else if other commands
}
答案 1 :(得分:2)
每个触摸事件的图像对象是一个坏主意恕我直言,你正在撕裂ram。为什么不保留一系列触摸点并动态绘制?很容易从该数组中删除最后几个元素以进行廉价的撤消操作
//// 2014年1月14日// //编辑以包含示例//
这里是一个快速绘图视图示例。 有三个mutableArrays,_touches,用于所有以前的绘图,_ currentTouch,它是当前绘图,只包含触摸事件期间的数据(触摸开始和触摸结束之间)..和一个重做数组,通过撤消删除数据复制而不是删除它(你当然可以做)
享受:)
//
// JEFdrawingViewExample.m
// Created by Jef Long on 14/01/2014.
// Copyright (c) 2014 Jef Long / Dragon Ranch. All rights reserved.
//
#import "JEFdrawingViewExample.h"
///don't worry, the header is empty :)
/// this is a subclass of UIView
@interface JEFdrawingViewExample()
-(UIColor *)colourForLineAtIndex:(int)lineIndex;
//swaps the coulour for each line
-(void)undo;
-(void)redo;
@end;
@implementation JEFdrawingViewExample
{
//iVars
NSMutableArray *_touches;
NSMutableArray *_currentTouch;
NSMutableArray *_redoStore;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
_touches = [[NSMutableArray alloc]init];
_currentTouch = [[NSMutableArray alloc]init];
_redoStore = [[NSMutableArray alloc]init];
}
return self;
}
#pragma mark - touches
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
[_currentTouch removeAllObjects];
[_currentTouch addObject:NSStringFromCGPoint(touchPoint)];
///there are other, possibly less expensive ways to do this.. (adding a CGPoint to an NSArray.)
// typecasting to (id) doesnt work under ARC..
// two NSNumbers probably not any cheaper..
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
[_currentTouch addObject:NSStringFromCGPoint(touchPoint)];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self];
[_currentTouch addObject:NSStringFromCGPoint(touchPoint)];
[_touches addObject:[NSArray arrayWithArray:_currentTouch]];
[_currentTouch removeAllObjects];
[self setNeedsDisplay];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[_currentTouch removeAllObjects];
[self setNeedsDisplay];
}
#pragma mark - drawing
- (void)drawRect:(CGRect)rect
{
//we could be adding a CALayer for each new line, which would be cheaper because you could draw each and basically forget it
CGContextRef _context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(_context, 1.0); //or whatever
///older lines
if ([_touches count]) {
for (int line = 0; line < [_touches count]; line ++) {
NSArray *thisLine = [_touches objectAtIndex:line];
if ([thisLine count]) {
CGContextSetStrokeColorWithColor(_context, [self colourForLineAtIndex:line].CGColor);
CGPoint start = CGPointFromString([thisLine objectAtIndex:0]);
CGContextMoveToPoint(_context, start.x, start.y);
for (int touch = 1; touch < [thisLine count]; touch ++) {
CGPoint pt = CGPointFromString([thisLine objectAtIndex:touch]);
CGContextAddLineToPoint(_context, pt.x, pt.y);
}
CGContextStrokePath(_context);
}
}
}
///current line
if ([_currentTouch count]) {
CGPoint start = CGPointFromString([_currentTouch objectAtIndex:0]);
CGContextSetStrokeColorWithColor(_context, [self colourForLineAtIndex:[_touches count]].CGColor);
CGContextMoveToPoint(_context, start.x, start.y);
for (int touch = 1; touch < [_currentTouch count]; touch ++) {
CGPoint touchPoint = CGPointFromString([_currentTouch objectAtIndex:touch]);
CGContextAddLineToPoint(_context, touchPoint.x, touchPoint.y);
}
CGContextStrokePath(_context);
}
}
-(UIColor *)colourForLineAtIndex:(int)lineIndex{
return (lineIndex%2 == 0) ? [UIColor yellowColor] : [UIColor purpleColor];
/// you might have a diff colour for each line, eg user might select a pencil from a toolbar etc
}
#pragma mark - undo mechanism
-(void)undo{
if ([_currentTouch count]) {
[_redoStore addObject:[NSArray arrayWithArray:_currentTouch]];
[_currentTouch removeAllObjects];
[self setNeedsDisplay];
}else if ([_touches count]){
[_redoStore addObject:[_touches lastObject]];
[_touches removeLastObject];
[self setNeedsDisplay];
}else{
//nothing left to undo
}
}
-(void)redo{
if ([_redoStore count]) {
[_touches addObject:[_redoStore lastObject]];
[_redoStore removeLastObject];
[self setNeedsDisplay];
}
}
@end