UIMenuController hides the keyboard
Asked Answered
V

3

6

I currently have an application which is for chatting. I used a UItextField for input box and bubbles for display messages, some thing like the system SMS. I want to enable copy paste on the message bubbles (labels). The problem is, when I want to show the UIMenuController, the label which i need to copy from need to become first responder. If the keyboard is currently displayed, when the label become first responder, the textfield will lost focus, thus the keyboard will be hide automatically. this cause an UI scroll and feels not good. Is there anyway that i can keep the keyboard shown even when i need to show the menu?

enter image description here

enter image description here

Vivisectionist answered 28/11, 2012 at 9:20 Comment(4)
It seems same issues are... 1. #8380873 2. https://mcmap.net/q/595179/-determine-when-a-uimenucontroller-is-dismissedGat
No , when I select the cell using long prees gesture (when keyboard is active) the UImenucontroller appears but keyboard ges away I want the keyboard to be on screen.Vivisectionist
When showing menu on long press, you have to write code something like, [sender.view becomeFirstResponder]; to maintain keyboard status.Gat
its not woorking for me , the keyboard is disappearing when UIMenuController is coming on longpressgesture a rowVivisectionist
H
2

You can try to subclass your uitextfield and override the firstresponder. Check in your long press gesture handler if the uitextfield is the first responder and override the nextresponder.

Horvath answered 28/11, 2012 at 12:41 Comment(4)
Something of this sort: @interface InputTextField : UITextField @property (nonatomic, weak) UIResponder *inputNextResponder; @end @implementation InputTextField @synthesize inputNextResponder; - (UIResponder *)nextResponder { if (inputNextResponder != nil) return inputNextResponder; else return [super nextResponder]; } @end The formatting is a bit screwed up :( but I guess you will get the point. Please accept my answer if if helps you solve your problem. HTH.Horvath
No its not helping in this situationVivisectionist
Could you tell what have you implemented exactly?Horvath
class TIField: UITextView { weak var overrideNextResponder: UIResponder? override var next: UIResponder? { get { if overrideNextResponder != nil { return overrideNextResponder } else { return super.next } } } override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if overrideNextResponder != nil { return false } else { return super.canPerformAction(action, withSender: sender) } } }Huihuie
P
12

For those who still looking for answer here is code (main idea belongs to neon1, see linked question).

The idea is following: if a responder doesn't know how to handle given action, it propogates it to the next responder in chain. Until now we have two candidates for first responders:

  1. Cell
  2. TextField

Each of them have separate chain of responders (in fact, no, they do have common ancestor, so their chains have something in common, but we cannot use it):

UITextField <- UIView <- ... <- UIWindow <- UIApplication
UITableViewCell <- UIView <- ... <- UIWindow <- UIApplication

So we would like to have following chain of reponders:

UITextField <- UITableViewCell <- ..... <- UIWindow <- UIApplication

We need to subclass UITextField (code is taken from here):

CustomResponderTextView.h

@interface CustomResponderTextView : UITextView
@property (nonatomic, weak) UIResponder *overrideNextResponder;
@end

CustomResponderTextView.m

@implementation CustomResponderTextView

@synthesize overrideNextResponder;

- (UIResponder *)nextResponder {
    if (overrideNextResponder != nil)
        return overrideNextResponder;
    else
        return [super nextResponder];
}

@end

This code is very simple: it returns real responder in case we haven't set any custom next responder, otherwise returns our custom responder.

Now we can set new responder in our code (my example adds custom actions):

CustomCell.m

@implementation CustomCell
- (BOOL) canBecomeFirstResponder {
    return YES;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    return (action == @selector(copyMessage:) || action == @selector(deleteMessage:));
}
@end

- (void) copyMessage:(id)sender {
   // copy logic here
}

- (void) deleteMessage:(id)sender {
   // delete logic here
}

Controller

- (void) viewDidLoad {
    ...
    UIMenuItem *copyItem = [[UIMenuItem alloc] initWithTitle:@"Custom copy" action:@selector(copyMessage:)];
    UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@"Custom delete" action:@selector(deleteMessage:)];
    UIMenuController *menu = [UIMenuController sharedMenuController];
    [menu setMenuItems:@[copyItem, deleteItem]];
    ...
}

