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

简介:

本节书摘来自异步社区《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所示。


cb1a4f7048739b97837b622ea83b3adcfd17eb07

当我们按下“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控件到界面中。这时候,请指定该控件的图形为破碎后的手机界面。


9169932a84d3146e0451817a9180817af50ec46b

选中界面中的手机背景,然后打开编辑器的辅助模式,按住“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拒绝的几率也十分高,读者们可以好好思考一下其中的原因。

相关文章
|
8月前
|
存储
13-iOS消息转发机制以及常用场景
13-iOS消息转发机制以及常用场景
30 0
|
12月前
|
API iOS开发
iOS 沙盒机制
iOS 沙盒机制
114 0
|
安全 数据安全/隐私保护 iOS开发
iOS逆向-day9:签名机制(下)
iOS逆向-day9:签名机制(下)
276 0
iOS逆向-day9:签名机制(下)
|
存储 安全 数据安全/隐私保护
iOS逆向-day9:签名机制(中)
iOS逆向-day9:签名机制(中)
150 0
iOS逆向-day9:签名机制(中)
|
算法 网络安全 数据安全/隐私保护
iOS逆向-day9:签名机制(上)
iOS逆向-day9:签名机制(上)
138 0
iOS逆向-day9:签名机制(上)
|
安全 iOS开发 开发者
iOS中RunLoop机制浅探
iOS中RunLoop机制浅探
115 0
|
iOS开发
iOS 并发机制
ios 并发机制(一) —— GCD中的信号量及几个重要函数ios 并发机制(二) —— NSOperation实现多并发之创建任务ios 并发机制(三) —— NSOperation实现多并发之创建队列和开启线程ios 并发机制(四) —— NSOp...
1111 0