В настоящее время у меня есть приложение, которое предназначено для общения в чате. Я использовал UItextField для поля ввода и пузыри для отображения сообщений, что-то вроде системного SMS. Я хочу включить копирование и вставку в сообщениях (метках). Проблема в том, что когда я хочу показать UIMenuController, метка, которую мне нужно скопировать, должна стать первой ответчиком. Если в данный момент отображается клавиатура, когда метка станет первой ответчиком, текстовое поле потеряет фокус, поэтому клавиатура будет скрыта автоматически. это вызывает прокрутку пользовательского интерфейса и не очень хорошо. В любом случае, я могу оставить клавиатуру отображаемой, даже когда мне нужно показать меню?
UIMenuController скрывает клавиатуру
Ответы (3)
Вы можете попытаться создать подкласс вашего uitextfield и переопределить первого ответчика. Проверьте в своем обработчике жестов длительного нажатия, является ли поле uitext первым ответчиком, и переопределите следующий ответчик.
@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
Форматирование немного испорчено :( но я думаю, вы поняли. Пожалуйста, примите мой ответ, если он поможет вам решить вашу проблему. HTH.
- person akdsouza; 29.11.2012
Для тех, кто все еще ищет ответ, вот код (основная идея принадлежит neon1, см. связанный вопрос).
Идея следующая: если ответчик не знает, как обработать данное действие, он передает его следующему ответчику в цепочке. На данный момент у нас есть два кандидата на роль спасателей:
- Клетка
- Текстовое поле
У каждого из них есть отдельная цепочка респондеров (на самом деле нет, у них есть общий предок, поэтому их цепочки имеют что-то общее, но мы не можем его использовать):
UITextField <- UIView <- ... <- UIWindow <- UIApplication
UITableViewCell <- UIView <- ... <- UIWindow <- UIApplication
Таким образом, мы хотели бы иметь следующую цепочку ответчиков:
UITextField <- UITableViewCell <- ..... <- UIWindow <- UIApplication
Нам нужно создать подкласс UITextField (код взят из здесь):
Кастомреспондертекствиев.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];
}
...
В случае, если мы установили наш пользовательский следующий ответчик, мы отправляем ему все действия (вы можете изменить эту часть, если вам нужны какие-то действия над текстовым полем), в противном случае мы спрашиваем наш супертип, может ли он это обработать.
Просто сделал это в Swift с помощью решения Никиты Тука.
У меня есть экран чата, где есть текстовое поле для ввода текста и метки для сообщений (их отображение). Когда вы нажимаете на метку сообщения, должно появиться МЕНЮ (копировать/вставить/...), но клавиатура должна оставаться открытой, если она уже есть.
Я подклассифицировал текстовое поле ввода:
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
}
}