【Xcode】キーボードで隠れないようにスクロール
キーボードで入力エリアが隠れてしまう場合に、キーボードに合わせてスクロールさせるサンプルです。
先日の「【Xcode】文字入力できるTableViewCellサンプル - 文系プログラマの勉強ノート」のソースに追加していきます。
キーボード表示・非表示時の通知登録
ViewController.swiftに以下を追加します。
override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) registerNotification() } override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) unregisterNotification() } // MARK: - Keyboard // 通知登録処理 func registerNotification() -> () { let center: NSNotificationCenter = NSNotificationCenter.defaultCenter() center.addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil) center.addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil) } // 通知登録解除処理 func unregisterNotification() -> () { let center: NSNotificationCenter = NSNotificationCenter.defaultCenter() center.removeObserver(self, name: UIKeyboardWillShowNotification, object: nil) center.removeObserver(self, name: UIKeyboardWillHideNotification, object: nil) }
スクロール処理を作成
キーボード表示・非表示前に呼ばれるスクロール処理をViewController.swiftに以下のように追加します。
// Keyboard表示前処理 func keyboardWillShow(notification: NSNotification) -> () { scrollTableCell(notification, showKeyboard: true) } // Keyboard非表示前処理 func keyboardWillHide(notification: NSNotification) -> () { scrollTableCell(notification, showKeyboard: false) } // TableViewCellをKeyboardの上までスクロールする処理 func scrollTableCell(notification: NSNotification, showKeyboard: Bool) -> () { if showKeyboard { // keyboardのサイズを取得 var keyboardFrame: CGRect = CGRectZero if let userInfo = notification.userInfo { if let keyboard = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue { keyboardFrame = keyboard.CGRectValue() } } // keyboardのサイズが変化した分ContentSizeを大きくする let diff: CGFloat = keyboardFrame.size.height - lastKeyboardFrame.size.height let newSize: CGSize = CGSizeMake(tableView.contentSize.width, tableView.contentSize.height + diff) tableView.contentSize = newSize lastKeyboardFrame = keyboardFrame // keyboardのtopを取得 let keyboardTop: CGFloat = UIScreen.mainScreen().bounds.size.height - keyboardFrame.size.height; // 編集中セルのbottomを取得 let cell: InputTextTableCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: editingPath.row, inSection: editingPath.section)) as! InputTextTableCell let cellBottom: CGFloat cellBottom = cell.frame.origin.y - tableView.contentOffset.y + cell.frame.size.height; // 編集中セルのbottomがkeyboardのtopより下にある場合 if keyboardTop < cellBottom { // 編集中セルをKeyboardの上へ移動させる let newOffset: CGPoint = CGPointMake(tableView.contentOffset.x, tableView.contentOffset.y + cellBottom - keyboardTop) tableView.setContentOffset(newOffset, animated: true) } } else { // 画面を下に戻す let newSize: CGSize = CGSizeMake(tableView.contentSize.width, tableView.contentSize.height - lastKeyboardFrame.size.height) tableView.contentSize = newSize tableView.scrollToRowAtIndexPath(editingPath, atScrollPosition: UITableViewScrollPosition.None, animated: true) lastKeyboardFrame = CGRectZero; } }
ポイントは、
- キーボードの高さ分ContentSizeを加算する
- 前回のキーボード表示時の高さからの差分をContentSizeに加算する
- セルの座標をTableView上の座標から画面左上からの座標に換算する
です。
1は、下の方の行をキーボードの上まで手動でスクロールできるようにするためです。
2は、キーボードの表示イベントが続けて来た場合に対処するためです。例えば日本語入力で変換予測のバーが表示されたときや、ある行を編集中に別の行の編集を開始した場合など、 表示→非表示→表示 ではなく 表示→表示 とイベントがくるので、単純にキーボードの高さを足すとまずいことになります。同じサイズのキーボードであれば加算の必要はないですし、前より高ければ高くなった分だけ足すようにします。
3はキーボードの座標と比較するためです。セルのy座標からtableView.contentOffset.yを引くことで、キーボードと同じく画面左上からの座標になります。
編集中の行を取得、変数追加
上記ソースでまだ定義していない変数を使っていましたので追加します。
まず編集中の行を取得するため、InputTextTableCell.swiftに編集開始を通知する処理を追加します。
protocol InputTextTableCellDelegate { // 追加 func textFieldDidBeginEditing(cell: InputTextTableCell, value: NSString) -> () } class InputTextTableCell: UITableViewCell, UITextFieldDelegate { (中略) internal func textFieldDidBeginEditing(textField: UITextField) { self.delegate.textFieldDidBeginEditing(self, value: textField.text!) } }
通知を受け、編集中の行を変数に保存する処理をViewController.swiftに追加します。
lastKeyboardFrameは前回表示されたキーボードのサイズを入れておく変数です。
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, InputTextTableCellDelegate {
var lastKeyboardFrame: CGRect = CGRectZero
var editingPath: NSIndexPath!
(中略)
func textFieldDidBeginEditing(cell: InputTextTableCell, value: NSString) -> () {
let path = tableView.indexPathForRowAtPoint(cell.convertPoint(cell.bounds.origin, toView: tableView))
editingPath = path
}
}
UIScrollViewやUICollectionViewも同じ要領でいけると思います。