UIMenuController скрывает клавиатуру

В настоящее время у меня есть приложение, которое предназначено для общения в чате. Я использовал UItextField для поля ввода и пузыри для отображения сообщений, что-то вроде системного SMS. Я хочу включить копирование и вставку в сообщениях (метках). Проблема в том, что когда я хочу показать UIMenuController, метка, которую мне нужно скопировать, должна стать первой ответчиком. Если в данный момент отображается клавиатура, когда метка станет первой ответчиком, текстовое поле потеряет фокус, поэтому клавиатура будет скрыта автоматически. это вызывает прокрутку пользовательского интерфейса и не очень хорошо. В любом случае, я могу оставить клавиатуру отображаемой, даже когда мне нужно показать меню?

введите здесь описание изображения

введите здесь описание изображения


person Anurag Kabra    schedule 28.11.2012    source источник
comment
Кажется, такие же проблемы... 1. stackoverflow. com/questions/8380373/ 2. stackoverflow.com/a/3717373/790794   -  person alloc_iNit    schedule 28.11.2012
comment
Нет, когда я выбираю ячейку с помощью длинного жеста prees (когда клавиатура активна), появляется UImenucontroller, но клавиатура исчезает. Я хочу, чтобы клавиатура была на экране.   -  person Anurag Kabra    schedule 28.11.2012
comment
При отображении меню при длительном нажатии вам нужно написать что-то вроде кода [sender.view beFirstResponder]; для сохранения состояния клавиатуры.   -  person alloc_iNit    schedule 28.11.2012
comment
у меня это не работает, клавиатура исчезает, когда UIMenuController подходит к длинному нажатию строки   -  person Anurag Kabra    schedule 28.11.2012


Ответы (3)


Вы можете попытаться создать подкласс вашего uitextfield и переопределить первого ответчика. Проверьте в своем обработчике жестов длительного нажатия, является ли поле uitext первым ответчиком, и переопределите следующий ответчик.

person akdsouza    schedule 28.11.2012
comment
Что-то в этом роде: @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
comment
Нет, в данной ситуации это не поможет - person Anurag Kabra; 30.11.2012
comment
Не могли бы вы сказать, что именно вы реализовали? - person akdsouza; 30.11.2012
comment
класс TIField: UITextView { слабый var overrideNextResponder: UIResponder? переопределить следующую переменную: 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) } } } - person Aditya Sharma; 09.10.2018

Для тех, кто все еще ищет ответ, вот код (основная идея принадлежит neon1, см. связанный вопрос).

Идея следующая: если ответчик не знает, как обработать данное действие, он передает его следующему ответчику в цепочке. На данный момент у нас есть два кандидата на роль спасателей:

  1. Клетка
  2. Текстовое поле

У каждого из них есть отдельная цепочка респондеров (на самом деле нет, у них есть общий предок, поэтому их цепочки имеют что-то общее, но мы не можем его использовать):

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

В случае, если мы установили наш пользовательский следующий ответчик, мы отправляем ему все действия (вы можете изменить эту часть, если вам нужны какие-то действия над текстовым полем), в противном случае мы спрашиваем наш супертип, может ли он это обработать.

person Nikita Took    schedule 24.05.2014
comment
@nikita: Спасибо за ваш пост. но в моем случае все еще не работает, не могли бы вы помочь мне решить эту проблему. - person Bucket; 01.04.2017
comment
@nikita: Спасибо за ваш пост. но в моем случае все еще не работает, не могли бы вы помочь мне решить эту проблему - person Aditya Sharma; 09.10.2018

введите здесь описание изображения

Просто сделал это в 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
   }
}
person Pavle Mijatovic    schedule 04.10.2016
comment
Спасибо за ваш пост. но в моем случае все еще не работает, не могли бы вы помочь мне решить эту проблему. Не могли бы вы поделиться демо - person Aditya Sharma; 09.10.2018