27

多数のUIViewControllers, UITableViews(withaccessoryViewsのセルUITextFields) などを含む iPad アプリを開発しています。これらの多くはUIViewControllers、ナビゲーション階層内に表示されます。

as など、さまざまな場所にUITextFields表示されUITableViewCell accessoryViewsます。

UITextFieldユーザーが現在編集中の外部に触れるたびにキーボードを閉じるための効率的な戦略を考案したいと思います。キーボードを閉じるテクニックを検索しましたが、一般的なキーボードを閉じる方法がどのように機能するかを説明する答えはまだ見つかりません.

たとえば、次のコードが任意の ViewController に追加されるこのアプローチが好きです。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"* * * * * * * * *ViewControllerBase touchesBegan");

    [self.view endEditing:YES]; // dismiss the keyboard

    [super touchesBegan:touches withEvent:event];
}

...しかし、この手法は、たとえば、UITableView表示されている 内でタッチが発生する状況には対応していません。endEditingそのため、 a がタッチされたときに呼び出すコードを追加する必要があります。つまり、他のさまざまなキーがタッチさUITableViewれたときにキーボードを閉じるために、アプリに多くのコードが散りばめられます。UIElements

タッチをインターセプトしてキーボードを閉じる必要があるさまざまな場所をすべて特定することはできると思いますが、iOS のキーボードを閉じるイベントを処理するためのより良い設計パターンがどこかにあるように思えます。

誰かがこの問題で経験を共有し、アプリ全体でキーボードの非表示を一般的に処理するための特定の手法を推奨できますか?

どうもありがとう

4

8 に答える 8

26

ビュー階層は 内にありUIWindowます。は、そのメソッドUIWindowでタッチ イベントを正しいビューに転送する責任があります。overridesendEvent:のサブクラスを作りましょう。UIWindowsendEvent:

@interface MyWindow : UIWindow
@end

ウィンドウには、現在のファーストレスポンダーが存在する場合、それへの参照が必要です。も使用する場合UITextViewがあるので、テキスト フィールドとテキスト ビューの両方からの通知を確認します。

@implementation MyWindow {
    UIView *currentFirstResponder_;
}

- (void)startObservingFirstResponder {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(observeBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeEndEditing:) name:UITextViewTextDidEndEditingNotification object:nil];
}

- (void)stopObservingFirstResponder {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil];
    [center removeObserver:self name:UITextFieldTextDidEndEditingNotification object:nil];
    [center removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil];
    [center removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil];
}

- (void)observeBeginEditing:(NSNotification *)note {
    currentFirstResponder_ = note.object;
}

- (void)observeEndEditing:(NSNotification *)note {
    if (currentFirstResponder_ == note.object) {
        currentFirstResponder_ = nil;
    }
}

ウィンドウは、初期化されると通知の監視を開始し、割り当てが解除されると停止します。

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self commonInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {
    [self startObservingFirstResponder];
}

- (void)dealloc {
    [self stopObservingFirstResponder];
}

sendEvent:イベントに基づいて最初のレスポンダーを「調整」するためにオーバーライドし、スーパーを呼び出しsendEvent:てイベントを正常に送信します。

- (void)sendEvent:(UIEvent *)event {
    [self adjustFirstResponderForEvent:event];
    [super sendEvent:event];
}

ファーストレスポンダーがいない場合は、ファーストレスポンダーについて何もする必要はありません。ファーストレスポンダーがいて、タッチが含まれている場合、強制的に辞任させたくありません。(複数のタッチが同時に発生する可能性があることを覚えておいてください!) ファーストレスポンダーがあり、ファーストレスポンダーになる可能性のある別のビューに新しいタッチが表示された場合、システムはそれを自動的に正しく処理するため、そのケースも無視したいと考えています。しかし、ファーストレスポンダーがいて、タッチが含まれておらず、ファーストレスポンダーになれないビューに新しいタッチが表示された場合、ファーストレスポンダーを辞任させたいと考えています。

- (void)adjustFirstResponderForEvent:(UIEvent *)event {
    if (currentFirstResponder_
        && ![self eventContainsTouchInFirstResponder:event]
        && [self eventContainsNewTouchInNonresponder:event]) {
        [currentFirstResponder_ resignFirstResponder];
    }
}

ファーストレスポンダーでイベントにタッチが含まれているかどうかを報告するのは簡単です。

- (BOOL)eventContainsTouchInFirstResponder:(UIEvent *)event {
    for (UITouch *touch in [event touchesForWindow:self]) {
        if (touch.view == currentFirstResponder_)
            return YES;
    }
    return NO;
}

イベントにファーストレスポンダーにならない新しいタッチがビューに含まれているかどうかを報告するのは、ほぼ同じくらい簡単です。

- (BOOL)eventContainsNewTouchInNonresponder:(UIEvent *)event {
    for (UITouch *touch in [event touchesForWindow:self]) {
        if (touch.phase == UITouchPhaseBegan && ![touch.view canBecomeFirstResponder])
            return YES;
    }
    return NO;
}

