前回の続き。今回はキーボードの表示・非表示によってSubviewのframeを変更する方法です。この手のサンプルはググればいくつも出てきますが、ここではキーボードの表示アニメーションに対応して、見栄え良くSubviewを再配置する方法を説明します。

サンプル: https://github.com/stack3/STProgrammaticLayoutViewSample

サンプルのMenu画面からWith Keypadを選択します。

[http://www.youtube.com/watch?v=gSoZL3CBBjU&:movie]

TextViewとToolbarが縦に並んでいます。キーボードの表示アニメーションにあわせてTextViewの高さが縮み、Toolbarは上に移動します。非表示にしたときは、 TextViewの高さが伸びて、Toolbarは下に移動します。

 回転した時は画面に合わせてTextView、Toolbarが適切な位置・サイズになります。

f:id:eimei23:20130127092349p:plain

STLayoutWithKeypadViewクラスを見てみましょう。TextViewとToolbarをSubviewに持っています。

@interface STLayoutWithKeypadView : UIView {
    __strong UITextView *_textView;
    __strong UIToolbar *_toolbar;
}
@property (strong, nonatomic) UITextView *textView;

@end

initWithFrameでframeとautoresizingMaskを設定しています。

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        CGRect bounds = self.bounds;
        
        CGRect textViewFrame;
        textViewFrame.origin.x = 0;
        textViewFrame.origin.y = 0;
        textViewFrame.size.width = bounds.size.width;
        textViewFrame.size.height = bounds.size.height - _ST_TOOLBAR_HEIGHT;
        _textView = [[UITextView alloc] initWithFrame:textViewFrame];
        _textView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        _textView.font = [UIFont systemFontOfSize:16.0];
        [self addSubview:_textView];
        
        CGRect toolbarFrame;
        toolbarFrame.origin.x = 0;
        toolbarFrame.origin.y = textViewFrame.size.height;
        toolbarFrame.size.width = bounds.size.width;
        toolbarFrame.size.height = _ST_TOOLBAR_HEIGHT;
        _toolbar = [[UIToolbar alloc] initWithFrame:toolbarFrame];
        _toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleTopMargin;
        [self addSubview:_toolbar];
        
        UIBarButtonItem *flexibleItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
        UIBarButtonItem *toggleKeypadButton = [[UIBarButtonItem alloc] initWithTitle:@"Keypad" style:UIBarButtonItemStyleBordered target:self action:@selector(toggleKeypadButtonDidTap)];
        _toolbar.items = @[flexibleItem, toggleKeypadButton];
    }
    return self;
}

ToolbarのKeypad Buttonを押した時、TextViewを入力状態にしてキーボードの表示・非表示を行います。

- (void)toggleKeypadButtonDidTap
{
    if ([_textView isFirstResponder]) {
        [_textView resignFirstResponder];
    } else {
        [_textView becomeFirstResponder];
    }
}

次にSTLayoutWithKeypadViewControllerクラスを見てみましょう。

viewDidLoadでSTLayoutWithKeypadView(_customView)を生成しaddSubviewしています。また、そのautoresizingMaskはFlexibleWidthとFlexibleHeightにして回転した時のサイズ変更に対応しています。

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];
    
    CGRect bounds = self.view.bounds;
    STLayoutWithKeypadView *customView = [[STLayoutWithKeypadView alloc] initWithFrame:bounds];
    _customView = customView;
    _customView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:customView];
}

viewAillAppearでキーボードの表示・非表示のNotificationを受け取るようにしています。 

- (void)viewWillAppear:(BOOL)animated
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [notificationCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

viewWillDisapperでNotificationの受け取りを解除。

- (void)viewWillDisappear:(BOOL)animated
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];
}

Viewが表示された時に自動でTextViewを入力状態にしてキーボードを表示するようにviewDidAppearを実装

- (void)viewDidAppear:(BOOL)animated
{
    [_customView.textView becomeFirstResponder];
}

キーボードが表示される時の処理。ここでSTLayoutWithKeypadView(_customView)のりサイズを行います。

- (void)keyboardWillShow:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    NSValue *keyValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
    CGRect keypadFrame = [keyValue CGRectValue];
    CGFloat keypadHeight = UIInterfaceOrientationIsPortrait(self.interfaceOrientation) ? keypadFrame.size.height : keypadFrame.size.width;
    NSTimeInterval duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    
    CGRect bounds = self.view.bounds;
    
    [UIView animateWithDuration:duration animations:^{
        CGRect customViewFrame = _customView.frame;
        customViewFrame.size.height = bounds.size.height - keypadHeight;
        _customView.frame = customViewFrame;
    }];
}

UIKeyboardFrameEndUserInfoKeyで取得したキーボードのkeypadFrame.sizeは、画面横向きのときはwidthとheightが逆なので入れ替えていることに注意。UIKeyboardAnimationDurationUserInfoKeyでキーボード表示のアニメーション時間(duration)を得ます。

UIView#animateWithDurationを使ってアニメーションしながら_customViewの高さをキーボードの分だけ縮めます。UIViewのアニメーション処理はブロック関数が使えるようになって便利になりました。

キーボード非表示の時も同様にアニメーションして高さを変更します。

- (void)keyboardWillHide:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    NSTimeInterval duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    
    CGRect bounds = self.view.bounds;
    
    [UIView animateWithDuration:duration animations:^{
        CGRect customViewFrame = _customView.frame;
        customViewFrame.size.height = bounds.size.height;
        _customView.frame = customViewFrame;
    }];
}

このようにキーボード表示に合わせてSubviewをリサイズする必要があるときは、ViewをSubclass化して、その中でSubviewを管理するとスッキリして良いと思います。

 

とりあえず、プログラムによるSubview配置は今回で終了です。また他のサンプルを書いたら更新するかもしれません。