如何在iOS中执行撤消操作以存储UIGraphicsContext?

时间:2013-02-01 10:55:35

标签: ios objective-c xcode nsundomanager

我想在绘图应用中对图形上下文执行撤消操作。按下撤消时,我想移动到包含旧图形的上一个上下文。

例如:

我在上下文中有一个矩形。在拖动时,我将矩形移动到新位置并重绘它。现在,当我按下撤消按钮时,我想将矩形移动到上一个位置。我怎么能这样做?

我对NSUndoManager已基本了解。

请帮助!

感谢。

4 个答案:

答案 0 :(得分:5)

您推断的功能并不存在。换句话说“图形上下文不能以这种方式工作(免费)。”您可能已经看过函数UIGraphicsPushContextUIGraphicsPopContext,但它们并不是您在这里所说的。它们所代表的“推”和“弹出”操作对图形上下文的尚未渲染的方面进行操作:例如当前的填充和描边颜色,剪辑矩形和变换矩阵。一旦某些东西被渲染到上下文中 - 即路径/ rect /等。已被描边或填充,或图像已被合成到上下文中 - 它将永久呈现。 “回归”的唯一方法是以某种方式重新创建以前的状态。不幸的是,UIGraphicsContext没有内置的魔法可以帮助你实现这一目标。

有几种方法可以解决这个问题。正如另一位用户所提到的,您可以在每次用户操作后捕获上下文的状态。简而言之,这意味着您的文档“模型”是位图堆栈。这将非常快速地获得内存密集 - 位图占用大量内存。你可以对这种方法进行优化以获得更多的利润,例如只保存每帧之间已经改变的区域,压缩位图,将它们交换到磁盘等等。有真正可用的应用程序。使用这种方法工作的App Store,但这种方法本质上是有限的,并且您最终将花费大量精力来优化和管理已保存的撤消状态堆栈。

当然,还有其他值得考虑的方法。最简单的方法是让您的实际文档模型是一堆小(即非位图)数据结构,描述重新创建上下文状态所需的操作。当用户撤消时,您只需删除堆栈顶部的操作,并通过回放剩余堆栈来重新创建图形上下文。对于“添加”类型的应用程序(想想“刷子”)来说,这是一个不错的方法,但即使在您描述的在画布上移动形状的简单场景中也会开始下降。它最终也会遇到一些与位图堆栈方法相同的问题 - 您拥有的操作越多,重新创建状态所需的时间就越长,因此您不可避免地会定期制作位图快照等等。

对于您所描述的画布上对象场景(“移动形状”操作),还有多种方法。一种经典的方法是让您的文档模型成为一组更小,更具体的数据结构,用于描述画布的当前状态。想想一个Shape类等。在最简单的情况下,当用户向画布添加一个矩形时,{z}有序的形状数组中会添加一个Shape实例。只要形状列表发生变化,就可以通过按Z顺序绘制每个形状来重新生成上下文。要实现撤消功能,每次改变形状数组时,还要使用NSUndoManager来记录一个调用,当重放时,将导致反向操作发生。

如果你的形状添加操作如下所示:

[shapeArray insertObject: newShape atIndex: 5];

然后,您将同时使用NSUndoManager

执行此操作
[[undoManager prepareInvocationWithTarget: shapeArray] removeObjectAtIndex: 5];

当用户单击撤消时,NSUndoManager将回放该调用,并且shapeArray将返回其先前状态。这是经典的NSUndoManager模式。它运作良好,但也有一些缺点。例如,跨应用程序终止持久化撤销堆栈并不一定是直截了当的。由于应用程序终止在iOS上很常见,并且用户通常希望应用程序能够跨终端无缝地恢复状态,因此根据您的要求,撤销堆栈无法在应用程序终止后继续存在的方法可能不具备启动性。还有其他更复杂的方法,但它们大多只是其中一个主题的变体。一个经典值得一读的是来自Command PatternGang of Four book, Design Patterns

