I referred various post on SO for the same issue. But still not able to get the solution.
I have sub-Class the UIButton where I am having UILongGestureRecognizer. My Implementation goes as below:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UILongPressGestureRecognizer *longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self addGestureRecognizer:longGesture];
}
return self;
}
- (BOOL)becomeFirstResponder
{
return YES;
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return YES;
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)longPress:(UILongPressGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan)
{
NSLog(#"ButtonView: longPress: event called");
UIMenuController *menu = [UIMenuController sharedMenuController];
if (![menu isMenuVisible])
{
ButtonView *btn = (ButtonView *)gesture.view;
if (![btn becomeFirstResponder])
{
NSLog(#"couldn't become first responder");
return;
}
UIMenuItem *menuItem = [[UIMenuItem alloc] initWithTitle:#"Change Color" action:#selector(changeColor:)];
UIMenuController *menuCont = [UIMenuController sharedMenuController];
menuCont.arrowDirection = UIMenuControllerArrowDown;
menuCont.menuItems = [NSArray arrayWithObject:menuItem];
if([btn canBecomeFirstResponder])
{
[menuCont setTargetRect:btn.frame inView:btn.superview];
[menuCont setMenuVisible:YES animated:YES];
NSLog(#"menu visible....");
}
}
}
if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled || gesture.state == UIGestureRecognizerStateFailed)
{
[self.layer setBorderColor:[UIColor clearColor].CGColor];
[self.layer setBorderWidth:0.0];
}
}
I have override becomeFirstResponder and canBecomeFirstResponder.
Important to Note: My Log message "menu visible...." is getting logged when I long pressed the Button, But I am not able to see the UIMenuController visible.
Is there anything that I am still missing in above code?? Thanks in advance.
Add this line before showing the menu:
[self becomeFirstResponder];
And remove this code:
- (BOOL)becomeFirstResponder
{
return YES;
}
Related
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 several pages on UIPageViewController. Each page is created from its own view, that has nested views. One of them is image. I want to have contol made in the way, that user can change pages by swipe on everything but image. Swipe on image will change images and not the page.
How can I achieve this? I have added UISwipeGestureRecognizer to the image, set userInteraction to YES, but swipe is send through and cause page turn.
I have this code in view load method (awakeFromNib)
UISwipeGestureRecognizer *swipeGestureRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(changeImageRight)];
[swipeGestureRight setDirection:UISwipeGestureRecognizerDirectionRight];
swipeGestureRight.delegate = self;
swipeGestureRight.numberOfTouchesRequired = 1;
[self.image addGestureRecognizer:swipeGestureRight];
Something like this should do the trick for you :
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (gestureRecognizer == self){
if ([otherGestureRecognizer isMemberOfClass:self.class]){
if ([self isGestureRecognizerInSuperviewHierarchy:otherGestureRecognizer]){
return YES;
} else if ([self isGestureRecognizerInSiblings:otherGestureRecognizer]){
return YES;
}
}
}
return NO;
}
- (BOOL) isGestureRecognizerInSiblings:(UIGestureRecognizer *)recognizer{
UIView *superview = self.view.superview;
NSUInteger index = [superview.subviews indexOfObject:self.view];
if (index != NSNotFound){
for (int i = 0; i < index; i++){
UIView *sibling = superview.subviews[i];
for (UIGestureRecognizer *viewRecognizer in sibling.gestureRecognizers){
if (recognizer == viewRecognizer){
return YES;
}
}
}
}
return NO;
}
- (BOOL) isGestureRecognizerInSuperviewHierarchy:(UIGestureRecognizer *)recognizer{
if (!recognizer) return NO;
if (!self.view) return NO;
//Check siblings
UIView *superview = self.view;
while (YES) {
superview = superview.superview;
if (!superview) return NO;
for (UIGestureRecognizer *viewRecognizer in superview.gestureRecognizers){
if (recognizer == viewRecognizer){
return YES;
}
}
}
}
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if ([gestureRecognizer respondsToSelector:#selector(gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:)]){
return [(id <UIGestureRecognizerDelegate>)gestureRecognizer gestureRecognizer:gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:otherGestureRecognizer];
}
return NO;
}
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
Having added a UITapGestureRecognized to a UIView, how do I parse the view for ie. changing the background color during the tap? The purpose of this is to imitate a button click.
UIView *locationView = [[UIView alloc] init];
locationView.tag = 11;
locationView.backgroundColor = [UIColor clearColor];
locationView.userInteractionEnabled = YES;
[locationView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(promptForLocation)]];
Let's say I want locationView.backgroundColor = [UIColor blueColor] right after the tap gesture. Would you just implement it in the target action or is there a specific implementation for this?
Update:
This is my final code inspired by #0x7fffffff
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ...
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDetectedLocation:)];
longPress.allowableMovement = 50.0f;
longPress.minimumPressDuration = 0.05;
UILongPressGestureRecognizer *longPress2 = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDetectedPhoto:)];
longPress2.allowableMovement = 50.0f;
longPress2.minimumPressDuration = 0.05;
[leftView addGestureRecognizer:longPress];
[rightView addGestureRecognizer:longPress2];
// ...
}
- (BOOL)longPressDetected:(UILongPressGestureRecognizer *)sender {
if ([self.view hasFirstResponder]) {
return NO;
}
if (sender.state == UIGestureRecognizerStateBegan) {
[sender.view setBackgroundColor:[UIColor colorWithRed:(4/255.0) green:(129/255.0) blue:(241/255.0) alpha:1]];
} else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed) {
[sender.view setBackgroundColor:[UIColor clearColor]];
}
CGPoint location = [sender locationInView:sender.view];
return sender.state == UIGestureRecognizerStateEnded && location.x > 0 && location.x < sender.view.frame.size.width && location.y > 0 && location.y < sender.view.frame.size.height;
}
- (void)longPressDetectedLocation:(UILongPressGestureRecognizer *)sender {
if ([self longPressDetected:sender]) {
[self promptForLocation];
}
}
- (void)longPressDetectedPhoto:(UILongPressGestureRecognizer *)sender {
if ([self longPressDetected:sender]) {
[self promptForPhoto];
}
}
Considering you're trying to imitate a button click, I'm assuming you'd want the view to revert to its original state after the touch ends. To do this, you'll want to use a UILongPressGestureRecognizer instead of a UITapGestureRecognizer.
With a tap gesture, the recognizer isn't detected until the touch ends, so you'd effectively be highlighting the view as soon as you lift your finger. To get around this, use the long press gesture its minimumPressDuration property set to 0.0. Then in its selector, check the state of the sending gesture; If it has just begun, change the background color, and if it has ended revert back to the original color.
Here's an example:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(100.0f, 100.0f, 100.0f, 100.0f)];
[myView setBackgroundColor:[UIColor redColor]];
[self.view addSubview:myView];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDetected:)];
[longPress setAllowableMovement:50.0f];
[longPress setMinimumPressDuration:0.0];
[myView addGestureRecognizer:longPress];
}
- (void)longPressDetected:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
[sender.view setBackgroundColor:[UIColor blueColor]];
}else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateFailed) {
[sender.view setBackgroundColor:[UIColor redColor]];
}
NSLog(#"%d",sender.state);
}
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tappedMethod:)];
[locationView addGestureRecognizer:gr];
This is method
-(void)tappedMethod:(UIGestureRecognizer *)ge
{
// write relavent code here;
locationView.backgroundColor = [UIColor blueColor];
}
In Apple iOs photos app, Each picture take the full screen, but when You tap on it, navigation bar and tab bar with some menu options (like share picture) just appear and remain for a couple of secconds. How can I do that in my UIImageView ?
Add a UITapGestureRecognizer to your view and UiView for your topbar and bottom bar or what else you like and follow below code. I think This may help you.
//Write below code in ViewDidLoad
UITapGestureRecognizer *singleTapOne = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
singleTapOne.numberOfTouchesRequired = 1; singleTapOne.numberOfTapsRequired = 1; singleTapOne.delegate = self;
[self.view addGestureRecognizer:singleTapOne]; [singleTapOne release];
for (UIGestureRecognizer *gR in self.view.gestureRecognizers) {
gR.delegate = self;
// handleSingleTap Method
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateRecognized)
{
CGRect viewRect = recognizer.view.bounds; // View bounds
CGPoint point = [recognizer locationInView:recognizer.view];
CGRect areaRect = CGRectInset(viewRect, TAP_AREA_SIZE, 0.0f); // Area
if (CGRectContainsPoint(areaRect, point)) // Single tap is inside the area
{
if ((m_CtrlViewTopBar.hidden == YES) || (m_CtrlViewBottomBar.hidden == YES))
{
[self showToolbar:m_CtrlViewTopBar];
[self showToolbar:m_CtrlViewBottomBar]; // Show
}
else
{
[self hideToolbar:m_CtrlViewTopBar];
[self hideToolbar:m_CtrlViewBottomBar];
}
return;
}
CGRect nextPageRect = viewRect;
nextPageRect.size.width = TAP_AREA_SIZE;
nextPageRect.origin.x = (viewRect.size.width - TAP_AREA_SIZE);
if (CGRectContainsPoint(nextPageRect, point)) // page++ area
{
//[self incrementPageNumber]; return;
}
CGRect prevPageRect = viewRect;
prevPageRect.size.width = TAP_AREA_SIZE;
if (CGRectContainsPoint(prevPageRect, point)) // page-- area
{
//[self decrementPageNumber]; return;
}
}
}
- (void)hideToolbar:(UIView*)view //Hide Toolbars
{
#ifdef DEBUGX
NSLog(#"%s", __FUNCTION__);
#endif
if (view.hidden == NO)
{
[UIView animateWithDuration:0.25 delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^(void)
{
view.alpha = 0.0f;
}
completion:^(BOOL finished)
{
view.hidden = YES;
}
];
}
[timer invalidate];
timer=nil;
}
- (void)showToolbar:(UIView*)view //Show Toolbars
{
#ifdef DEBUGX
NSLog(#"%s", __FUNCTION__);
#endif
if (view.hidden == YES)
{
[UIView animateWithDuration:0.25 delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^(void)
{
view.hidden = NO;
view.alpha = 1.0f;
}
completion:NULL
];
if (!timer) {
timer=[NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:#selector(HideToolBarWithTime)
userInfo:nil
repeats:YES];
}
}
[self.view addSubview:view];
}
-(void)HideToolBarWithTime //Hide Toolbars with time
{
[self hideToolbar:m_CtrlViewTopBar];
[self hideToolbar:m_CtrlViewBottomBar];
[timer invalidate];
timer=nil;
}
// Gesture Delegates
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}