ずっと構想はあったものの、うまく形にできずに早2ヶ月経ってしまったのですが、ようやく形にできたので公開してみます。
どういうライブラリかというと、この記事のタイトルにもなっていますが、
「キーボードの「次へ」を押して、次のTextField,TextViewに移動する処理を簡単にした」 ライブラリとなっています。
例えば会員登録画面で、“名前”、“メールアドレス”、“パスワード"と入力項目があった時に、
“名前"の記入が終わって、キーボードの「次へ」(next)を押したら、次の"メールアドレス"に移って入力を継続したい場合があります。
そうなったときにアプローチとしては、
- if文でチェックして次を指定する
- 配列とtagを用いる
が考えられます。そうしたアプローチは以下のような感じになります。
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet private weak var nameTextField: UITextField!
@IBOutlet private weak var mailTextField: UITextField!
@IBOutlet private weak var passwordTextField: UITextField!
@IBOutlet private weak var profileTextView: UITextView!
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
if textField == nameTextField {
mailTextField.becomeFirstResponder()
} else if textField == mailTextField {
passwordTextField.becomeFirstResponder()
} else if textField == passwordTextField {
profileTextView.becomeFirstResponder()
} else {
return true
}
return false
}
}
あるいは、
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet private weak var nameTextField: UITextField!
@IBOutlet private weak var mailTextField: UITextField!
@IBOutlet private weak var passwordTextField: UITextField!
private var textfields = [UITextField]()
override func viewDidLoad() {
super.viewDidLoad()
textfields += [nameTextField, mailTextField, passwordTextField]
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
let tag = textField.tag
let nextIndex = tag + 1
if nextIndex < textfields.count {
textfields[nextIndex].becomeFirstResponder()
return false
}
return true
}
}
こんな感じになりますが、
- if文でやる場合、項目が増えた時にしんどい
- tagで管理するのはなんとなくスッキリしない
- 配列を保持しておかないといけないので、余計なプロパティが増える
- 配列で管理する場合、UITextField→UITextViewに移ったりが面倒になる。配列をAnyObjectにするわけにもいかないし…
といった問題点がでてきます。そこで、それらを解決するFormChangeable
を作成しました。
使い方
まずはFormChangeable
をimportします。
import FormChangeable
そうすると、UITextFieldまたはUITextViewにnextForm, previousForm
というプロパティが追加されているので、
キーボードの「次へ」を押下したときに移動したい移動先のformを指定します。
nameTextField.nextForm = mailTextField
mailTextField.nextForm = passwordTextField
passwordTextField.nextForm = profileTextView
これで、キーボードの「次へ」をおした時に、
“nameTextField"→"mailTextField"→"passwordTextField"→"profileTextView"と移動するように設定されます。
また、UITextField→UITextView→UITextFieldと、 2つが混ざっても問題なく繋げられます
また、これだと複数formがあった時に大変なので、以下のようにFormChangeable
の配列を用意して、一気に登録することができます。
let forms: [FormChangeable] = [nameTextField, mailTextField, passwordTextField, profileTextView]
forms.registerNextForm() //"nameTextField"→"mailTextField"→"passwordTextField"→"profileTextView"
あとは、各種TextField、TextViewのdelegateを設定し、
func textFieldShouldReturn(textField:)
func textView(textView:, shouldChangeTextInRange:, replacementText:)
の2つのメソッドで、以下のように記述します。
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.changeToNextForm()
return false
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
textView.changeToNextForm()
return false
}
return true
}
最初に提示したアプローチよりも簡潔になりました!
また、もしnextFormが設定されていない場合は、そのTextField/TextViewのキーボードを閉じるだけになります。
どう実装したか
単純にUITextFieldのみであれば、UITextFieldにextensionを追加するだけなのですが、UITextViewが混ざっても大丈夫なようにするためには、
どうしてもprotocolを採用させ、nextFormに入れる型がUITextField、UITextViewでも問題ないようにしました。
public protocol FormChangeable {
var form: UIResponder { get }
var nextForm: FormChangeable? { get set }
var previousForm: FormChangeable? { get set }
func setReturnKeyType(keyType: UIReturnKeyType)
var returnKeyType: UIReturnKeyType { get set }
}
extension UITextField: FormChangeable {}
extension UITextView: FormChangeable {}
また、内部ではnextForm,previousFormに入れるオブジェクトを弱参照で保持するために、
objc_setAssociatedObject
,objc_getAssociatedObject
を用いています。
更に、UITextField、UITextViewにFormChangeableを採用したことで、これらを入れた配列のみに対して、配列操作を行う関数を実装出来るようになります。
public extension CollectionType where Generator.Element == FormChangeable {
func registerNextForm() {
var pre: FormChangeable?
forEach {
pre?.nextForm = $0
pre = $0
}
}
func registerPreviousForm() {
var pre: FormChangeable?
reverse().forEach {
pre?.previousForm = $0
pre = $0
}
}
func setNextReturnKeyType(lastKeyType: UIReturnKeyType = .Done) {
forEach { $0.setReturnKeyType(.Next) }
reverse().first?.setReturnKeyType(lastKeyType)
}
}
この、where Generator.Element == FormChangeable
の箇所で、CollectionTypeのelementがFormChangeable
を採用した型と限定されます。これで、registerNextForm()
によって、一気にnextFormを指定することができます。
ただ、どうにもならない問題としては、UITextField、UITextViewが混ざった配列は、Swiftが [UIView] と推論してしまうので、
let forms: [FormChangeable] = [nameTextField, mailTextField, passwordTextField, profileTextView]
forms.registerNextForm() //"nameTextField"→"mailTextField"→"passwordTextField"→"profileTextView"
と、[FormChangeable]
と明示してあげる必要があります。そうでないと、registerNextForm()
を呼び出すことができません。
長くなりましたが、以上となります。よかったら使ってみてください!