I have UIWebView for displaying some articles. I need to select some text from UIWebView and use bookmark. So i'm using selection = [wbCont stringByEvaluatingJavaScriptFromString:#"window.getSelection().toString()"]; But when i longpress the UIMenuItem displays with copy,define. I read some doc and used canPerformAction: copy NO. But still it displaying.
- (void)viewDidLoad
{
[wbCont loadHTMLString:webString baseURL:nil];
[self.view addSubview:wbCont];
NSMutableArray *items = [[[UIMenuController sharedMenuController] menuItems] mutableCopy];
if (!items) items = [[NSMutableArray alloc] init];
UIMenuItem *menuItem;
menuItem = [[UIMenuItem alloc] initWithTitle:#"BookMark" action:#selector(book:)];
[items addObject:menuItem];
[menuItem release];
menuItem = [[UIMenuItem alloc] initWithTitle:#"Note" action:#selector(note:)];
[items addObject:menuItem];
[menuItem release];
[[UIMenuController sharedMenuController] setMenuItems:items];
[items release];
}
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(copy:))
{
return NO;
}
if (action == #selector(book:))
{
return YES;
}
else if (action == #selector(note:))
{
return YES;
}
return [super canPerformAction:action withSender:sender];
}
You have to subclass UIWebView. (Create a new Objective-C class and select subclass of UIWebView).
Within your subclass write the method:
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender{
if (action == #selector(copy:))
{
return NO;
}
return [super canPerformAction:action withSender:sender];
}
You don't need to set you own custom selectors there if you are adding them inside your contoller (as I guess that's what you where doing).
More details can be found here: How do you REALLY remove Copy from UIMenuController
I have two UIViewController, one is the main and from this trough a button you can go to the second. In SecondView.m I have the following code:
- (IBAction)showpopup:(id)sender {
[self becomeFirstResponder];
UIMenuController *sharedController = [UIMenuController sharedMenuController];
UIMenuItem *x2 = [[UIMenuItem alloc] initWithTitle:#"2x2" action: #selector(mat)];
UIMenuItem *x3 = [[UIMenuItem alloc] initWithTitle:#"3x3" action: #selector(mat)];
UIMenuItem *x4 = [[UIMenuItem alloc] initWithTitle:#"4x4" action: #selector(mat)];
UIMenuItem *x5 = [[UIMenuItem alloc] initWithTitle:#"5x5" action: #selector(mat)];
NSArray *menuArray = [NSArray arrayWithObjects: x2,x3,x4,x5, nil];
CGRect drawRect = [sender convertRect:[sender bounds] toView: self.view];
[sharedController setTargetRect:drawRect inView: self.view];
[sharedController setMenuItems:menuArray];
[sharedController setMenuVisible:YES animated:YES];
[sharedController setMenuItems: nil];
}
-(BOOL)canBecomeFirstResponder{
return YES;
}
-(int)mat:(id)sender{
return 0;
}
The Button is linked as "touch up inside", but when I run the UIMenuController doesn't show up.
The exact same code works in the main UIViewController.
Thanks
If I am not missing anything, I think you should, e.g., add your sharedController.view as a subview to your mainController.view, e.g. (assuming that `showpopup is defined in your main controller):
- (IBAction)showpopup:(id)sender {
[self becomeFirstResponder];
UIMenuController *sharedController = [UIMenuController sharedMenuController];
...
[sharedController setMenuItems:menuArray];
[sharedController setMenuVisible:YES animated:YES];
[sharedController setMenuItems: nil];
[self.view addSubview:sharedController.view];
}
or you could present modally your sharedController (replace the addSubview line above):
[self presentViewController:sharedController animated:YES completion:nil];
In any case, it seems to me that the "presenting" bit is missing.
I'm trying to display a custom UIMenuController from a UIImageView, which is embedded in a UITextView.
How do I achieve this?
I get a menu displaying with Cut, Copy, Paste, Select, Select All, shown in the image below:
Only if I tap more do I get what I want:
Here's some of my code:
#interface UIImageView (Extended)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (BOOL)canBecomeFirstResponder;
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender;
#end
#implementation UIImageView (Extended)
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == #selector(copy:) || action == #selector(paste:) || action == #selector(cut:) || action == #selector(delete:) ||
action == #selector(select:) || action == #selector(selectAll:)) {
return YES;
}
return NO;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.nextResponder touchesBegan:touches withEvent:event];
}
#end
- (void)showMenuController {
UIMenuController *sharedController = [UIMenuController sharedMenuController];
UIMenuItem *moreOptionsItem = [[UIMenuItem alloc] initWithTitle:#"More options" action:#selector(imageMenuItemPressedMore:)];
UIMenuItem *cut = [[UIMenuItem alloc] initWithTitle:#"Cut" action:#selector(cutImageItemPressed:)];
UIMenuItem *copy_item = [[UIMenuItem alloc] initWithTitle:#"Copy" action:#selector(copyImagePressed:)];
UIMenuItem *paste = [[UIMenuItem alloc] initWithTitle:#"Paste" action:#selector(pasteImagePressed:)];
UIMenuItem *crop = [[UIMenuItem alloc] initWithTitle:#"Crop" action:#selector(cropImagePressed:)];
sharedController.menuItems = [NSArray arrayWithObjects:cut, copy_item, paste, crop, moreOptionsItem, nil];
[moreOptionsItem release];
[cut release];
[copy_item release];
[paste release];
[crop release];
[sharedController setTargetRect:CGRectMake(theView.frame.size.width/2, 0, 0, 0) inView:theView];
[sharedController setMenuVisible:YES animated:YES];
}
What am I doing wrong?
Any help appreciated.
I'm trying to display a custom UIMenuController when a User long presses on a cell in a grouped UITableView. However, I can't seem to get the UIMenuController to display after successfully detecting the long press. Any help is greatly appreciated.
MyViewController.h
#interface MyViewController : UIViewController <UITableViewDelegate,UITableViewDataSource>
UITableView *table;
#property (nonatomic, retain) IBOutlet UITableView *table;
#end
In the cellForRowAtIndexPath I attach my Long Press Gesture Recognizer
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:SectionsTableIdentifier] autorelease];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
[cell addGestureRecognizer:longPress];
[longPress release];
Here is my handleLongPress action method
-(void)handleLongPress:(UIGestureRecognizer *)longPress {
if (longPress.state == UIGestureRecognizerStateBegan) {
CGPoint pressLocation = [longPress locationInView:self.table];
NSIndexPath *pressedIndexPath = [self.table indexPathForRowAtPoint:pressLocation];
UIMenuItem *first = [[UIMenuItem alloc] initWithTitle:#"Save" action:#selector(saveRecent)];
UIMenuItem *second = [[UIMenuItem alloc] initWithTitle:#"Edit" action:#selector(editQuery)];
UIMenuController *menuController = [UIMenuController sharedMenuController];
menuController.menuItems = [NSArray arrayWithObjects:first,second,nil];
[menuController setTargetRect:longPress.view.frame inView:longPress.view.superview];
[menuController setMenuVisible:YES animated:YES];
[pressedIndexPath release];
}
}
The Action methods for the Edit and Save just display a UIAlertView. I also implemented the below method to ensure that when the UIMenuController is displayed only Save and Edit options will be present
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
BOOL canPerform = NO;
if (action == #selector(saveRecent)) {
canPerform = YES;
}
if (action == #selector(editQuery)) {
canPerform = YES;
}
return canPerform;
}
I'm also claiming MyViewController to be first responder
-(BOOL)canBecomeFirstResponder {
return YES;
}
I believe you need to have a view claiming firstResponder status in order to present the UIMenuController. I don't see that happening in your code.
I wrote up directions for using the UIMenuController as an answer to this question:
Customize UIMenuController
I'm trying to get the following code work:
UIMenuController * menu = [UIMenuController sharedMenuController];
[menu setTargetRect: CGRectMake(100, 100, 100, 100) inView: self.view];
[menu setMenuVisible: YES animated: YES];
The menu instance is ready but it doesn't show - the width is always zero.
Or is there some sample code on this UIPasteboard/UIMenuController topic?
I was not able to get it working even when I read all of your answers. I'm presenting ready code that will work for everyone.
Let's say we have a controller class named Controller. You can simply paste the following code to this controller to have the menu working on its view:
- (void)loadView {
[super loadView];
UILongPressGestureRecognizer *gr = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.view addGestureRecognizer:gr];
}
- (void) longPress:(UILongPressGestureRecognizer *) gestureRecognizer {
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
CGPoint location = [gestureRecognizer locationInView:[gestureRecognizer view]];
UIMenuController *menuController = [UIMenuController sharedMenuController];
UIMenuItem *resetMenuItem = [[UIMenuItem alloc] initWithTitle:#"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:CGRectMake(location.x, location.y, 0.0f, 0.0f) inView:[gestureRecognizer view]];
[menuController setMenuVisible:YES animated:YES];
}
}
- (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:)) {
return YES;
}
return NO;
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
What has to be done in order for menu to work is that the firstResponder(in our case our controller - see line with [self becomeFirstResponder]) has to be able to become first responder (override method canBecomeFirstResponder cause default implementation returns NO) as well as - (BOOL) canPerformAction:(SEL)selector withSender:(id) sender which should return YES to any action that can be performed by firstResponder
If you're implementing a custom view and that view is supposed to be the responder (as opposed to some other view, like a UITextField), you need to override the canBecomeFirstResponder function in your view and return YES:
- (BOOL)canBecomeFirstResponder {
return YES;
}
then, when you're displaying the menu, you should do something like the following:
- (void)myMenuFunc {
if (![self becomeFirstResponder]) {
NSLog(#"couldn't become first responder");
return;
}
UIMenuController *theMenu = [UIMenuController sharedMenuController];
CGRect selectionRect = CGRectMake(0, 0, 0, 0);
[theMenu setTargetRect:selectionRect inView:self];
[theMenu setMenuVisible:YES animated:YES];
}
In case somebody still has problems: My menu used to work and some day stopped working miraculously. Everything else in my app still worked. Now I had removed the [window makeKeyAndVisible] method from application:didFinishLaunchingWithOptions: method, and while everything else still worked, this breaks UIMenuController!
Stupid error on my side, difficult to find the culprit...
to display UIMenuController one has to add following
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(cut:))
return NO;
else if (action == #selector(copy:))
return YES;
else if (action == #selector(paste:))
return NO;
else if (action == #selector(select:) || action == #selector(selectAll:))
return NO;
else
return [super canPerformAction:action withSender:sender];
}
I think Cam is right, need override both canPerformAction and canBecomeFirstResponder
- (BOOL) canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(doSomething:)) {
return YES;
}
return NO;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
// MyView.h
#interface MyView : UIView {
IBOutlet UITextField * textField_;
}
#end
// MyView.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"show menu");
[textField_ becomeFirstResponder];
// [self.window becomeFirstResponder];
UIMenuController * menu = [UIMenuController sharedMenuController];
[menu setTargetRect: CGRectMake(0, 0, 100, 10) inView: self];
[menu setMenuVisible: YES animated: YES];
NSLog(#"menu width %f, visible %d", menu.menuFrame.size.width, menu.menuVisible);
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
return YES;
}
You need to add more code in canPerformAction:withSender: - it should check the pasteboard and your selection state. Apple's iPhone Application Programming Guide provide several code snippets.
Don't you have to add the UIMenuController* menu to the main or subview, E.G. self.view?
I think it's something like [self.view addSubView:menu.view]; Or am I missing the point of your question. You might also want to set the frame of the menu's view.
UIMenuController doesn't have a view. I just searched some code from apple's iPhone Application Programming Guide: Event Handling:
Listing 3-4 Displaying the editing menu
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *theTouch = [touches anyObject];
if ([theTouch tapCount] == 2 && [self becomeFirstResponder]) {
// selection management code goes here...
// bring up editing menu.
UIMenuController *theMenu = [UIMenuController sharedMenuController];
CGRect selectionRect = CGRectMake(currentSelection.x, currentSelection.y, SIDE, SIDE);
[theMenu setTargetRect:selectionRect inView:self];
[theMenu setMenuVisible:YES animated:YES];
}
}
I did it in the following way below. Just call the method that shows the menu after very short delay in init. I didn't want to call it from View Controller and also didn't find an event that indicates that my custom view appeared and i'm ready to show menu. So this way OK from my perspective. Delay can be less, but its up to you.
#implementation DTSignatureImageView
- (id)initWithImage:(UIImage *)image
{
self = [super initWithImage:image];
if(self){
self.contentMode = UIViewContentModeScaleAspectFit;
self.frame = CGRectMake(0, 0, image.size.width / 2.5, image.size.height / 2.5);
self.userInteractionEnabled = YES;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(signatureDidPan:)];
[self addGestureRecognizer:pan];
[self becomeFirstResponder];
[self performSelector:#selector(showMenu) withObject:nil afterDelay:0.5];
}
return self;
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)showMenu
{
UIMenuController *menu = [UIMenuController sharedMenuController];
menu.menuItems = #[
[[UIMenuItem alloc] initWithTitle:#"Apply" action:#selector(applySignature)],
[[UIMenuItem alloc] initWithTitle:#"Update" action:#selector(updateSignature)],
[[UIMenuItem alloc] initWithTitle:#"Clear" action:#selector(delegateSignature)]];
[menu setTargetRect:self.bounds inView:self];
[menu setMenuVisible:YES animated:YES];
}
- (NSArray *)menuActions
{
static NSArray *actions = nil;
if (actions == nil){
actions = #[
NSStringFromSelector(#selector(applySignature)),
NSStringFromSelector(#selector(updateSignature)),
NSStringFromSelector(#selector(delegateSignature))];
}
return actions;
}
- (void) signatureDidPan: (UIPanGestureRecognizer *)gesture
{
switch (gesture.state) {
case UIGestureRecognizerStateBegan: {
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
break;
}
case UIGestureRecognizerStateEnded: {
[self becomeFirstResponder];
[self showMenu];
}
default:
break;
}
CGPoint point = [gesture locationInView:gesture.view.superview];
gesture.view.center = point;
}