无论您选择何种方法,这种应用都将是一个挑战。这个图形撤消功能根本不是UIGraphicsContext为“免费”而构建的内容。您已在评论中多次询问过一个示例。我很遗憾地说,但这是一个足够复杂的概念,在StackOverflow答案的限制范围内提供一个工作示例是不可能的。希望这些想法和指针是有帮助的。当然也有许多开源绘图应用程序可供您查看灵感(虽然我个人并不了解iOS的任何开源绘图应用程序。)

答案 1 :(得分:2)

UIGraphicsContext没有自己的撤消堆栈。您需要将您正在绘制的内容的每个元素存储在堆栈中,并从该堆栈中删除和添加项目以撤消和重做。 NSUndoManager类可以帮助您管理撤消和重做操作本身的逻辑,但是您有责任编写将绘图操作保存到堆栈的代码,然后从中读取以重新创建{{1 }}。

答案 2 :(得分:1)

首先设置undomanager对象并初始化它以供使用。

NSUndoManager *undoObject;
undoObject =[[NSUndoManager alloc] init];

每次更改上下文时,使用目标函数注册撤消对象以保存uigraphics上下文。

[[undoObject prepareWithInvocationTarget:self] performUndoWithObject:currentContext withLastpoint:lastPoint andFirstPoint:firstPoint];

编写userDefined函数定义

-(void)performUndoWithObject:(CGContextRef )context withLastpoint:(CGPoint)previousLastPoint andFirstPoint:(CGPoint)previousFirstPoint
{
    // necessary steps for undoing operation
}

单击撤消按钮时指定操作。

-(void)undoButtonClicked
{
    if([undoObject canUndo])
    {
        [undoObject undo];
    }   
}

答案 3 :(得分:1)

如何在目标c中执行撤消和重做操作,以便在iOS中存储值数组。我已经在Undo和Redo Button中创建了一个小型演示,替换了字符串值。 在ViewController.h文件中创建两个NSMutableArray

@interface ViewController : UIViewController<UITextFieldDelegate>
{
     NSMutableArray *arrUndo, *arrRedo;
}
@property (weak, nonatomic) IBOutlet UIButton *btnUndo;
@property (weak, nonatomic) IBOutlet UIButton *btnRedo;
@property (weak, nonatomic) IBOutlet UITextField *txtEnterValue;
@property (weak, nonatomic) IBOutlet UILabel *lblShowUndoOrRedoValue;
@property (weak, nonatomic) IBOutlet UIButton *btnOK;
- (IBAction)btnUndo:(id)sender;
- (IBAction)btnRedo:(id)sender;
- (IBAction)btnOK:(id)sender;
@end

实现ViewController.m文件以在撤消或重做中添加两个按钮操作方法。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.txtEnterValue.delegate = self;
    arrUndo = [[NSMutableArray alloc] init];
    arrRedo = [[NSMutableArray alloc] init];
}
-(BOOL)canBecomeFirstResponder {
    return YES;
}
- (IBAction)btnOK:(id)sender {

    NSString *str = [NSString stringWithFormat:@"%@",    [self.txtEnterValue.text description]];
    self.txtEnterValue.text = nil;
    self.lblShowUndoOrRedoValue.text = str;
    [arrUndo addObject:str];
    NSLog(@"Text Value : %@", [arrUndo description]);
}
- (IBAction)btnUndo:(id)sender {

    if ([arrUndo lastObject] == nil) {
        NSLog(@"EMPTY");
    }
    else
    {
    [arrRedo addObject:[arrUndo lastObject]];
    NSLog(@"ArrAdd %@", [arrUndo description]);

    [arrUndo removeLastObject];
    for (NSObject *obj in arrUndo) {
        if ([arrUndo lastObject] == obj) {

            self.lblShowUndoOrRedoValue.text = obj;
        }
    }
    NSLog(@"ArrText %@", [arrUndo description]);
    }
}
- (IBAction)btnRedo:(id)sender {

    if ([arrRedo lastObject] == nil) {
        NSLog(@"EMPTY");
    }
    else
    {
    [arrUndo addObject:[arrRedo lastObject]];
    for (NSObject *obj in arrRedo) {
        if ([arrRedo lastObject] == obj) {

            self.lblShowUndoOrRedoValue.text = obj;

        }
    }
    }
    [arrRedo removeLastObject];
}