There are three important things you have to know before you can start:
1) You'll have to write your custom menu controller view, but I guess you kinda expected that. I only know of a commercial implementation of a custom menu controller, but this shouldn't be too hard.
2) There is a useful method on UIResponder
called -canPerformAction:withSender:
. Read more about it in the UIResponder Class Reference. You can use that method to determine whether your text view supports a specific standard action (defined in the UIResponderStandardEditActions protocol).
This will be useful when deciding which items to show in your custom menu controller. For example the Paste menu item will only be shown when the user's pasteboard contains a string to paste.
3) You can detect when the UIMenuController
will be shown by listening to the UIMenuControllerWillShowMenuNotification
notification.
Now that you know all of that, this is how I'd start tackling that:
1) Listen for UIMenuControllerWillShowMenuNotification
s when the text view is first responder
- (void)textViewDidBeginEditing:(UITextView *)textView {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuWillBeShown:) name:UIMenuControllerWillShowMenuNotification object:nil];
}
- (void)textViewDidEndEditing:(UITextView *)textView {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerWillShowMenuNotification object:nil];
}
2) Show your custom menu controller instead of the default UIMenuController
- (void)menuWillBeShown:(NSNotification *)notification {
CGRect menuFrame = [[UIMenuController sharedMenuController] menuFrame];
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO]; // Don't show the default menu controller
CustomMenuController *controller = ...;
controller.menuItems = ...;
// additional stuff goes here
[controller setTargetRectWithMenuFrame:menuFrame]; // menuFrame is in screen coordinates, so you might have to convert it to your menu's presenting view/window/whatever
[controller setMenuVisible:YES animated:YES];
}
Misc. 1) You can use a fullscreen UIWindow
for showing your custom menu so it can overlap the status bar.
UIWindow *presentingWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
presentingWindow.windowLevel = UIWindowLevelStatusBar + 1;
presentingWindow.backgroundColor = [UIColor clearColor];
[presentingWindow addSubview:controller];
[presentingWindow makeKeyAndVisible];
Misc. 2) For determining which menu items to show you can use the mentioned -canPerformAction:withSender:
BOOL canPaste = [textView canPerformAction:@selector(paste:) withSender:nil];
BOOL canSelectAll = [textView canPerformAction:@selector(selectAll:) withSender:nil];
Misc. 3) You'll have to handle dismissing the menu yourself by using a UITapGestureRecognizer
on the presenting window or something like that.
This won't be easy, but it's doable and I hope it works out well for you. Good luck!
Update:
A new menu implementation popped up on cocoacontrols.com today that you might want to check out: https://github.com/questbeat/QBPopupMenu
Update 2:
As explained in this answer you can get the frame of a text view's selected text using -caretRectForPosition:
.