SmartKeyboardなども発売され、iPadにはずっとキーボードを接続して利用している。というユーザーも多いのではないかと思いますが、そのような市場の様子を見て「enterを確定や直下の新規作成操作とし、shift + enterで改行操作」と思った場合、素直に対応したAPIがあれば良いですがUITextViewDelegateには該当するものが存在しないため、少し工夫をする必要があります。
この記事ではその実現方法について解説します。
方針
こちらのStackoverflowの質問が参考にしましたが、
**「UIKeyCommandをoverrideしてインターセプトし、shift + enterで独自の改行アクションを実行する」**という方針になります。
実装
コードはこちらのgistに投稿しましたので参考にしてください。
今回はUITextViewのサブクラスを利用して実現を図りました。
ポイント1: keyCommandsのoverride
システム定義以外のあるキーが押されたとき、システムはresponderChainを探して該当するキーに対応するUIKeyCommandが実装されているか確認し、存在すればそのアクションを実行します。
以下の実装では、shift + return(enter)を受け取るとnewLine(sender:)というアクションを実行するようなコマンドを返しています。
override var keyCommands: [UIKeyCommand]? {
// キーボードへのinputは、returnなので"\r". うっかり"\n"としてしまう事があるが、それは押下したキーではないため.
return [UIKeyCommand(input: "\r", modifierFlags: .shift, action: #selector(newLine(sender:)))]
}
UITextViewDelegateのtextView(_:shouldChangeTextIn:replacementString:)を実装している場合であっても、このkeyCommandsの実装がインターセプトするため、テキスト入力まで辿り着かずデリゲートメソッドは呼ばれません。
なので、任意の文字列を追加した後にデリゲートが動いて欲しいときは、手動で呼んであげるようにすると良いでしょう。
ポイント2: アクションの実装
UITextViewのtextプロパティに直接\nを代入したいところですが、firstResponderが外れてしまう上に予期しない挙動を引き起こしてしまいます。
そのため、現在アクティブなUITextView/UITextFieldのテキストに変更を加える場合は、UIKeyInputプロトコルに属しているinsertText(_:)メソッドを利用しましょう。
このメソッドは、テキストの現在カーソルが当たっているindexに対してStringの挿入をして、その後テキストを表示し直してくれます。(バッキングストアなどについて気になる場合は、TextKitをご参照ください)
@objc func newLine(sender: UIKeyCommand) {
// プログラム経由のテキスト更新はデリゲートメソッドは呼ばれない
insertText("\n")
}
プログラム経由でテキスト更新をした場合にはデリゲートメソッドは呼ばれないことに注意してください。
まとめ
以上のような2つのポイントを抑えて実装することで、Shift + Enterでの改行操作が実現可能となります。
もしもっとスマートな実装方法などご存知の場合は、ご教授頂けると助かります。