はじめに
iOSアプリでUITextFieldを使ったフォーム画面を作る時、一緒に考えるべき項目や必要な処理をまとめていこうと思います。
観点
何を入力するのか
入力させる内容によって、キーボードの種類を変えてあげると親切です。
その設定を主に行うのがText Input Traits
です。
Text Input Traits
storyboard上でTextFieldを選択すると、画面右側(ユーティリティ領域)にText Input Traitsの設定箇所が表示されます。
設定の内容や選択肢が結構多いのと、設定の組み合わせによっては結果が若干変わってきたりするので、全部紹介はできません。
私は「まとめるよりも実際に見て選べた方がいいよね」ということで、サンプルアプリを作成していて、どの設定を使おうか選んだりしています。
GitHubにも上げているので、よければお使いください。
https://github.com/Todate/UITextFieldSample
よく出るパターン
Returnキーになんと表示するか
Returnキーを押す時にユーザーが行いたいアクションに対応させる必要があるでしょう。
- 何か検索条件を入力しているなら
Search
- URLを入力し終えてサイトにアクセスしたい時は
Go
...のような感じでしょうか。
入力がなかったらReturnキーを無効にしたい
あるあるですね。
-
Auto-enable Return Key
をtrueにする(storyboard上ではチェックを入れるだけ)
入力補助
何か入力状態の時、キーボード上部に「過去に入力した単語」や「打ち間違いじゃね?」など、ユーザーが入力したいであろう選択肢を出してくれる機能です。
入力の内容によっては出さないであげた方がいいケースが多い...と感じています。
-
Correction
の設定でDefault
/No
/Yes
を選べます(Default
は他の設定によって挙動が変わるので注意)
数字だけの入力
電話番号/クレジットカード番号などを入力するのに、QWERTYキーボードを出されるとイラっとしますよね。
- 電話番号なら、名前的には
Keyboard Type
をPhonePad
にするのが最有力候補。 - 本当に数字だけの入力なら
NumberPad
でも十分。 - ハイフンなど記号の入力が必要な場合は
Numbers and Punctuation
が妥当。
パスワードの入力
入力内容を隠す設定もここで行えます。
-
Secure Text Entry
をtrueにする(storyboard上ではチェックを入れるだけ)
特定シーンの入力
-
Keyboard Type
をURL
にすると、スペースの部分がピリオドやスラッシュ、ドメインの入力補助キーになる -
Keyboard Type
をE-mail Address
にすると、スペースの部分に@キーとピリオドキーが増える
他にもKeyboard Type
はたくさんあるので、見てて楽しいです。
iOS11から出てきた「Smart Dashes/Insert/Quotes」
日本語キーボードを使用しているとなかなかお世話になることはないと思いますが、英語などの入力をしている時の入力補助が強化されました。
設定 | 内容 |
---|---|
Smart Dashes | ハイフンの入力回数でen-dash/em-dashに自動で変換する |
Smart Insert | 単語を選択してCut(Delete)をした時やコピペした単語をPaste(Insert)した時、前後のスペースの数を自動で調整する |
Smart Quotes |
' や" を入力した時に、region-specific glyphsの形に自動で変換する(対になるように始まりの方が反転してる形...といえばわかりやすいでしょうか) |
気をつけないと自動変換されるので、注意が必要です。
その他にもいろいろ
Contents Type
の使い道はまだ掴みきれてません。(また詳しく調査したいです)
iOS11から増えた設定のUsername
/Password
は、端末に保存している値の自動入力に対応しているみたいですし、なんか便利そうです。
他にも、大文字にするタイミング(Capitalization
)や、キーボードの色(Keyboard Look
)、スペルミスっぽい所を赤罫線で表示する(Spell Checking
)などがありますが...ここでは割愛します。
TextField外をタップしたら、キーボードが消えて欲しい
iOSはAndroidと違ってBackボタンがないので、自前で書かないとキーボードが消えてくれません。
でも、毎回処理を書くのは面倒...
そんなときはProtocol Extensionですね。
import UIKit
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.hideKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
@objc func hideKeyboard() {
view.endEditing(true)
}
}
あとは、viewDidLoad
などで設定するためのメソッドを呼んであげれば、大丈夫です。
TextFieldに入力できる文字数を制限したい
パスワードや年月の入力で、「xx文字まで」と決まっているケース。
TextFieldにdelegateをつけて、入力内容の変化を監視して、指定長を超えたら入力を無効にする...んですけど、画面に複数のTextFieldがあると、途端にソースが長くなってしまいます。
それに、毎回処理を書くのは面倒...
そんなときは拡張しちゃいましょう。
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
@IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(self, action: #selector(limitLength), for: .editingChanged)
}
}
@objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text, prospectiveText.count > maxLength else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
#if swift(>=4.0)
text = String(prospectiveText[..<maxCharIndex])
#else
text = prospectiveText.substring(to: maxCharIndex)
#endif
selectedTextRange = selection
}
}
すると、なんということでしょう。
Storyboard上からTextFieldを選択すると、Max Lengthが設定できるようになりました。
同じような感じで、正規表現のパターンとかも設定できるように出来るんじゃないか...と思ってます。
(2018/11/17) 追記
正規表現もOKなTextFieldを、今度は継承で作ってみました。
import UIKit
class RegexTextField: UITextField {
private var length: Int = Int.max
private var pattern: String = ".*"
private var tmpText: String?
init() {
super.init(frame: .zero)
}
override init(frame: CGRect) {
super.init(frame: frame)
registerForNotifications()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
registerForNotifications()
}
private func registerForNotifications() {
NotificationCenter.default.addObserver(
self,
selector: #selector(textDidChange),
name: NSNotification.Name(rawValue: "UITextFieldTextDidChangeNotification"),
object: self
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@IBInspectable var maxLength: Int {
get { return length }
set { length = newValue }
}
@IBInspectable var regexPattern: String {
get { return pattern }
set { pattern = newValue }
}
@objc func textDidChange() {
let target = text ?? ""
// 正規表現でチェック
if !isMatch(target: target, pattern: pattern) {
text = tmpText
return
}
// 長さでチェック
if target.count > length {
text = tmpText
return
}
// 次回の比較用に退避
tmpText = text
}
private func isMatch(target: String, pattern: String) -> Bool {
do {
let re = try NSRegularExpression(pattern: pattern)
let matches = re.matches(in: target, range: NSMakeRange(0, target.count))
return matches.count > 0
} catch {
return false
}
}
}
自分の変更を自分で受け取ってゴニョゴニョしているだけです。
textDidChange()
の中の処理を変更すれば、自分の思い通りに制御できますね!
※入力の度にパターンとマッチするか調べているので、使用するときは^[0-9]*$
みたいに許可する文字を制限するパターンを使いましょう。
(いずれマッチするかもしれないチェックって出来るのかな...?)
キーボードが表示されても、スクロールで全体を表示されるようにしたい
たとえば、こんな画面。
うんざりするほど長いフォーム画面ですね。
キーボードが出てきたら、下の青いボタンが隠れそうなのは想像できると思います。
では、「キーボードを表示している状態でも、下の青いボタンを押せるようにする」にはどうするか。
私なりのベストプラクティスは...
- 画面全体にScrollViewを配置
- ScrollView内にViewを配置して、その中に表示内容を入れていく
そして、ここでも登場。Protocol Extensionですね。
import UIKit
protocol ContentScrollable {
/// ViewController上で@IBOutletでStoryboardと接続されている前提
var scrollView: UIScrollView! { get }
/// Notificationを設定
/// (viewWillAppearで呼ぶ)
func configureObserver()
/// Notificationを削除
/// (viewWillDisappearで呼ぶ)
func removeObserver()
}
extension ContentScrollable where Self: UIViewController {
func configureObserver() {
NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { notification in
self.keyboardWillShow(notification)
}
NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { notification in
self.keyboardWillHide(notification)
}
}
func removeObserver() {
NotificationCenter.default.removeObserver(self)
}
/// キーボードが表示される時の処理
func keyboardWillShow(_ notification: Notification) {
guard let userInfo = notification.userInfo else { return }
let keyboardSize = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.height
scrollView.contentInset.bottom = keyboardSize
}
/// キーボードが隠れる時の処理
func keyboardWillHide(_ notification: Notification) {
scrollView.contentInset = .zero
scrollView.scrollIndicatorInsets = .zero
}
}
キーボードが表示/非表示になる時に発火するNotificationをobserveして、ViewControllerにあるscrollViewの表示領域をキーボードの高さ分だけ縮めたり戻したりしています。
あとは、必要なViewControllerにこのProtocolを適用させ、viewWillAppear
やviewWillDisappear
のライフサイクルメソッドで、監視の設定/解除をしてあげればOKです。
import UIKit
class ViewController: UIViewController, ContentScrollable {
// Protocolで強制されているので、storyboardと連携する際でも変数名は揃えてください
@IBOutlet weak var scrollView: UIScrollView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
configureObserver()
}
override func viewWillDisappear(_ animated: Bool) {
removeObserver()
super.viewWillDisappear(animated)
}
}
これで、キーボードが表示中でも残りの領域がスクロールされます。
おわりに
他にも様々なケースがあるかと思いますが、思いつき次第追記して拡充していきたいと思っています。
(コメントもお待ちしております)