@end

このクラスを実装したら、 の代わりにこのクラスを使用するようにアプリを変更する必要がありますUIWindow

UIWindowで作成する場合は、 の上部にあるapplication:didFinishLaunchingWithOptions:必要があり、 の代わりにを作成するように変更します。#import "MyWindow.h"AppDelegate.mapplication:didFinishLaunchingWithOptions:MyWindowUIWindow

nib で作成する場合UIWindowは、ウィンドウのカスタム クラスを nib に設定する必要がありますMyWindow

于 2012-06-30T18:39:13.660 に答える
3

私は通常、以下を使用します

初め:

実装ファイルに次の拡張子を含めるとfindFirstResponder、最初のリゾンダーを見つけるのに役立ちます

@implementation UIView (FindFirstResponder)
- (UIView*)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;     
    }
    for (UIView *subView in self.subviews) {
        UIView *responder = [subView findFirstResponder];
        if (responder)
            return responder;
    }

    return nil;
}
@end

次に、View ControllerviewDidLoadに以下を追加します。

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(keyboardDidShow:) 
                   name:UIKeyboardDidShowNotification 
                 object:nil];

    [center addObserver:self selector:@selector(keyboardDidHide:) 
                   name:UIKeyboardWillHideNotification 
                 object:nil];
    // Do any additional setup after loading the view, typically from a nib.
}

通知機能は次のようになります

- (void) keyboardDidShow:(NSNotification*)notification
{    
    UIButton *button = [[UIButton alloc] init];

    CGRect rect = self.view.bounds;


    button.frame = rect;
    button.backgroundColor = [UIColor blackColor];
    button.tag = 111;
    UIView *currentResponer = [self.view findFirstResponder];
    [button addTarget:currentResponer action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];

    [self.view insertSubview:button belowSubview:currentResponer];
}

- (void) keyboardDidHide:(NSNotification*)notification
{
    [[self.view viewWithTag:111] removeFromSuperview];
}

UIButtonキーボードが表示されたら、現在のファーストレスポンダの下にa を追加します。このボタン アクションはキーボードを非表示にします。

ここでの制限は、UITextFieldself.view必要であるということですが、いくつかの変更を加えて、この手法を必要に応じて適応させることができます。

于 2012-06-30T12:59:02.780 に答える
2

私は実際、Omar のテクニックが本当に気に入っています - それは美しく機能します - しかし、私のケースではいくつかの行を追加しました。

- (void)keyboardDidShow:(NSNotification*)notification
{
    UIButton *button = [[UIButton alloc] init];

    CGRect rect = self.view.bounds;

    button.frame = rect;
    button.backgroundColor = [UIColor blackColor];

    [button setAlpha:0.5f];
    button.tag = 111;
    UIView *currentResponder = [self.view findFirstResponder];

    [self.view bringSubviewToFront:currentResponder];

    if (currentResponder == _descriptionTextView) [_descriptionTextView setTextColor:[UIColor whiteColor]];

    [button addTarget:currentResponder action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];

    [self.view insertSubview:button belowSubview:currentResponder];
}

彼のソリューションに 3 行追加しました (上記参照)。

  1. ボタンのアルファを 0.6 に設定して、その背後にある viewController の一部が見えるようにします。
  2. currentResponder を前面に移動して、他のビューがボタンの前面に表示されないようにします。
  3. UITextView の色を切り替えて、テキストがより明確になるようにします (UITextView の背景が明確であるため、テキストがはっきりと表示されませんでした)。

とにかく、マイナーな変更のみですが、うまくいけばこれらが役に立ちます。ありがとうオマール。

于 2013-01-08T13:41:46.713 に答える
1

現在のファーストレスポンダーを辞任するために、endEditing を使用してみましたが、後でファーストレスポンダーを設定する際に問題が発生しました。次に、再帰的な方法をしばらく使用して、UIView カテゴリ メソッドでファーストレスポンダーを見つけました。

- (UIView *)getFirstResponder {

    if (self.isFirstResponder) {
        return self;
    }

    for (UIView *subView in self.subviews) {
        UIView *firstResponder = [subView getFirstResponder];

        if (firstResponder != nil) {
            return firstResponder;
        }
    }

    return nil;
}

レスポンダが返されたら、resignFirstResponder を呼び出すだけです。

ちょうど今日、職場の私の仲間がここでもっと良い方法を教えてくれました: http://overooped.com/post/28510603744/i-had-completely-forgotten-about-nil-targeted

これを呼び出すだけです:

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

素晴らしいワンライナー!

于 2013-11-07T16:01:58.350 に答える
0

私はあなたが何を望んでいるかを正しく理解していないかもしれませんが、- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch方法はキーボードを閉じるのに役立つかもしれません

于 2012-06-30T13:30:15.710 に答える
0

テキスト フィールド コントロールに「textFieldDidChange」通知メソッドを追加します。

[textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingDidEnd];

私のために働く

于 2014-07-16T18:45:06.500 に答える