1, 基本使用
以對一個UILabel長按彈出菜單為例
子類化UILabel
因為需要覆蓋這幾個方法:
- (BOOL)canBecomeFirstResponder
; 返回YES
同時需要在每次UI元素出現的時候去becomeFirstResponder
一次,才能顯示出菜單. 在我的實測中, 我在ViewDidLoad
里面這么做了, 當UI導航到別的頁面(導航控件, 或modal頁面), 然后回來, 菜單又失效了, 所以我寫到ViewWillAppear
里面去了, 通過
- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender
;
這個方法會在每一個menuItem生成的時候調用一次, 因此在方法體里就要根據action來判斷是否需要顯示在菜單里, 如果不需要, 則返回NO
. 也就是說, 如果你什么都不做, 直接返一個YES
, 那么所有的默認菜單項都會顯示出來, 此處我們只要一個Copy
選項吧:
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
return (action == @selector(copy:));
}
添加觸發方式
如果是以長按為觸發, 則添加長按手勢, 代碼片段如下:
// 在awakeFromNib里面添加即可
UILongPressGestureRecognizer *menuGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(menu:)];
menuGesture.minimumPressDuration = 0.2;
[self addGestureRecognizer:menuGesture];
- (void)menu:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setTargetRect:self.frame inView:self.superView]; // 把誰的位置告訴控制器, 菜單就會以其為基準在合適的位置出現
[menu setMenuVisible:YES animated:YES];
}
}
編寫菜單行為
上面我們只要了copy, 那么就覆蓋默認的copy方法:
- (void)copy:(id)sender {
UIPasteboard *paste = [UIPasteboard generalPasteboard];
paste.string = self.text;
}
2, 添加自定義菜單項
自定義菜單只需要在菜單控制器添加幾個item即可, 結合上例, 我的那個label是顯示電話號碼的, 那么就讓它多顯示一個”打電話”和一個”發短信”菜單吧, 唯一需要注意的是, 在設置自定義菜單項時, 設置的items只影響自定義部分, 標準菜單項仍然是由canPerformAction
決定的:
UIMenuItem *itemCall = [[UIMenuItem alloc] initWithTitle:@"Call" action:@selector(call:)];
UIMenuItem *itemMessage = [[UIMenuItem alloc] initWithTitle:@"Message" action:@selector(message:)];
[[UIMenuController sharedMenuController] setMenuItems: @[itemCall, itemMessage]];
[[UIMenuController sharedMenuController] update];
注, 添加了兩個菜單后, canPerformAction需要相應變化, 自己想想應該怎么改. 也可以在下一節看代碼. 當然也要自行寫完里面的call和message方法, 參照copy的寫法即可
3, UITableViewCell長按顯示菜單
標準菜單項
UITableView里面長項條目顯示標準菜單, 只需要實現下述代理方法即可:
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
return (action == @selector(copy:)); // 只顯示Copy
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
if (action == @select(copy:)) {
UIPasteboard *paste = [UIPasteboard generalPasteboard];
paste.string = cell.detailLabel.text; // 自行寫業務邏輯
}
}
4, TableViewCell添加自定義菜單項
同樣也得子類化一個TableViewCell,目的也是為了覆蓋同樣的幾個方法:
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
return (action == @selector(copy:) || action == @selector(call:) || action == @selector(message:)); // 此處我們把三個行為都寫全了, 回答上一節的問題
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
但因為tableView已經實現了菜單, 所以不需要顯式為每個cell去becomeFirtResponder
了.
添加菜單項的方法同上, 寫菜單行為的方法同copy:
, 都是一樣的.
注: 你們或許已經發現了, 添加自定義菜單項的時候, 仍然需要
canPerformAction
, 在這里, 與tableView代理里面的同名方法有什么關系? 是的, 兩個都要寫, tableView里面的只會影響標準菜單, 文檔說只支持這兩個UIResponderStandardEditActions
(copy/paste)
注: 然而,
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
這個方法卻有點別扭, 一來不需要去實現了, 二來又不能注釋掉(你們自己試一下), 等于一定要留一個空的方法體在那里…