UIMenuController隐藏了键盘

时间:2012-11-28 09:20:37

标签: iphone uimenucontroller iphone-softkeyboard

我目前有一个用于聊天的应用程序。 我使用UItextField作为输入框和气泡来显示消息,有些东西就像系统短信。 我想在消息气泡(标签)上启用复制粘贴。问题是,当我想显示UIMenuController时,我需要复制的标签需要成为第一响应者。如果当前显示键盘,当标签成为第一响应者时,文本字段将失去焦点,因此键盘将自动隐藏。 这导致UI滚动并感觉不好。 无论如何,即使我需要显示菜单,我仍然可以保持键盘显示?

enter image description here

enter image description here

3 个答案:

答案 0 :(得分:11)

对于那些仍在寻找答案的人来说,代码(主要想法属于neon1,请参阅链接问题)。

这个想法如下:如果一个响应者不知道如何处理给定的动作,它会将它传播给链中的下一个响应者。到目前为止,我们有两名第一响应者候选人:

  1. 细胞
  2. 的TextField
  3. 他们每个都有独立的响应者链(事实上,不,他们确实有共同的祖先,所以他们的链有共同点,但我们不能使用它):

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

    所以我们希望有以下的转发链:

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

    我们需要继承UITextField(代码取自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
    

    这段代码非常简单:如果我们没有设置任何自定义的下一个响应者,它会返回真实的响应者,否则返回我们的自定义响应者。

    现在我们可以在代码中设置新的响应者(我的示例添加了自定义操作):

    <强> 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
    }
    

    <强>控制器

    - (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];
    }
    

    最后一步是让第一响应者(在我们的案例中为文本字段)向下一个响应者(在我们的例子中为单元格)传播copyMessage:deleteMessage:个动作。正如我们所知,如果给定的响应者可以处理该动作,则iO发送canPerformAction:withSender:知道。

    我们需要修改CustomResponderTextView.m并添加以下功能:

    <强> CustomResponderTextView.m

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

    如果我们设置了自定义的下一个响应者,我们会向它发送所有操作(如果你需要在textField上执行某些操作,你可以修改这个部分),否则我们会询问我们的超类型是否可以处理它。

答案 1 :(得分:2)

您可以尝试子类化uitextfield并覆盖第一个响应者。如果uitextfield是第一个响应者并检查下一个响应者,请检查长按手势处理程序。

答案 2 :(得分:2)

enter image description here

通过Nikita Took的解决方案在Swift中做到了。

我有一个聊天屏幕,其中有一个文本字段用于文本输入和标签用于消息(显示)。当您点击消息标签时,应出现MENU(复制/粘贴/ ...),但键盘必须保持打开状态。

我将输入文本字段子类化:

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)
  }
 }
}

然后在我的自定义消息标签(UILabel的子类,但它可以是你的情况下的View Controller)中,它具有启动UIMenuController的逻辑,我在

之后添加了
if recognizer.state == UIGestureRecognizerState.Began { ... 

以下块

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

当用户点击UIMenuController之外

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

您必须获取对activeTxtField对象的引用。我做了它迭代导航堆栈,让我的视图控制器保存所需的文本字段,然后使用它。

万一你需要它,这里也是该部分的片段。

var activeTxtField = CutomTxtInputField()
  for vc in navigationController?.viewControllers {
    if vc is CustomMessageThreadVC {
     let msgVC = vc as! CustomMessageThreadVC         
     activeTxtField = msgVC.textBubble
   }
}