Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How could I replace UIMenuController with my own view when text is selected?

When text is selected, by default a UIMenuController pops up with cut/copy/paste etc.

enter image description here

I'd like to replace this with my own custom view (similar looking, but twice as high so that I can have two rows of buttons/custom views). How can I do this?

I know there's no easy way. I'm expecting that if there's an easy solution, it won't be very elegant. The code can't use any private API either.

I'd really, really rather not have to implement my own text view, reimplement text selection and input, and reimplement the magnifying view just so I can write my own UIMenuController clone if there's any way to avoid it. It's pretty important to the app's interface that I can replace the UIMenuController, so if there's no other answer then I may end up doing this. I'll be VERY grateful if anyone can save me a decent chunk of time and propose another, easier way of doing this!

like image 449
Jordan Smith Avatar asked Jan 30 '13 02:01

Jordan Smith


2 Answers

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 UIMenuControllerWillShowMenuNotifications 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:.

like image 54
Fabian Kreiser Avatar answered Nov 15 '22 06:11

Fabian Kreiser


I think this may help you https://github.com/cxa/UIMenuItem-CXAImageSupport

UIMenuItem uses UILabel to display its title, that means we can swizzle -drawTextInRect: to support image.

UIMenuItem+CXAImageSupport is a dirty hack but should be safe in most cases. Contains no any private API.

Make a category instead of subclassing for UIMenuItem gains more flexibility. Yes, this category can be applied to the awesome PSMenuItem too!

enter image description here

like image 3
yijiankaka Avatar answered Nov 15 '22 06:11

yijiankaka