💬
【Swift】UIAlertControllerでバリデーションチェックをする 〜文字数によってエラーメッセージ表示かつボタン非活性〜
はじめに
UIAlertControllerでバリデーションチェックをする方法です。
バリデーションチェックが必要な入力用のダイアログをUIAlertControllerで作るんだ!っていうのは割とニッチな気はしますがぜひ参考にしてください。
今回実装したのは以下となります。
- 未入力または入力文字数が5を超えた場合に登録ボタンが非活性になる
- 入力文字数が5を超えた場合は「5文字以内で入力してください」というエラーメッセージを表示する(エラーメッセージとは別で、通常メッセージは常に表示されている)
環境
Swift5
Xcode13.3
iOS15
実装する
UIAlertControllerを拡張して、必要箇所で呼び出す形で実装します。
拡張クラス
import UIKit
extension UIAlertController {
static func addAlertWithValidation(
register: @escaping (_ text: String) -> Void
) -> UIAlertController {
let descriptionString = "入力してください"
let validationString = "5文字以内で入力してください"
var alert = UIAlertController()
var token: NSObjectProtocol?
// UIAlertControllerを作成する
alert = UIAlertController(title: "登録ダイアログ", message: descriptionString, preferredStyle: .alert)
// 登録時の処理
let registerAction = UIAlertAction(title: "登録", style: .default, handler: { _ in
guard let textFields = alert.textFields else { return }
guard let text = textFields[0].text else { return }
register(text)
guard let token = token else { return }
// オブサーバ登録を解除・・・①
NotificationCenter.default.removeObserver(token)
})
// キャンセル時の処理
let cancelAction = UIAlertAction(title: "キャンセル", style: .cancel, handler: { _ in
guard let token = token else { return }
// オブサーバ登録を解除・・・①
NotificationCenter.default.removeObserver(token)
})
// テキストフィールドを追加
alert.addTextField { (textField: UITextField!) -> Void in
// テキスト変更の通知を受け取るためにオブサーバを登録する・・・②
token = NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: nil, queue: nil) { _ in
let text = textField.text ?? ""
registerAction.isEnabled = false
if text.count > 5 {
// 入力文字が5文字より多い場合(バリデーションエラー)
let messageString = "\(descriptionString)\n\(validationString)"
let range: NSRange = NSString(string: messageString).range(of: validationString )
let alertText = NSMutableAttributedString(string: messageString)
// validationStringのみを赤字にする・・・③
alertText.addAttributes([
.foregroundColor: UIColor.red,
], range: range)
alert.setValue(alertText, forKey: "attributedMessage")
} else {
// 入力文字が5文字以内の場合(正常)
let alertText = NSMutableAttributedString(string: descriptionString)
alert.setValue(alertText, forKey: "attributedMessage")
if text.count != 0 {
// 登録ボタン非活性(未入力時)
registerAction.isEnabled = true
}
}
}
}
// 登録ボタン非活性(初期表示)
registerAction.isEnabled = false
alert.addAction(cancelAction)
alert.addAction(registerAction)
return alert
}
}
ポイントは3点です。
- 入力テキストフィールドの追加時、入力テキスト変更の通知を受け取るためにオブサーバを登録する(②)
- バリデーションエラー時、メッセージの文字列の一部を赤色に変更してsetValueすることでエラーメッセージを表現する(③)
- 登録またはキャンセルボタン押下時のUIAlertActionが実行されるタイミングで、②で登録したオブザーバの解除をする(①)
呼び出し側
こちらは上で作成したメソッドを呼び出すだけとなります。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func tapButton(_ sender: Any) {
present(UIAlertController.addAlertWithValidation(
register: { text in
// 登録時の処理
}
), animated: true)
}
}
またメソッドに引数を追加することで、入力文字数の上限を可変にすることもできます。(上限の文字数を渡す、ダイアログのタイプを分類分けしてenum型で渡すetc)
そうすれば使い回しやすくなり、拡張クラスで用意したうまみも大きくなるかと思います。
参考
Discussion