クラウド Mac 上の iOS シミュレータで、中国語の氏名を入力する。ピンインから漢字を選ぶ前にフォームには「中国語で入力してください」と表示され、候補を確定した直後には送信ボタンが一瞬無効になる。原因は正規表現の間違いではなく、IME で変換中のテキストを確定済みの値として扱っていることが多い。再現して修正するには、text プロパティだけを見るのではなく、フィールドの状態を「変換中」と「確定後」に分けて考える必要がある。
検証対象の状態を先に決める
中国語のピンイン入力、日本語のかな漢字変換、韓国語のハングル入力には、いずれも候補の選択や文字の組み立てを行う段階がある。UITextInput に準拠する UITextField では、markedTextRange が nil でなければ、現在マーク付きテキストが存在する。この間も入力中の文字は画面に表示してよいが、それを根拠に形式エラーを出したり、送信を許可したり、確定値として保存したりしてはいけない。
入力イベントの発生は、入力の確定を意味しない。変換中のテキストは編集過程の一部であり、業務上の検証は確定後の値に対して行う。
まずフィールドの扱いを決める。変換中は以前のエラーを消して形式チェックを一時停止し、変換が終わったら現在の text で再検証する。送信時には変換が終わっているかを確認し、もう一度検証する。最後の確認を省くと、候補を確定していないユーザーが別の操作から送信できてしまう。空欄を許すか、氏名を漢字に限定するかは業務ルールであり、変換中かどうかの判定とは分けて扱う。
フィールドのイベントでは判定を保留する
次のオブジェクトは、検証関数を呼ぶタイミングだけを制御する。例では空欄を許さず、漢字のみを有効とする。実際のアプリでは isValid に相当するルールを要件に合わせて差し替え、氏名のルールをメールアドレスや住所に流用しないこと。
import UIKit
@MainActor
final class NameFieldValidator {
private(set) var message: String?
private(set) var canSubmit = false
func editingDidChange(_ field: UITextField) {
guard field.markedTextRange == nil else {
message = nil
canSubmit = false
return
}
evaluate(field.text ?? "")
}
func submit(_ field: UITextField) -> Bool {
guard field.markedTextRange == nil else {
canSubmit = false
return false
}
evaluate(field.text ?? "")
return canSubmit
}
private func evaluate(_ value: String) {
canSubmit = !value.isEmpty &&
value.unicodeScalars.allSatisfy {
(0x3400...0x9FFF).contains(Int($0.value))
}
message = canSubmit ? nil : "请填写中文姓名"
}
}
ビューコントローラでは .editingChanged を editingDidChange(_:) に接続し、message と canSubmit に応じてメッセージとボタンを更新する。マーク付きテキストの変更でイベントが発火するかどうかは、入力経路によって異なる。「候補の確定」が必ず一度の .editingChanged に対応するとは考えず、編集終了時と送信処理の入口でもフィールドの状態を読み直す。プログラムから text を変更した場合も、直接代入はユーザーのキー入力と同じではないため、画面を明示的に更新する。例のコードポイント範囲は処理の説明用であり、氏名に使われるすべての文字、拡張漢字、区切り記号を網羅するものではない。
単体テストで状態遷移を固定する
アプリの単体テストターゲットで実際の UITextField を作り、変換中は送信できず、確定後に初めて最終的なテキストで判定されることを確認する。UIKit のオブジェクトはメインスレッドで扱う。このテストは画面上のキーボードには依存しない。
import XCTest
import UIKit
@MainActor
final class NameFieldValidatorTests: XCTestCase {
func testMarkedTextIsNotSubmitted() {
let field = UITextField()
let validator = NameFieldValidator()
field.setMarkedText("zhong", selectedRange: NSRange(location: 5, length: 0))
validator.editingDidChange(field)
XCTAssertNotNil(field.markedTextRange)
XCTAssertFalse(validator.canSubmit)
XCTAssertFalse(validator.submit(field))
field.setMarkedText("中文", selectedRange: NSRange(location: 2, length: 0))
field.unmarkText()
validator.editingDidChange(field)
XCTAssertNil(field.markedTextRange)
XCTAssertEqual(field.text, "中文")
XCTAssertTrue(validator.submit(field))
}
}
テスト対象の型をテストターゲットから参照できるようにしてから、クラウド Mac の Xcode で Test アクションを実行する。CI に組み込む際は、まず Xcode で scheme を共有し、xcodebuild -showdestinations -scheme <方案名> -project <工程名>.xcodeproj で利用可能なシミュレータを確認する。選んだ platform=iOS Simulator,id=<设备ID> を xcodebuild test に渡す。デバイス ID は実行するマシンで取得し、別のマシンの ID をスクリプトに書き込まない。
このテストが確認するのは、フィールドの状態に対するバリデータの反応であり、システムキーボードがイベントを送る順序ではない。markedTextRange のアサーションが失敗したら、まずテスト用フィールドがマーク付きテキストに対応しているか、次に呼び出しがメインスレッドで実行されているかを確認する。テストを通すために変換中の状態を確認するアサーションを削除してはいけない。
シミュレータで実際の入力経路を確かめる
単体テストが通ったら、クラウド Mac の GUI から対象の iOS シミュレータを開く。システム設定で対象の入力方法を有効にし、現在のキーボードが実際にその入力方法へ切り替わっていることを確認する。入力をそのまま物理キーボードのテキストとして送る設定をオフにしてから、氏名欄を操作する。
- ピンインを入力し、候補を選ばずにエラー表示と送信ボタンを確認する。どちらも未確定のテキストを有効な氏名として扱ってはならない。
- 漢字の候補を選び、メッセージ、ボタン、フィールドの値が一致するか確認する。続けて一文字削除し、確定後の値に合わせて検証結果が更新されるか確かめる。
- 変換中にキーボードの Return キーや別の送信経路から送信を試みる。ボタンが押せないように見えるだけでなく、送信処理自体が拒否することを確認する。
- 対象のシステムバージョンと入力方法を切り替えて繰り返す。完成した漢字を貼り付けるだけの自動化スクリプトでは、最も重要な変換過程を検証できない。
「候補を確定したのに再検証されない」場合は、.editingChanged、編集終了時、送信処理の入口での状態を一時的に記録すると、取りこぼしたイベントを調べられる。デバッグ出力はローカルでの原因調査に限り、ユーザーの入力内容を永続ログに残さない。
境界を適切な層に置く
実際のフォームでは、自動入力、貼り付け、音声入力、プログラムによる値の設定も起こり得る。これらの経路ではマーク付きテキストが発生するとは限らないが、フィールドの値は変わり得る。そのため、送信時には独立して最終確認を行う。一方、markedTextRange が nil でないからといって、編集中の文字を捨ててはいけない。誤判定につながる業務上の検証だけを一時停止し、入力中の表示は維持する。
受け入れ確認では、候補が出る前に誤ったエラーが表示されないか、確定後に速やかに更新されるか、ボタンを介さない送信もチェックを通るか、の三点を見る。それぞれを分けてテストしてこそ、クラウド Mac で再現した入力の問題を「漢字が入力できた」という一枚のスクリーンショットで見落とさずに済む。
よくある質問
IME の変換中に検証を待つ理由は何ですか?
markedTextRange がある間は候補の選択で文字列が変わります。変換が終わってから確定した値を検証します。
XCTest だけでキーボード操作を確認できますか?
できません。XCTest は検証ロジックを確認できますが、候補の選択やイベント順序は対象バージョンのシミュレータでも確認します。
次のタスクに合わせてクラウドMacを用意
モデルのメモリ要件やビルド負荷に合わせてMac miniをお選びください。3つの構成を、日・週・月・四半期単位でご利用いただけます。ノードの空き状況は管理画面にリアルタイムで表示されます。