iOSのメッセージアプリの以下のような挙動を真似したい。
-
キーボード上部からの下ドラッグに合わせてキーボードが隠れる。
ドラッグを上に戻すと、キーボードを隠す途中でもキャンセルできる -
メッセージ入力ボックスは、入力フォーカスを持たない状態では画面最下部にドックされているが、
フォーカスを持った状態だと、キーボードの上部に配置され、常に表示される。
1.についてはコンテンツを表示する UIScrollView の keyboardDismissMode プロパティをUIScrollViewKeyboardDismissModeInteractive に設定してやればよい。
2.については、UIKeyboardWillShowNotification 等を通知を捕まえて、メッセージ入力ボックスをキーボード表示アニメーションに合わせて移動させる方法があるが、厳密にキーボードアニメーションに追従させないと、アニメーション中にメッセージボックスとキーボードの間に隙間ができるため見た目が悪い。なにより面倒くさい。。
上記解決するため、以下方法を用いた。(Stackoverflow のこちらを参考にさせていただいた)
-
UIViewControllerの inputAccessoryView で入力ボックス(UITextField, UITextArea 等を含む任意のビュー)を返す
-
UIViewControllerの canBecomeFirstResponder メソッドをオーバーライドする。
「UIViewControllerの」というところがポイントである。標準キーボード上部に任意のビューを配置する方法として UITextField 等の inputAccessoryView をオーバーライドする方法が一般的であるが、今回の場合は UIViewController で行っている。
また、入力ボックスとして返すビューは UIViewController のルート view 階層には含めない。
(含めるとキーボード非表示時に画面下部にドックしない)
#import "DebugViewController.h"
@interface DebugViewController () <UITextFieldDelegate>
@property(nonatomic, weak) IBOutlet UIScrollView *scrollView;
// テキスト入力ボックス
@property(nonatomic, weak) IBOutlet UITextField *textField;
// キーボード上部に表示する、textField を含むビュー. UIViewController.view のビュー階層に置かずに、強参照にする.
@property(nonatomic, strong) IBOutlet UIView *textFieldContainer;
@end
@implementation SBDebugViewController {
BOOL _showInputView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_textField.delegate = self;
_scrollView.contentSize = [UIScreen mainScreen].bounds.size;
_scrollView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
_showInputView = YES;
[self reloadInputViews];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
_showInputView = NO;
[self reloadInputViews];
}
// こちらをオーバーライドすることで下にドックする view が得られる.
- (UIView *)inputAccessoryView
{
if (_showInputView) {
return _textFieldContainer;
}
else {
// nil にして [self reloadInputViews]; しないとメモリリーク!
// inputContainer が外部で強参照されているらしい.
return nil;
}
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
@end
何故か inputAccessoryView で返したビューは参照が解放されないので
ビューが非表示になるタイミングで reloadInputViews を呼び、nil を返し直して参照を解放している。