現在、チャット用のアプリケーションがあります。入力ボックスには UItextField を、表示メッセージにはバブルを使用しました。システム SMS のようなものです。メッセージの吹き出し (ラベル) でコピー ペーストを有効にしたい。問題は、UIMenuController を表示したいときに、コピーする必要があるラベルがファーストレスポンダーになる必要があることです。キーボードが現在表示されている場合、ラベルがファーストレスポンダーになると、テキストフィールドはフォーカスを失い、キーボードは自動的に非表示になります。これにより、UI のスクロールが発生し、気分が悪くなります。メニューを表示する必要がある場合でも、キーボードを表示したままにできる方法はありますか?
3 に答える
ここでまだ答えを探している人はコードです(主なアイデアはneon1に属します。リンクされた質問を参照してください)。
アイデアは次のとおりです。レスポンダーが特定のアクションを処理する方法を知らない場合、チェーン内の次のレスポンダーに伝播します。これまでのところ、ファーストレスポンダーの候補は 2 つあります。
- 細胞
- テキストフィールド
それぞれがレスポンダーのチェーンを別々に持っています (実際、いいえ、共通の祖先を持っているため、チェーンには共通点がありますが、それを使用することはできません):
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:
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 で何らかのアクションが必要な場合は、この部分を変更できます)。
Nikita Takeのソリューションを介して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)
}
}
}
次に、UIMenuController を開始するロジックを持つカスタム メッセージ ラベル (UILabel のサブクラスですが、あなたの場合はビュー コントローラーにすることができます) に、後で追加しました
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 オブジェクトへの参照を取得する必要があります。ナビゲーション スタックを反復処理し、目的のテキスト フィールドを保持する View Controller を取得してから使用しました。
必要な場合に備えて、その部分のスニペットもここにあります。
var activeTxtField = CutomTxtInputField()
for vc in navigationController?.viewControllers {
if vc is CustomMessageThreadVC {
let msgVC = vc as! CustomMessageThreadVC
activeTxtField = msgVC.textBubble
}
}
uitextfield をサブクラス化し、firstresponder をオーバーライドすることができます。uitextfield が最初のレスポンダーである場合は、長押しジェスチャ ハンドラーをチェックインし、nextresponder をオーバーライドします。