《iOS创意程序设计家》——第6.5节Undo与Redo机制

    xiaoxiao2023-12-05  151

    本节书摘来自异步社区《iOS创意程序设计家》一书中的第6章,第6.5节Undo与Redo机制,作者 林柏全,更多章节内容可以访问云栖社区“异步社区”公众号查看

    6.5 Undo与Redo机制iOS创意程序设计家还记得第5章在提到UITextView的时候是怎样实现Undo机制的吗?其实,iPhone OS3.0以后就内建了Undo-Redo的机制。在默认的情况下,每一个应用程序的window对象都提供一个NSUndoManager对象,用以管理Undo与Redo的操作,而窗口内的每一个控件也都有其各自的NSUndoManager对象。

    这个Undo-Redo机制是怎么运作的呢?首先,它会有两组堆栈(stack),分别用来存放Undo与Redo的操作,而每一个操作都会以NSInvocation这个类来封装。很明显地,在这个类里面必须记录这个操作所使用的选择器(selector)、这个选择器的信息接收者以及所有的参数。

    这里用一个例子来说明这一对堆栈的运作情况。假设有一个文本框textView,用户将原先的文字“ABC”修改为“DEF”,则这个修改的操作将会被以“反向”操作方式推入堆栈,也就是说将用户输入的“DEF”的文字还原为“ABC”的操作,如图6.20所示。

    当我们按下“Undo”后,textView的文字会改回“ABC”,而这时候NSInvocation A会推出Undo堆栈,此时,Redo堆栈则是一个与NSInvocation A相反的操作NSInvocation B。

    从上面的说明来看,当执行了一项操作时,我们必须要提供给NSUndoManager一个反向的操作。例如:当我们新增了一笔数据时,其反向操作就是将这笔数据删除。看起来,我们的程序代码似乎得多写不少东西。好在大部分的图形化控件都有其各自的NSUndoManager,所以要在原有的程序上加上Undo与Redo的功能其实是很简单的。接下来,我们可以把在第5章写的UITextViewDemo这个例子稍微改写一下,如下所示:

    -(void) undoInput:(id) sender {   self.navigationItem.leftBarButtonItem = nil;   [[content undoManager] undo];   // [content setText:prevText]; }

    上面程序中批注的部分就是我们尚未介绍NSUndoManager之前的做法,但是有了NSUndoManager之后,只需要简简单单的一行就可以实现Undo的功能了。当然,也可以再加上Redo的功能,只要调用[[content undoManager] redo]就可以了。

    虽然部分图形化控件都提供这种功能,但是,我们是否也可以将Undo-Redo机制应用在其他非图形化控件上呢?其实也是可以的,不过我们需要自己产生一个NSUndoManager,并建立相对应的Undo与Redo机制。例如:

    NSUndoManager *manager = [NSUndoManager new]; 还可以通过 prepareWithInvocationTarget:来建立您的Undo的操作。 [[manager prepareWithInvocationTarget:self] myUndoAction]; 有了Invocation之后就可以调用Undo与Redo方法来进行Undo与Redo。

    应用范例:破裂的手机现在我们要应用Undo-Redo机制以及摇晃事件的检测来制作一个有趣的应用程序。这个应用程序一开始会让界面呈现破碎的样子,当然这并非是真的,而是我们制作出来的效果。但是当其他人看到这个界面的时候,肯定会很吃惊。这时候,只要摇晃一下手机就会恢复原状,再摇晃一次界面就会变成破碎的模样。

    学习重点:

    Undo-Redo机制的使用摇晃事件的应用动画的使用建立一个Single View的项目,并命名为“BrokeniPhone”。

    在建立项目过程中,请记得勾选“Use Storyboard”以及“Use Automatic Reference Counting”选项。

    在项目中加入两张手机的界面。

    在设计界面中加入Image View控件,如图6.21所示。

    打开“MainStorybard.storyboard”并从控件库中加入一个Image View控件到界面中。这时候,请指定该控件的图形为破碎后的手机界面。

    选中界面中的手机背景,然后打开编辑器的辅助模式,按住“Control”键后拉到ViewController.h以建立Outlet。完成后,我们先在ViewController.h中定义一个NSUndoManager的变量以及restoreScreen和breakScreen这两个方法,代码如下:

    #import <UIKit/UIKit.h> @interface ViewController : UIViewController{ @private   NSUndoManager *undoManager; } @property (strong, nonatomic) IBOutlet UIImageView *screen; -(void) restoreScreen; -(void) breakScreen; @end

    打开ViewController.m,并编辑如下:

    处理晃动事件的第一步就是让目前的ViewController可以成为First Responder。

    // 让ViewController可以成为First Responder - (BOOL) canBecomeFirstResponder {   return YES; } 接着要让ViewController变成First Responder,而在界面消失后让ViewController变成非First Responder。 - (void)viewDidAppear:(BOOL)animated {   [super viewDidAppear:animated];   // 让ViewController变成First Responder     [self becomeFirstResponder]; } - (void)viewDidDisappear:(BOOL)animated {    [super viewDidDisappear:animated];   // 让ViewController变成非First Responder     [self resignFirstResponder]; } 最后,加入摇晃操作的检测事件处理就好了。 - (void) motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {   if (motion==UIEventSubtypeMotionShake) {     if ([undoManager canUndo]) {        [undoManager undo];     }else{        [undoManager redo];     }   } }

    由于我们要让undoManager在Undo与Redo间不断切换,因此,我们可以通过canUndo这个方法来判断在Undo堆栈里面是否有Invocation。

    这样就完成了晃动事件的处理。

    现在开始初始化undoManager。由于刚开始的界面是破碎的模样,因此,我们在Undo的堆栈里面要建立一个恢复原状的Invocation。

    - (void)viewDidLoad {   [super viewDidLoad];   undoManager = [NSUndoManager new];   [[undoManager prepareWithInvocationTarget:self] restoreScreen]; } 建立好Invocation后,undoManager就可以进行Undo了。 7.jpg 建立界面破碎以及恢复界面的操作。 - (void) breakScreen {   [UIView transitionWithView:screen      duration:0.5f options:UIViewAnimationOptionTransitionCrossDissolve animations: ^(void){      screen.image = [UIImage imageNamed:@"broken.png"];   } completion:nil];   [[undoManager prepareWithInvocationTarget:self] restoreScreen];   } - (void) restoreScreen {   [UIView transitionWithView:screen      duration:0.5f options:UIViewAnimationOptionTransitionCrossDissolve animations:^(void){      screen.image = [UIImage imageNamed:@"normal.png"];   } completion:nil];   [[undoManager prepareWithInvocationTarget:self] breakScreen]; }

    这两个方法的操作刚好相反,包括加载的图形以及堆栈内的Invocation。我们在每一个操作内都要去建立相反的操作,这样在堆栈内才有相反的操作可以还原回去。现在,您可以拿着这个应用程序去吓唬您的朋友,相信他们应该会吓一跳吧。不过这样的应用程序并不适合上架,被Apple拒绝的几率也十分高,读者们可以好好思考一下其中的原因。

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)