UIMenuController not showing up - iphone

I'm trying to create a custom UIMenuController and display it in my view. Here's my code:
UIMenuController *menuController = [UIMenuController sharedMenuController];
UIMenuItem *listMenuItem = [[UIMenuItem alloc] initWithTitle:#"List" action:#selector(addList:)];
[menuController setMenuItems:[NSArray arrayWithObject:listMenuItem]];
[menuController setTargetRect:CGRectMake(50.0, 50.0, 0, 0) inView:self.view];
[menuController setMenuVisible:YES animated:YES];
[listMenuItem release];
There are no errors or exceptions, but the menu controller just doesn't show up.

You need to do three things:
You need to call -becomeFirstResponder on the view or view controller.
Your view or view controller needs to implement -canBecomeFirstResponder (returning YES).
Optionally, your view or view controller can implement -canPerformAction:action withSender:sender to show/hide menu items on an individual basis.

The answer mentions three things, but to be picky, there are six:
The menu handler must be a UIView. If it isn't, -becomeFirstResponder fails.
The menu handler must have userInteractionEnabled = YES
The menu handler must be in the view hierarchy and its -window property must be the same as the window for the view in the inView: argument.
You need to implement -canBecomeFirstResponder and return YES.
You need to call [handler becomeFirstResponder], before [menu setTargetRect:inView:] is called, or the latter will fail.
You need to call [menu setTargetRect:inView] (at least once) and [menu setMenuVisible:animated:].
In particular points 1-3 above got me. I wanted a custom menu handler class that was a UIResponder at first, which caused -becomeFirstResponder to return NO; then it was a UIView, which failed, then I tried making it a UIButton which worked, but only because userInteractionEnabled defaults to YES for buttons and NO for UIViews.

UIMenuController is visible on any view only if the view is first responder and
- (BOOL)canPerformAction method returns YES
Hence if your menu controller is to be shown on button click, the first line in the button action should be [self becomeFirstResponder]. NOTE: here self is the view which will present the menus.
If your menus are to be shown on long press gesture, then add longPressGesture to the UIView and in the longpress event before writing
[menuController setTargetRect:CGRectMake(50.0, 50.0, 0, 0) inView:self.view];
[menuController setMenuVisible:YES animated:YES];
write [self becomeFirstResponder];
Then follow the steps mentioned by OZ.

The below is a full commented working example ...
View subclass header file
#import <Foundation/Foundation.h>
#interface MenuControllerSupportingView : UIView
{
}
#end
View subclass source file
#import "MenuControllerSupportingView.h"
#implementation MenuControllerSupportingView
//It's mandatory and it has to return YES then only u can show menu items..
-(BOOL)canBecomeFirstResponder
{
return YES;
}
-(void)MenuItemAClicked
{
NSLog(#"Menu item A clicked");
}
-(void)MenuItemBClicked
{
NSLog(#"Menu item B clicked");
}
-(void)MenuItemCClicked
{
NSLog(#"Menu item C clicked");
}
//It's not mandatory for custom menu items
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if(action == #selector(MenuItemAClicked))
return YES;
else if(action == #selector(MenuItemBClicked))
return YES;
else if(action == #selector(MenuItemCClicked))
return YES;
else
return NO;
}
view Controller header file
#import <UIKit/UIKit.h>
#interface ViewController1 : UIViewController
#end
view Controller source file
#import "ViewController1.h"
#import "MenuControllerSupportingView.h"
#interface ViewController1 ()
{
MenuControllerSupportingView *vu;
}
#end
#implementation ViewController1
- (void)viewDidLoad
{
[super viewDidLoad];
vu=[[SGGI_MenuControllerSupportingView alloc]initWithFrame:CGRectMake(0,0,768,1024)];
[self.view addSubview:vu];
UIButton *btn=[UIButton buttonWithType:UIButtonTypeCustom];
[btn setFrame:CGRectMake(200,200,200,30)];
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[btn setTitle:#"Show" forState:UIControlStateNormal];
[btn addTarget:self action:#selector(SHowMenu) forControlEvents:UIControlEventTouchUpInside];
[vu addSubview:btn];
}
-(void)SHowMenu
{
UIMenuController *menucontroller=[UIMenuController sharedMenuController];
UIMenuItem *MenuitemA=[[UIMenuItem alloc] initWithTitle:#"A" action:#selector(MenuItemAClicked)];
UIMenuItem *MenuitemB=[[UIMenuItem alloc] initWithTitle:#"B" action:#selector(MenuItemBClicked)];
UIMenuItem *MenuitemC=[[UIMenuItem alloc] initWithTitle:#"C" action:#selector(MenuItemCClicked)];
[menucontroller setMenuItems:[NSArray arrayWithObjects:MenuitemA,MenuitemB,MenuitemC,nil]];
//It's mandatory
[vu becomeFirstResponder];
//It's also mandatory ...remeber we've added a mehod on view class
if([vu canBecomeFirstResponder])
{
[menucontroller setTargetRect:CGRectMake(10,10, 0, 200) inView:vu];
[menucontroller setMenuVisible:YES animated:YES];
}
}
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
In View class if u write return YES alone in canPerformAction you will see all the default menuitems like camera symbol,cut,copy etc..
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return YES;
}
if u want to show something like camera alone then
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if(action==#selector(_insertImage:))
return YES;
else
return NO;
}
if u want to know about all the actions then
visit the link

Just in case anyone is having this issue specifically (and randomly) with iOS6: you might want to look at this SO related to having Speak Selection enabled on the device (Settings -> General -> Accessibility -> Speak Selection: On). A small number of my users were not able to see the custom UIMenuItems and this was the cause.

In Swift 3.0 -
In my case I wanted to have the VC pre-select the text in a TextView and display a custom menu for the user to take action on that selection. As mentioned by Kalle, order is very important, especially making setMenuVisible last.
In VC, viewDidLoad:
menuCont = UIMenuController.shared
let menuItem1: UIMenuItem = UIMenuItem(title: "Text", action: #selector(rtfView.textItem(_:)))
let menuItems: NSArray = [menuItem1]
menuCont.menuItems = menuItems as? [UIMenuItem]
In VC, when the user hits a button:
#IBAction func pressed(_ sender: Any) {
self.textView.selectedRange = NSMakeRange(rangeStart, rangeLength)
self.textView.becomeFirstResponder()
menuCont.setTargetRect(CGRect.zero, in: self.textView)
menuCont.setMenuVisible(true, animated: true)
}
Finally, in the sub-class of the TextView:
class rtfView: UITextView {
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any!) -> Bool {
if (action == #selector(textItem(_:))) {
return true
} else {
return false
}
}
}

maybe because CGRectMake(50.0, 50.0, 0, 0) creates a CGRect with width = 0 and height = 0?
cheers,
anka

Related

How to enable cancel button with UISearchBar?

In the contacts app on the iPhone if you enter a search term, then tap the "Search" button, the keyboard is hidden, BUT the cancel button is still enabled. In my app the cancel button gets disabled when I call resignFirstResponder.
Anyone know how to hide the keyboard while maintaining the cancel button in an enabled state?
I use the following code:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[searchBar resignFirstResponder];
}
The keyboard slides out of view, but the "Cancel" button to the right of the search text field is disabled, so that I cannot cancel the search. The contacts app maintains the cancel button in an enabled state.
I think maybe one solution is to dive into the searchBar object and call resignFirstResponder on the actual text field, rather than the search bar itself.
Any input appreciated.
This method worked in iOS7.
- (void)enableCancelButton:(UISearchBar *)searchBar
{
for (UIView *view in searchBar.subviews)
{
for (id subview in view.subviews)
{
if ( [subview isKindOfClass:[UIButton class]] )
{
[subview setEnabled:YES];
NSLog(#"enableCancelButton");
return;
}
}
}
}
(Also be sure to call it anywhere after [_searchBar resignFirstResponder] is used.)
try this
for(id subview in [yourSearchBar subviews])
{
if ([subview isKindOfClass:[UIButton class]]) {
[subview setEnabled:YES];
}
}
The accepted solution will not work when you start scrolling the table instead of tapping the "Search" button. In that case the "Cancel" button will be disabled.
This is my solution that re-enables the "Cancel" button every time it is disabled by using KVO.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Search for Cancel button in searchbar, enable it and add key-value observer.
for (id subview in [self.searchBar subviews]) {
if ([subview isKindOfClass:[UIButton class]]) {
[subview setEnabled:YES];
[subview addObserver:self forKeyPath:#"enabled" options:NSKeyValueObservingOptionNew context:nil];
}
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Remove observer for the Cancel button in searchBar.
for (id subview in [self.searchBar subviews]) {
if ([subview isKindOfClass:[UIButton class]])
[subview removeObserver:self forKeyPath:#"enabled"];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Re-enable the Cancel button in searchBar.
if ([object isKindOfClass:[UIButton class]] && [keyPath isEqualToString:#"enabled"]) {
UIButton *button = object;
if (!button.enabled)
button.enabled = YES;
}
}
As of iOS 6, the button appears to be a UINavigationButton (private class) instead of a UIButton.
I have tweaked the above example to look like this.
for (UIView *v in searchBar.subviews) {
if ([v isKindOfClass:[UIControl class]]) {
((UIControl *)v).enabled = YES;
}
}
However, this is obviously brittle, since we're mucking around with the internals. It also can enable more than the button, but it works for me until a better solution is found.
We should ask Apple to expose this.
This seemed to work for me (in viewDidLoad):
__unused UISearchDisplayController* searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
I realize I should probably be using the UISearchDisplayController properly, but this was an easy fix for my current implementation.
You can use the runtime API to access the cancel button.
UIButton *btnCancel = [self.searchBar valueForKey:#"_cancelButton"];
[btnCancel setEnabled:YES];
I expanded on what others here already posted by implementing this as a simple category on UISearchBar.
UISearchBar+alwaysEnableCancelButton.h
#import <UIKit/UIKit.h>
#interface UISearchBar (alwaysEnableCancelButton)
#end
UISearchBar+alwaysEnableCancelButton.m
#import "UISearchBar+alwaysEnableCancelButton.h"
#implementation UISearchBar (alwaysEnableCancelButton)
- (BOOL)resignFirstResponder
{
for (UIView *v in self.subviews) {
// Force the cancel button to stay enabled
if ([v isKindOfClass:[UIControl class]]) {
((UIControl *)v).enabled = YES;
}
// Dismiss the keyboard
if ([v isKindOfClass:[UITextField class]]) {
[(UITextField *)v resignFirstResponder];
}
}
return YES;
}
#end
Here's a slightly more robust solution that works on iOS 7. It will recursively traverse all subviews of the search bar to make sure it enables all UIControls (which includes the Cancel button).
- (void)enableControlsInView:(UIView *)view
{
for (id subview in view.subviews) {
if ([subview isKindOfClass:[UIControl class]]) {
[subview setEnabled:YES];
}
[self enableControlsInView:subview];
}
}
Just call this method immediately after you call [self.searchBar resignFirstResponder] like this:
[self enableControlsInView:self.searchBar];
Voila! Cancel button remains enabled.
Till iOS 12, you can use like this:-
if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "_cancelButton") as? UIButton{
cancelButton.isEnabled = true
}
As of iOS 13, if you use like (forKey: "_cancelButton"), so this use of private API is caught and leads to a crash,
unfortunately.
For iOS 13+ & swift 5+
if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
cancelButton.isEnabled = true
}
I found a different approach for making it work in iOS 7.
What I'm trying is something like the Twitter iOS app. If you click on the magnifying glass in the Timelines tab, the UISearchBar appears with the Cancel button activated, the keyboard showing, and the recent searches screen. Scroll the recent searches screen and it hides the keyboard but it keeps the Cancel button activated.
This is my working code:
UIView *searchBarSubview = self.searchBar.subviews[0];
NSArray *subviewCache = [searchBarSubview valueForKeyPath:#"subviewCache"];
if ([subviewCache[2] respondsToSelector:#selector(setEnabled:)]) {
[subviewCache[2] setValue:#YES forKeyPath:#"enabled"];
}
I arrived at this solution by setting a breakpoint at my table view's scrollViewWillBeginDragging:. I looked into my UISearchBar and bared its subviews. It always has just one, which is of type UIView (my variable searchBarSubview).
Then, that UIView holds an NSArray called subviewCache and I noticed that the last element, which is the third, is of type UINavigationButton, not in the public API. So I set out to use key-value coding instead. I checked if the UINavigationButton responds to setEnabled:, and luckily, it does. So I set the property to #YES. Turns out that that UINavigationButton is the Cancel button.
This is bound to break if Apple decides to change the implementation of a UISearchBar's innards, but what the hell. It works for now.
SWIFT version for David Douglas answer (tested on iOS9)
func enableSearchCancelButton(searchBar: UISearchBar){
for view in searchBar.subviews {
for subview in view.subviews {
if let button = subview as? UIButton {
button.enabled = true
}
}
}
}
Most of the posted solutions are not robust, and will let the Cancel button get disabled under various circumstances.
I have attempted to implement a solution that always keeps the Cancel button enabled, even when doing more complicated things with the search bar. This is implemented as a custom UISearchView subclass in Swift 4. It uses the value(forKey:) trick to find both the cancel button and the search text field, and listens for when the search field ends editing and re-enables the cancel button. It also enables the cancel button when switching the showsCancelButton flag.
It contains a couple of assertions to warn you if the internal details of UISearchBar ever change and prevent it from working.
import UIKit
final class CancelSearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
guard let searchField = value(forKey: "_searchField") as? UIControl else {
assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
return
}
searchField.addTarget(self, action: #selector(enableSearchButton), for: .editingDidEnd)
}
override var showsCancelButton: Bool {
didSet { enableSearchButton() }
}
#objc private func enableSearchButton() {
guard showsCancelButton else { return }
guard let cancelButton = value(forKey: "_cancelButton") as? UIControl else {
assertionFailure("UISearchBar internal implementation has changed, this code needs updating")
return
}
cancelButton.isEnabled = true
}
}
Building on smileyborg's answer, just place this in your searchBar delegate:
- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
dispatch_async(dispatch_get_main_queue(), ^{
__block __weak void (^weakEnsureCancelButtonRemainsEnabled)(UIView *);
void (^ensureCancelButtonRemainsEnabled)(UIView *);
weakEnsureCancelButtonRemainsEnabled = ensureCancelButtonRemainsEnabled = ^(UIView *view) {
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:[UIControl class]]) {
[(UIControl *)subview setEnabled:YES];
}
weakEnsureCancelButtonRemainsEnabled(subview);
}
};
ensureCancelButtonRemainsEnabled(searchBar);
});
}
This solution works well on iOS 7 and above.
For iOS 10, Swift 3:
for subView in self.movieSearchBar.subviews {
for view in subView.subviews {
if view.isKind(of:NSClassFromString("UIButton")!) {
let cancelButton = view as! UIButton
cancelButton.isEnabled = true
}
}
}
For iOS 9/10 (tested), Swift 3 (shorter):
searchBar.subviews.flatMap({$0.subviews}).forEach({ ($0 as? UIButton)?.isEnabled = true })
For iOS 11 and above, Swift 4-5:
extension UISearchBar {
func alwaysShowCancelButton() {
for subview in self.subviews {
for ss in subview.subviews {
if #available(iOS 13.0, *) {
for s in ss.subviews {
self.enableCancel(with: s)
}
}else {
self.enableCancel(with: ss)
}
}
}
}
private func enableCancel(with view:UIView) {
if NSStringFromClass(type(of: view)).contains("UINavigationButton") {
(view as! UIButton).isEnabled = true
}
}
}
UISearchBarDelegate
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.searchBar.resignFirstResponder()
self.searchBar.alwaysShowCancelButton()
}
for (UIView *firstView in searchBar.subviews) {
for(UIView* view in firstView.subviews) {
if([view isKindOfClass:[UIButton class]]) {
UIButton* button = (UIButton*) view;
[button setEnabled:YES];
}
}
}
You can create your CustomSearchBar inheriting from UISearchBar and implement this method:
- (void)layoutSubviews {
[super layoutSubviews];
#try {
UIView *baseView = self.subviews[0];
for (UIView *possibleButton in baseView.subviews)
{
if ([possibleButton respondsToSelector:#selector(setEnabled:)]) {
[(UIControl *)possibleButton setEnabled:YES];
}
}
}
#catch (NSException *exception) {
NSLog(#"ERROR%#",exception);
}
}
A better solution is
[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil].enabled = YES;
Better & Easy method:
[(UIButton *)[self.searchBar valueForKey:#"_cancelButton"] setEnabled:YES];
Swift 5 & iOS 14
if let cancelButton : UIButton = self.menuSearchBar.value(forKey: "cancelButton") as? UIButton {
cancelButton.isEnabled = true
}
One alternative that should be slightly more robust against UIKit changes, and doesn't reference anything private by name is to use the appearance proxy to set the tag of the cancel button. i.e., somewhere in setup:
let cancelButtonAppearance = UIBarButtonItem.appearance(whenContainedInInstancesOf: [UISearchBar.self])
cancelButtonAppearance.isEnabled = true
cancelButtonAppearance.tag = -4321
Then we can use the tag, here the magic number -4321 to find the tag:
extension UISearchBar {
var cancelButton: UIControl? {
func recursivelyFindButton(in subviews: [UIView]) -> UIControl? {
for subview in subviews.reversed() {
if let control = subview as? UIControl, control.tag == -4321 {
return control
}
if let button = recursivelyFindButton(in: subview.subviews) {
return button
}
}
return nil
}
return recursivelyFindButton(in: subviews)
}
}
And finally use searchBar.cancelButton?.isEnabled = true whenever the search bar loses focus, such as in the delegate. (Or if you use the custom subclass and call setShowsCancelButton from the delegate, you can override that function to also enable the button whenever it is shown.)

iPhone - UITableView : change default Edit button name

I have a usual editButtonItem in my navigationBar (created by the system), and I'd like to change its name. Si I wrote this lines in my TableViewController :
- (void)viewDidLoad
{
[super viewDidLoad];
[Some code...]
self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.navigationItem.rightBarButtonItem.title = #"New name";
}
That works, but when entering and exiting the Edit Mode, its system name is restored.
I tried to force it again in didEndEditingRowAtIndexPath for example, but with no success...
What should I do to fix this custom name without having to build the button from start by myself in the code ?
#Bojan's method is close but very inefficient, since it gets called for every single row. Just change the title at the beginning on viewDidLoad and in addition do this:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
// Make sure you call super first
[super setEditing:editing animated:animated];
if (editing)
{
self.editButtonItem.title = NSLocalizedString(#"Cancel", #"Cancel");
}
else
{
self.editButtonItem.title = NSLocalizedString(#"Edit", #"Edit");
}
}
Create your own edit button item if you don't like the name of the existing one.
Declare an ivar editItem in your header then create the item like so:
editItem = [[UIBarButtonItem alloc] initWithTitle:#"New Name" style:UIBarButtonItemStyleBordered target:self action:#selector(toggleEditing)]
in -toggleEditing, call
[self setEditing:!self.editing animated:YES]
and also update the title of the button (and optionally the appearance) to reflect the editing state.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.tableView.editing) {
self.editButtonItem.title = #"CustomDoneName";
}
else
self.editButtonItem.title = #"CustomEditName";
return YES;
}
This works fine for me.
To supplement these answers. Custom is the way to go BUT keep in mind:
If you do create your own button, which will change title, make sure you set it's possibleTitles property.
self.myEditButton.possibleTitles = [NSSet setWithObjects:#"Edit Seats", #"Done", nil];
Otherwise, the transition animation on your button may get weird, especially if the titles differ in length significantly. The reason for this is, we change the button's title as it's animating being tapped on.
Here is the only way I found out to fix the temporary shrunk dirty transition state of the editButtonItem when toggling the button with custom titles.
Even setting possibleTitles does not provide a perfect result.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
NSString *editButtonTitleBefore = self.editButtonItem.title;
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
[self setToolbarLayout];
if (editing && ![self.editButtonItem.title isEqualToString:NSLocalizedString(#"Cancel", #"Cancel")])
{
self.editButtonItem.title = NSLocalizedString(#"Cancel", #"Cancel");
}
else if (!editing && ![editButtonTitleBefore isEqualToString:#"Edit"])
{
// Dirty hax to avoid the editButton system title label shrink bug
self.editButtonItem.title = #"";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.editButtonItem.title = NSLocalizedString(#"Edit", #"Edit");
});
}
}
For Swift 3 you should override the setEditing(_ editing: Bool, animated: Bool) UIViewController's method as bellow:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if self.isEditing {
self.editButtonItem.title = "[New editing title]"
}
else {
self.editButtonItem.title = "[New to edit title]"
}
}
Furthermore, you should call this method on viewDidLoad to the editButtonItem starts on the view with the correct title, as bellow:
override func viewDidLoad() {
super.viewDidLoad()
self.setEditing(false, animated: false)
self.navigationItem.leftBarButtonItem = self.editButtonItem
}

how to show UIMenuController to UIBarButtonItem

How to show UIMenuController under UIBarButtonItem when click it?
Assume your UIBarButtonItem has been connected to:
-(void)buttonClicked:(UIBarButtonItem*)sender event:(UIEvent*)event;
Then paste these codes into your view controller:
-(void)buttonClicked:(UIBarButtonItem*)sender event:(UIEvent*)event{
[self becomeFirstResponder];
/*get the view from the UIBarButtonItem*/
UIView *buttonView=[[event.allTouches anyObject] view];
CGRect buttonFrame=[buttonView convertRect:buttonView.frame toView:self.view];
UIMenuController *menuController = [UIMenuController sharedMenuController];
UIMenuItem *resetMenuItem = [[UIMenuItem alloc] initWithTitle:#"Menu Item" action:#selector(menuItemClicked:)];
NSAssert([self becomeFirstResponder], #"Sorry, UIMenuController will not work with %# since it cannot become first responder", self);
[menuController setMenuItems:[NSArray arrayWithObject:resetMenuItem]];
[menuController setTargetRect:buttonFrame inView:self.view];
[menuController setMenuVisible:YES animated:YES];
[resetMenuItem release];
}
- (void) copy:(id) sender {
// called when copy clicked in menu
}
- (void) menuItemClicked:(id) sender {
// called when Item clicked in menu
}
- (BOOL) canPerformAction:(SEL)selector withSender:(id) sender {
if (selector == #selector(menuItemClicked:) /*|| selector == #selector(copy:)*/ /*<--enable that if you want the copy item */) {
return YES;
}
return NO;
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
The key is to return YES for canBecomeFirstResponder and canPerformAction.
Here's the sample project if you need it.
These codes are actually come from other posts in stackoverflow, I just combined them.
Figure out UIBarButtonItem frame in window?
How to get UIMenuController work for a custom view?

iOS: Pop-up menu does not behave in accordance with first responder set

I have several objects inheriting UIView in my application that are tracking taps on them and presenting Copy/Paste pop-up if they contain some specific data. When pop-up is presented I change the appearance of the object as well.
This is how it is implemented:
- (void)viewDidReceiveSingleTap:(NSNotification *)n {
MyObject *mo = (MyObject *)n.object;
[mo becomeFirstResponder];
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu update];
[menu setTargetRect:CGRectMake(...) inView:mo];
[menu setMenuVisible:YES animated:YES];
}
MyObject class, in turn, defines canBecomeFirstResponder: and canResignFirstResponder: as always returning YES. becomeFirstResponder:, resignFirstResponder:, and canPerformAction:withSender: is also defined accordingly (this is where I change the appearance of the object).
This is what goes wrong:
I tap object 1. The method viewDidReceiveSingleTap: above is getting called, and the object's canBecomeFirstResponder: and becomeFirstResponder: are getting called too. The pop-up menu is displayed as expected.
I tap another object 2. viewDidReceiveSingleTap: is called again and here's where the trouble starts. First, canResignFirstResponder:, resignFirstResponder: of object 1 are called, but not always, and I can't figure out the pattern. canBecomeFirstResponder: and becomeFirstResponder: of object 2 are called properly, but the pop-up menu does not relocate. It just disappears (though in the viewDidReceiveSingleTap: I clearly call setMenuVisible:YES). In order to make it appear, I have to tap object 2 (or any other) again -- in this case I can see from the debugger that object 2 was set as a first responder, it's just the pop-up that wasn't appearing.
What am I doing wrong? Any clues on relation between pop-up menu visibility and first responder?
The issue is that, when you tap anywhere except on the menu, it does an animated hide of itself. That hide is taking precedence over your animated show. So, if you tap a view to make the menu show, tap anywhere else (either on another one of those views or just anywhere else), and then tap another one of those views, the menu will show every time.
I think there's a good argument to be made for keeping that behavior, because it's the standard behavior that users will expect. But, of course, you have a better idea of what makes sense for your app. So, here's the trick:
[menu setMenuVisible:NO animated:NO];
[menu setMenuVisible:YES animated:YES];
Here's the code I used to try it out:
#implementation MenuView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor blueColor];
}
return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self becomeFirstResponder];
UIMenuController *menu = [UIMenuController sharedMenuController];
UIMenuItem *item = [UIMenuItem alloc] initWithTitle:#"Test"
action:#selector(test)];
NSArray *items = [NSArray arrayWithObject:item];
[item release];
[menu setMenuItems:items];
[menu setTargetRect:self.bounds inView:self];
[menu setMenuVisible:NO animated:NO];
[menu setMenuVisible:YES animated:YES];
}
- (void)test {
NSLog(#"Test!");
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
return action == #selector(test);
}
- (BOOL)canBecomeFirstResponder {
NSLog(#"Can become called");
return YES;
}
- (BOOL)canResignFirstResponder {
NSLog(#"Can resign called");
return YES;
}
- (BOOL)becomeFirstResponder {
[super becomeFirstResponder];
NSLog(#"Become called");
return YES;
}
- (void)dealloc {
[super dealloc];
}
#end

Customize UIMenuController

Hi I want to create a customize bubble menu, like cut/copy/paste menu, in IPhone SDK3.x. I know it is UIMenuController but it is only provide standard cut/copy/past menu. Anyone know how to make a bubble menu similar like this. Any example and code for reference?
1) you need to add custom menu items to the shared UIMenuController:
UIMenuItem* miCustom1 = [[[UIMenuItem alloc] initWithTitle: #"Custom 1" action:#selector( onCustom1: )] autorelease];
UIMenuItem* miCustom2 = [[[UIMenuItem alloc] initWithTitle: #"Custom 2" action:#selector( onCustom2: )] autorelease];
UIMenuController* mc = [UIMenuController sharedMenuController];
mc.menuItems = [NSArray arrayWithObjects: miCustom1, miCustom2, nil];
2) you need to implement your handler methods somewhere in the responder chain for the view that will be first-responder when you show the menu:
- (void) onCustom1: (UIMenuController*) sender
{
}
- (void) onCustom2: (UIMenuController*) sender
{
}
3) you optionally need to implement canPerformAction: in the responder chain for the view that will be first-responder when you show the menu:
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
if ( action == #selector( onCustom1: ) )
{
return YES; // logic here for context menu show/hide
}
if ( action == #selector( onCustom2: ) )
{
return NO; // logic here for context menu show/hide
}
if ( action == #selector( copy: ) )
{
// turn off copy: if you like:
return NO;
}
return [super canPerformAction: action withSender: sender];
}
4) if the view you want to present the menu for doesn't already support showing a menu, (i.e. a UIWebView will show a menu when the user does a long-tap, but a UILabel has no built in support for showing a menu), then you need to present the menu yourself. This is often done by attaching a UILongPressGestureRecognizer to the view, then showing the menu in the callback:
UILongPressGestureRecognizer* gr = [[[UILongPressGestureRecognizer alloc] initWithTarget: self action: #selector( onShowMenu: ) ] autorelease];
[_myview addGestureRecognizer: gr];
- (void) onShowMenu: (UIGestureRecognizer*) sender
{
[sender.view becomeFirstResponder];
UIMenuController* mc = [UIMenuController sharedMenuController];
CGRect bounds = sender.view.bounds;
[mc setTargetRect: sender.view.frame inView: sender.view.superview];
[mc setMenuVisible: YES animated: YES];
}
Note, there has to be a view that claims firstResponder for the menu to show.
5) make sure the view you're showing the menu for returns YES/TRUE to canBecomeFirstResponder. For example, if you try to make a UILabel a first responder it will return NO, so you would have to subclass it.
6) that's about it. You may want to resignFirstResponder when the action callback is called - but to do this you'll need to implement logic to discover the firstResponder.
Use the menuItems property on UIMenuController.