I am trying to use UIMenuCnotroller to show a list of dynamically generated items, they share the same action method, and so I need to know which item is selected in the single action method.
However, in the action method - (void)menuItemAction:(id)sender;the sender is actually the UIMenuController object, and I didn't find any method of UIMenuController can tell me which menuitem is selected.
One solution I can think of is to dynamically generate different action selectors for different items, and do some tricks in forwardInvocation
But is there any easier way?
You can use UIMenuCnotroller like:
1) creation:
UIMenuController *menuController = [UIMenuController sharedMenuController];
UIMenuItem *open = [[UIMenuItem alloc] initWithTitle:#"Open" action:#selector(open:)];
UIMenuItem *reDownload = [[UIMenuItem alloc] initWithTitle:#"Re-Download" action:#selector(reDownload:)];
[menuController setMenuItems:[NSArray arrayWithObjects:open, reDownload, nil]];
[menuController setTargetRect:cell.frame inView:self.view];
[menuController setMenuVisible:YES animated:YES];
[open release];
[reDownload release];
2) To catch actions should implement next methods:
- (BOOL) canPerformAction:(SEL)selector withSender:(id) sender
{
if (selector == #selector(open:))
{
return YES;
}
if (selector == #selector(reDownload:))
{
return YES;
}
return NO;
}
- (BOOL) canBecomeFirstResponder
{
return YES;
}
3) And realization of yours methods:
- (void) open:(id) sender
{
[self doSomething];
}
- (void) reDownload:(id) sender
{
[self doSomething];
}
Hope, this helps.
Okay, I've solved this one. It involves messing with [NSObject forwardInvocation:] and is a bit dirty, but the resulting code is quite minimal. Answered over here: https://stackoverflow.com/a/9874092/790036
One easiest way would be to use different #selector method for each menu item
Examples:
UIMenuItem *oneObj = [[UIMenuItem alloc] initWithTitle:#"One" action:#selector(One:)];
UIMenuItem *twoObj = [[UIMenuItem alloc] initWithTitle:#"Two" action:#selector(Two:)];
Related
How to remove default menu items like copy, past, SelectAll when i select WebView content. How to put custom actions in middle of default menu items.Which items i'm putting these are showing in last, i want to my custom actions from starting.
I'm using below code in view didAppear method.
UIMenuItem *customMenuItem1=[[UIMenuItem alloc] initWithTitle:#"Highlight" action:#selector(customAction1:)];
UIMenuItem *customMenuItem2=[[UIMenuItem alloc] initWithTitle:#"UnHighlight" action:#selector(UnHighlighted:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects:customMenuItem1,customMenuItem2,nil]];
[UIMenuController sharedMenuController].menuVisible=YES;
Please help me.
As answered here showing custom menu on selection in UIWebView in iphone
- (void)viewDidLoad {
[super viewDidLoad];
[self.webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"https://www.google.com"]]];
self.webview.backgroundColor = [UIColor blueColor];
// Do any additional setup after loading the view.
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIMenuItem *customMenuItem1 = [[UIMenuItem alloc] initWithTitle:#"Custom 1" action:#selector(customAction1:)];
UIMenuItem *customMenuItem2 = [[UIMenuItem alloc] initWithTitle:#"Custom 2" action:#selector(customAction2:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects:customMenuItem1, customMenuItem2, nil]];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[[UIMenuController sharedMenuController] setMenuItems:nil];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (self.webview.superview != nil)// && ![urlTextField isFirstResponder])
{
if (action == #selector(customAction1:) || action == #selector(customAction2:))
{
return YES;
}
}
return [super canPerformAction:action withSender:sender];
}
-(void)customAction1:(UIMenuItem*)item
{}
-(void)customAction2:(UIMenuItem*)item
{}
In my app users can send messages to each other. I use UITextView inside of a bubble image to display the chat history.
[messageTextView setFrame:CGRectMake(padding, padding+5, size.width, size.height+padding)];
[messageTextView sizeToFit];
messageTextView.backgroundColor=[UIColor clearColor];
UIImage *img = [UIImage imageNamed:#"whiteBubble"];
UIImageView *bubbleImage=[[UIImageView alloc] initWithImage:[img stretchableImageWithLeftCapWidth:24 topCapHeight:15]];
messageTextView.editable=NO;
[bubbleImage setFrame:CGRectMake(padding/2, padding+5,
messageTextView.frame.size.width+padding/2, messageTextView.frame.size.height+5)];
[cell.contentView addSubview:bubbleImage];
[cell.contentView addSubview:messageTextView];
Currently, when a user holds down on the message text, they see the 'Copy' and 'Define' options with cursors to select text.
However, I would rather have the basic iOS messaging option of holding down on a chat bubble to copy the entire message. How can this be achieved?
I would subclass UITextView to implement your own version of the copy menu. You can do it a number of ways, but one possible way is like below.
The basic idea is that the text view sets up a UILongPressGestureRecognizer that will create the popup menu when a long press is detected.
UILongPressGestureRecognizer has several default system menus that will show up unless you tell them not to. The way to do that is to return NO for any selectors that you don't want to handle in canPerformAction:withSender:. In this case, we're returning NO for any selector except for our custom copyText: selector.
Then that selector just gets a reference to the general UIPasteboard and sets it's text to the text of the TextView.
In your subclass's implementation:
#implementation CopyTextView
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (void)setup {
self.editable = NO;
self.selectable = NO;
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDetected:)];
longPress.minimumPressDuration = 0.3f; // however long, in seconds, you want the user to have to press before the menu shows up
[self addGestureRecognizer:longPress];
}
- (void)longPressDetected:(id)sender {
[self becomeFirstResponder];
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
if (longPress.state == UIGestureRecognizerStateEnded) {
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:#"Copy" action:#selector(copyText:)];
UIMenuController *menuCont = [UIMenuController sharedMenuController];
[menuCont setTargetRect:self.frame inView:self.superview];
menuCont.arrowDirection = UIMenuControllerArrowDown;
menuCont.menuItems = [NSArray arrayWithObject:menuItem];
[menuCont setMenuVisible:YES animated:YES];
}
}
- (BOOL)canBecomeFirstResponder { return YES; }
- (void)copyText:(id)sender {
UIPasteboard * pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setString:self.text];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == #selector(copyText:)) return YES;
return NO;
}
#end
Useful documentation:
UILongPressGestureRecognizer Documentation
UIMenuController Documentation
I have 2 viewControllers and in first one I'm using tapRecognizer to press and hold in order to show a UImenucontroller to copy a string. The tap is used for selecting the title on navigation bar, and it shows a UImenucontroller with copy item on it.
It works for the first time, but when user switch to another view controller and come back to the first view controller again, the menu does not show any more.
-(void)viewDidLoad{
[super viewDidLoad];
UIView *viewWithTitleLabel = self.navigationController.navigationBar.subviews[1];
viewWithTitleLabel.userInteractionEnabled = YES;
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(topBarTitleTap:)];
[viewWithTitleLabel addGestureRecognizer:longPress];
}
-(void)topBarTitleTap:(UILongPressGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
UIMenuController *menuController = [UIMenuController sharedMenuController];
[menuController setTargetRect:CGRectMake(CGRectGetMidX([self.view bounds]), -12.0, 0.0f, 0.0f) inView:self.view];
[menuController setMenuVisible:YES animated:YES];
}
}
- (void) copy:(id) sender {
// called when copy clicked in tab bar title
NSString *copyStringverse = self.navigationItem.title;
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setString:copyStringverse];
}
- (BOOL) canBecomeFirstResponder {
return YES;
}
Add [self becomeFirstResponder]; before pop UIMenuController
For example you can change your code as follow
-(void)topBarTitleTap:(UILongPressGestureRecognizer *)gestureRecognizer
{
[self becomeFirstResponder];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
UIMenuController *menuController = [UIMenuController sharedMenuController];
[menuController setTargetRect:CGRectMake(CGRectGetMidX([self.view bounds]), -12.0, 0.0f, 0.0f) inView:self.view];
[menuController setMenuVisible:YES animated:YES];
}
}
And don't forget to implement
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
//Customize your action if statement here
return YES;
}
For your viewcontroller
Check if LongPressGestureRecognizer is working, every time.
I would place the gesturerecognizer code in viewDidAppear instead of ViewDidLoad, just to be safe
I would like the behaviour is that:
When I click on my custom UIMenuItem, it would show another list of UIMenuItems to choose.
I have implemented in that way.
#implementation CustomUIWebView{
BOOL clickedShowSubMenu;
NSArray *mainMenuItems;
NSArray *subMenuItems;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
UIMenuController *mc = [UIMenuController sharedMenuController];
UIMenuItem *testSubMenu = [[UIMenuItem alloc] initWithTitle:#"TestSubMenu" action:#selector(testSubMenu:)];
mainMenuItems = [NSArray arrayWithObjects:testSubMenu, nil];
UIMenuItem *subItem1 = [[UIMenuItem alloc] initWithTitle:#"Item1" action:#selector(item1:)];
UIMenuItem *subItem2 = [[UIMenuItem alloc] initWithTitle:#"Item2" action:#selector(item2:)];
subMenuItems = [NSArray arrayWithObjects:subItem1, subItem2, nil];
[mc setMenuItems:mainMenuItems];
[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onMenuItemDismissed:) name:UIMenuControllerDidHideMenuNotification object:nil];
clickedShowSubMenu = NO;
}
return self;
}
- (void)testSubMenu:(id)sender{
UIMenuController *mc = [UIMenuController sharedMenuController];
[mc setMenuItems:subMenuItems];
mc.menuVisible = YES; //from te doc, it say it would keep the menu not dismiss when this function end
clickedShowSubMenu = YES;
}
- (void)item1:(id)sender{
}
- (void)item2:(id)sender{
}
- (void)onMenuItemDismissed:(id)sender{
if(clickedShowSubMenu == YES){
UIMenuController *mc = [UIMenuController sharedMenuController];
[mc setMenuItems:mainMenuItems];
clickedShowSubMenu = NO;
}
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{
if(action == #selector(testSubMenu:))return YES;
else if(action == #selector(item1:))return YES;
else if(action == #selector(item2:))return YES;
return NO;
}
#end
It works when I select long touch some text and click the Test Sub Menu, it did appear the "subMenu": Item1 and Item2 on the same position that Test Sub Menu appear.
However, the problem is the second time and later. I select other text and the Test Sub Menu appear correctly, but when I click it ,the Item1 and Item2 is showed on the OLD position that it first appear. Their position is never changed. I do not know what cause this.
EDIT
Ok. By making use of How to get coordinates (CGRect) for the selected text in UIWebView? to get the rect boundary of the selection and say [mc setTargetRect:theRect inView:self]; before mc.menuVisible = YES;
It work and the sub menu show approximate same position of the main menu. Is it recommended? There is still little problem that when I highlight a whole paragraph, the main menu is show UNDER the paragraph, however, the sub menu show ABOVE the paragraph.
I'm writing an application that uses webkit to read epubs. When I'm selecting text by long press , the menu bar opens, and there should only be Facebook, and Twitter buttons. So here is my code how do I did it :
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == #selector(facebookItemClicked:) || action == #selector(twitterItemClicked:)) {
return YES;
}else if (action == #selector(copy:)){
NSLog(#"copy");
} return NO;}
Here is how I initialize menu Items
UIMenuItem *facebookMenuItem = [[UIMenuItem alloc] initWithTitle:#"Facebook" action:#selector(facebookItemClicked:)];
UIMenuItem *twitterMenuItem = [[UIMenuItem alloc] initWithTitle:#"Twitter" action:#selector(twitterItemClicked:)];
But the problem is that copy selector never appears it as action so I cannot catch it and every time menu bar is shown there is also a copy button beside Facebook and Twitter.
It would be great if someone could help me.
Thanks in advance.
Solved problem.
Default UIWebView was overriding my menuBar actions. So I created a class CustomWebView inherited from UIWebView added
+ (void)initialize{
UIMenuItem *itemA = [[UIMenuItem alloc] initWithTitle:#"A" action:#selector(a:)];
UIMenuItem *itemB = [[UIMenuItem alloc] initWithTitle:#"B" action:#selector(b:)];
[[UIMenuController sharedMenuController] setMenuItems:[NSArray arrayWithObjects:itemA, itemB, nil]];
// [itemA release];
// [itemB release];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
BOOL can = [super canPerformAction:action withSender:sender];
if (action == #selector(a:) || action == #selector(b:))
{
can = YES;
}
if (action == #selector(copy:))
{
can = NO;
}
NSLog(#"%# perform action %# with sender %#.", can ? #"can" : #"cannot", NSStringFromSelector(action), sender);
return can;
}
which overrides menubar actions in webview and then I use this CustomWebView in other classes that use webvew.