5

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

例如:

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

我只是对NSUndoManager.

请帮忙!

谢谢。

4

4 回答 4

5

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

有几种方法可以解决这个问题。正如另一位用户提到的,您可以在每个用户操作后捕获上下文的状态。简而言之,这意味着您的文档“模型”是一个位图堆栈。这将很快占用大量内存——位图占用大量内存。当然,您可以对这种方法进行优化,以从中获得更多收益,例如仅保存在每帧之间发生变化的区域、压缩位图、将它们交换到磁盘等。使用这种方法工作的 App Store,但这种方法本质上是有限的,您最终将花费不小的努力来优化和管理已保存的撤消状态堆栈。

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

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

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

[shapeArray insertObject: newShape atIndex: 5];

然后,您将同时使用以下命令执行此操作NSUndoManager

[[undoManager prepareInvocationWithTarget: shapeArray] removeObjectAtIndex: 5];

当用户单击撤消时,NSUndoManager播放该调用,并shapeArray返回其先前状态。这是经典的NSUndoManager模式。它运作良好,但也有一些缺点。例如,在应用程序终止时保留撤消堆栈并不一定很简单。由于应用程序终止在 iOS 上司空见惯,并且用户通常希望应用程序在终止时无缝恢复状态,因此根据您的要求,撤消堆栈无法在应用程序终止后继续存在的方法可能是行不通的。还有其他更复杂的方法,但它们大多只是这些主题之一的变体。值得一读的经典著作是《四人帮》中的命令模式,设计模式

无论您选择哪种方法,这种应用程序的开发都将是一个挑战。这种图形撤消功能根本不是UIGraphicsContext“免费”内置的。您在评论中多次询问示例。很抱歉,这是一个足够复杂的概念,在 StackOverflow 答案的限制范围内,有人提供一个工作示例不太可能。希望这些想法和建议对您有所帮助。当然,还有许多开源绘图应用程序可供您寻找灵感(尽管我个人不知道任何适用于 iOS 的开源绘图应用程序。)

于 2013-02-06T03:35:58.633 回答
2

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

于 2013-02-04T05:24:32.623 回答
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];
    }   
}
于 2013-02-05T04:17:15.050 回答
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 文件以在 undo 或 redo 中添加两个按钮操作方法。

- (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];
}
于 2016-06-01T04:27:30.730 回答