在iOS系统中,所谓“编辑菜单(Editing Menu)”和“上下文菜单(Contextual Menu)”是有区别的,但在桌面操作系统中,我们常说的“右键菜单”就已经囊括了“编辑菜单”和“上下文菜单”。iOS将两者细分开来,大概是因为移动客户端显示屏大小限制,将所有菜单揉和在一起的话,必定导致超出显示范围,带来不好的操作体验。




Editing Menu





Contextual Menu


UIWebView为例,以此作为文本载体讲解如何自定义显示自己所需的编辑菜单项,当然,你也可以换作UITextView或任何一种文本容器,思路都是一样的。

先来看看一些App对编辑菜单的实现效果吧。最好的实现当然还是苹果自家的应用,下图是iBooks的实现,无论是菜单的自定义项,还是菜单文字的国际化处理,都很完美。




iBooks' Editing Menu


与此相比,Amazon的Kindle应用功能稍显单一,但从自定义菜单的处理上来看,还不失大公司水准,除了没有兼顾到中文国际化语言,仍算是比较优秀的作品。也许会有人反驳说Kindle不兼容中文是因为在App Store中国区根本就无法下载此应用,但是不是要照顾一下生活在英语地区的不以英语为母语的那些人呢?Google在这一点上就做得很好,虽然中国区没有Google+这款应用,但只要将iPhone系统语言设置为中文,其菜单文本就会转换为相应语言,几乎所有iOS系统支持的语言,Google+都做了处理。产品做到这种程度,由不得你不佩服。




Kindle's Editing Menu




Google+ Editing Menu


然后来看一款名为Kobo的电子书应用,其亮点是社交化阅读和分享,但一些细节上的处理真的无法让人满意。当用户长按并选中某段文字时,会同时叠加出现系统内置和自定义显示的菜单项,这是怎样一种蛋疼的行为啊?更令人发指的是,它在中国区的评分居然达到四颗星,中国区用户是不是太好打发了?




Kobo's Editing Menu


好吧,既然国外大公司都这种水平,那就更没有理由对国内产品横加指责了。一向以优雅、完美著称的iWeekly居然也是如此,还是中英文混搭的哦:)




iWeekly's Editing Menu


UIWebView,并重写UIRespondercanPerformAction:withSender:方法。selector名称如下:cut:copy:select:selectAll:paste:delete:_promptForReplace:_showTextStyleOptions:_define:_accessibilitySpeak:_accessibilityPauseSpeak:makeTextWritingDirectionRightToLeft:makeTextWritingDirectionLeftToRight:selector都被定义在一个名为UIResponderStandardEditActions的informal protocol中,所谓informal protocol,就是在NSObject上定义一个category,详见苹果官方文档Objective-C基础部分的讲解。UIResponderStandardEditActions定义在UIResponder接口中,每一个继承自UIResponder的具体类中都有各自不同的实现,比如UIWebView中就只实现了其中的copy:paste:,当然,还有一个私有的_define:。当你继承了一个UIWebView之后,就可以重写canPerformAction:withSender:这个消息,来控制是否显示某些系统内置菜单项。

// file: CustomWebView.h

#import <Foundation/Foundation.h>

@interface CustomWebView : UIWebView
@end
 
 
// file: CustomWebView.m

#import "CustomWebView.h"

@implementation CustomWebView {

}

// 隐藏“定义”菜单项。
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
  if (action == @selector(_define:)) return NO;
  return [super canPerformAction:action withSender:sender];
}

@end
 
UIWebView本身不支持的内置菜单,比如selectAll:,须注意,如果显示了父类中不支持的菜单,则必须在自定义的子类中实现该方法,否则,当用户点击菜单时,程序会不作任何响应,甚至崩溃。
 
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
  if (action == @selector(selectAll:)) return YES;
  return [super canPerformAction:action withSender:sender];
}

- (void)selectAll:(id)sender {
  NSLog(@"selectAll: in custom web view.");
}
 
CustomeWebView显示之前,向UIMenuController中添加一些自定义项即可,这是一个单例对象,一个应用中仅维护一个这样的实例。下面的代码向菜单列表中添加了一个名为Flag的项。
 
UIMenuItem *flag = [[UIMenuItem alloc] initWithTitle:@"Flag" action:@selector(flag:)];
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setMenuItems:[NSArray arrayWithObjects:flag, nil]];
[flag release];


.lproj文件夹即可。如果你添加了一个zh.lproj的文件夹到工程目录下,并在该文件夹下新建一个名为InfoPlist.strings的空白文件,那么,当你的程序运行在系统语言被设定为中文的移动终端时,这些内置菜单项自然会显示为中文。下面是一个Demo的效果图。



Custom Editing Menu Demo