我想在绘图应用中对图形上下文执行撤消操作。按下撤消时,我想移动到包含旧图形的上一个上下文。
例如:
我在上下文中有一个矩形。在拖动时,我将矩形移动到新位置并重绘它。现在,当我按下撤消按钮时,我想将矩形移动到上一个位置。我怎么能这样做?
我对NSUndoManager
已基本了解。
请帮助!
感谢。
答案 0 :(得分:5)
您推断的功能并不存在。换句话说“图形上下文不能以这种方式工作(免费)。”您可能已经看过函数UIGraphicsPushContext
和UIGraphicsPopContext
,但它们并不是您在这里所说的。它们所代表的“推”和“弹出”操作对图形上下文的尚未渲染的方面进行操作:例如当前的填充和描边颜色,剪辑矩形和变换矩阵。一旦某些东西被渲染到上下文中 - 即路径/ 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 Pattern的Gang 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];
}