我目前有一个用于聊天的应用程序。我使用 UItextField 作为输入框,使用气泡显示消息,例如系统 SMS。我想在消息气泡(标签)上启用复制粘贴。问题是,当我想显示 UIMenuController 时,我需要从中复制的标签需要成为第一响应者。如果当前显示键盘,当标签成为第一响应者时,文本字段将失去焦点,因此键盘将自动隐藏。这会导致 UI 滚动并且感觉不好。无论如何,即使我需要显示菜单,我也可以保持键盘显示?
3 回答
对于那些仍然在这里寻找答案的人是代码(主要思想属于 neon1,请参阅链接问题)。
这个想法如下:如果响应者不知道如何处理给定的动作,它会将其传播给链中的下一个响应者。到目前为止,我们有两个第一响应者候选人:
- 细胞
- 文本域
他们每个人都有独立的响应者链(实际上,不,他们确实有共同的祖先,所以他们的链有共同点,但我们不能使用它):
UITextField <- UIView <- ... <- UIWindow <- UIApplication
UITableViewCell <- UIView <- ... <- UIWindow <- UIApplication
因此,我们希望有以下响应链:
UITextField <- UITableViewCell <- ..... <- UIWindow <- UIApplication
我们需要继承 UITextField (代码取自这里):
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:
操作。正如我们所知,iOs 发送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 进行一些操作),否则我们会询问我们的超类型是否可以处理它。
您可以尝试子类化您的 uitextfield 并覆盖第一响应者。如果 uitextfield 是第一响应者并覆盖下一个响应者,请签入您的长按手势处理程序。
刚刚通过 Nikita Take 的解决方案在 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 的子类,但在您的情况下它可以是视图控制器)中,它具有启动 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
}
}