- (void) longCellTap {
    // cell is UITableViewCell, that has received tap
    if ([self.textField isFirstResponder]) {
        self.messageTextView.overrideNextResponder = cell;
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(menuDidHide:) name:UIMenuControllerDidHideMenuNotification object:nil];
    } else {
        [cell becomeFirstResponder];
    }
}

- (void)menuDidHide:(NSNotification*)notification {
    self.messageTextView.overrideNextResponder = nil;
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIMenuControllerDidHideMenuNotification object:nil];
}

Last step is making first responder (in our case text field) propogate copyMessage: and deleteMessage: actions to next responder (cell in our case). As we know iOs sends canPerformAction:withSender: to know, if given responder can handle the action.

We need to modify CustomResponderTextView.m and add the following function:

CustomResponderTextView.m

...
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    if (overrideNextResponder != nil)
        return NO;
    else
        return [super canPerformAction:action withSender:sender];
}
...

In case we've set our custom next responder we send all actions to it (you can modify this part, if you need some actions on textField), otherwise we ask our supertype if it can handles it.

Powel answered 24/5, 2014 at 21:48 Comment(2)
@nikita: Thanks for your post. but in my case still not working can you please help me out to fix this issue.Klatt
@nikita: Thanks for your post. but in my case still not working can you please help me out to fix this issueHuihuie
H
2

You can try to subclass your uitextfield and override the firstresponder. Check in your long press gesture handler if the uitextfield is the first responder and override the nextresponder.

Horvath answered 28/11, 2012 at 12:41 Comment(4)
Something of this sort: @interface InputTextField : UITextField @property (nonatomic, weak) UIResponder *inputNextResponder; @end @implementation InputTextField @synthesize inputNextResponder; - (UIResponder *)nextResponder { if (inputNextResponder != nil) return inputNextResponder; else return [super nextResponder]; } @end The formatting is a bit screwed up :( but I guess you will get the point. Please accept my answer if if helps you solve your problem. HTH.Horvath
No its not helping in this situationVivisectionist
Could you tell what have you implemented exactly?Horvath
class TIField: UITextView { weak var overrideNextResponder: UIResponder? override var next: UIResponder? { get { if overrideNextResponder != nil { return overrideNextResponder } else { return super.next } } } override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { if overrideNextResponder != nil { return false } else { return super.canPerformAction(action, withSender: sender) } } }Huihuie
B
2

enter image description here

Just did it in Swift via Nikita Took's solution.

I have a chat screen where there is a Text Field for text Input and Labels for messages (their display). When you tap on a message label, MENU (copy/paste/...) should appear, but the keyboard must stay open if already.

I subclassed the input text field:

import UIKit

class TxtInputField: UITextField {

weak var overrideNextResponder: UIResponder?

override func nextResponder() -> UIResponder? {
  if overrideNextResponder != nil {
    return overrideNextResponder
  } else {
    return super.nextResponder()
  }
}

override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
  if overrideNextResponder != nil {
    return false
  } else {
    return super.canPerformAction(action, withSender: sender)
  }
 }
}

Then in my custom message label (subclass of UILabel but it can be a View Controller in your case) which has logic to start UIMenuController, I added after

if recognizer.state == UIGestureRecognizerState.Began { ... 

the following chunk

if let activeTxtField = getMessageThreadInputSMSField() {
  if activeTxtField.isFirstResponder() {
    activeTxtField.overrideNextResponder = self
  } else {
    self.becomeFirstResponder()
  }
} else {
  self.becomeFirstResponder()
}

When user taps outside of UIMenuController

func willHideEditMenu() {
    if let activeTxtField = getMessageThreadInputSMSField() {
      activeTxtField.overrideNextResponder = nil
   }
    NSNotificationCenter.defaultCenter().removeObserver(self, name: UIMenuControllerWillHideMenuNotification, object: nil)
  }

You have to get the reference to the activeTxtField object. I did it iterating the Navigation stack, getting my View Controller which holds the desired text field and then using it.

Just in case you need it, here is the snippet for that part as well.

var activeTxtField = CutomTxtInputField()
  for vc in navigationController?.viewControllers {
    if vc is CustomMessageThreadVC {
     let msgVC = vc as! CustomMessageThreadVC         
     activeTxtField = msgVC.textBubble
   }
}
Billboard answered 4/10, 2016 at 11:59 Comment(1)
Thanks for your post. but in my case still not working can you please help me out to fix this issue. Could you share demoHuihuie

© 2022 - 2024 — McMap. All rights reserved.