Customize UIMenuController - iphone

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.

Related

Objective-C how to disable user interaction selectively

I have a Main View Controller that has many subviews. What I want is to disable all other views except one subview and its subviews programmatically from the subview file. But all I get is all frozen views. What did I do wrong?
I tried this code:
#define kDontDisableUserInteraction 321
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"initWithFrame");
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.tag = kDontDisableUserInteraction;
}
return self;
}
-(void)something{
MVC *myController = [self getMVC];
for (UIView* subview in myController.view.subviews) {
NSLog(#"subview.tag %i", subview.tag);
if (subview.tag != kDontDisableUserInteraction){
subview.userInteractionEnabled = NO;
}
}
for (UIView *view in self.subviews){
NSLog(#"enabled!");
view.userInteractionEnabled = YES;
}
}
- (MVC *)getMVC {
Class vcc = [MVC class]; // Called here to avoid calling it iteratively unnecessarily.
UIResponder *responder = self;
while ((responder = [responder nextResponder])) if ([responder isKindOfClass: vcc]) return (MVC *)responder;
return nil;
}
Following links may be helpful:
How to disable touch input to all views except the top-most view?
UIView -- "user interaction enabled" false on parent but true on child?
I solved it by applying a full screen of a button on all other views and get the one view that I want to have user interaction upon the button. This way I disallow the user to click on any function except the one view I want the user to click on certain functions.

iphone:Pop up button in UIWebView

I want to put pop up button like Email in UIWebView.In my app i have created ebook
in webView now whenever i click(long press) on index page link it will create the pop up
window with open and copy button as shwon as below:
Like this i want to put another button like Email and Print.How to create another button in pop up window in WebView?Thanks in advance!
The popup you refer to is called a UIMenuController. You can access the [UIMenuController sharedMenuController] method to get the menu controller. You can then add your own UIMenuItems to the menu controller.
UIMenuItem* myBtn1 = [[[UIMenuItem alloc] initWithTitle: #"Button 1" action:#selector( onButton1: )] autorelease];
UIMenuItem* myBtn2 = [[[UIMenuItem alloc] initWithTitle: #"Button 2" action:#selector( onButton2: )] autorelease];
UIMenuController* mc = [UIMenuController sharedMenuController];
mc.menuItems = [NSArray arrayWithObjects: myBtn1, myBtn2, nil];
Now implement the methods
- (void) onButton1: (UIMenuController*) sender
{
}
- (void) onButton2: (UIMenuController*) sender
{
}
For more detail refer apple's Doc.
Edit
you can implement Long Gesture
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];
}
you can create a View Controller, and in the view controller's .xib file you can add your buttons.
then you can call that viewcontroller in the UIPopover..and load that view controller in it.
Now as you want to show this popover in the index page only, for this you have to keep a track of pages like pages form 1-3 are of index page so the popOver should be seen on that page.
and when you click on the index link you can use the webView delegate function i.e
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
under this function you can apply your logic and initiate the popover that will be seen

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

UIMenuController not showing up

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