Sur un simulateur iOS exécuté sur un Mac cloud, une personne saisit son nom en chinois dans un formulaire. Avant même qu’elle ait choisi un caractère parmi les candidats proposés pour son pinyin, le formulaire affiche « Le nom doit être écrit en chinois ». Lorsqu’elle sélectionne enfin un candidat, le bouton d’envoi se désactive brièvement. Le problème ne vient généralement pas de l’expression régulière : l’application traite le texte en cours de composition comme une valeur définitive. Pour reproduire puis corriger ce comportement, il faut distinguer la saisie en cours du texte confirmé, au lieu de se fier uniquement à la propriété text.
Définir ce qui doit être validé
La saisie du pinyin chinois, la conversion des kana japonais et la composition des caractères coréens passent toutes par une phase de composition ou de sélection de candidats. Dans un UITextField conforme à UITextInput, une valeur non nulle de markedTextRange indique la présence de texte marqué. L’interface peut afficher ce que la personne est en train de saisir, mais elle ne doit pas encore signaler une erreur de format, autoriser l’envoi ni enregistrer ce texte comme valeur définitive.
Un événement de saisie ne signifie pas que la saisie est terminée. Le texte en cours de composition appartient au processus d’édition ; les règles métier doivent porter sur la valeur confirmée.
Définissez d’abord le comportement attendu du champ : pendant la composition, effacer l’ancienne erreur et suspendre la validation du format ; une fois la composition terminée, valider à nouveau la valeur courante de text ; au moment de l’envoi, vérifier encore que la composition est finie et refaire la validation. Cette dernière étape est indispensable : d’autres actions peuvent déclencher l’envoi alors qu’un candidat n’a pas encore été confirmé. L’acceptation d’une valeur vide et l’obligation d’écrire le nom en caractères chinois sont des règles métier distinctes du contrôle de l’état de composition.
Reporter la validation dans les événements du champ
L’objet ci-dessous détermine uniquement quand appeler la fonction de validation. Dans cet exemple, la valeur doit être non vide et ne contenir que des caractères chinois. Dans une application réelle, remplacez isValid selon le champ concerné : une règle destinée aux noms ne convient pas telle quelle aux adresses e-mail ou postales.
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 : "请填写中文姓名"
}
}
Dans le contrôleur de vue, reliez .editingChanged à editingDidChange(_:), puis actualisez le message et le bouton à partir de message et de canSubmit. Le déclenchement d’un événement lorsque le texte marqué change dépend du parcours de saisie : ne supposez pas que la sélection d’un candidat correspond toujours exactement à un événement .editingChanged. Relisez aussi l’état du champ à la fin de l’édition et à chaque point d’entrée de l’envoi. Si le programme modifie text, actualisez également l’interface : une affectation directe n’équivaut pas à une frappe de l’utilisateur. La plage de points de code utilisée ici illustre seulement le mécanisme ; elle ne couvre pas tous les caractères possibles dans un nom, les extensions des caractères chinois ni les séparateurs.
Tester les transitions d’état par des tests unitaires
Dans la cible de tests unitaires de l’application, créez un véritable UITextField pour vérifier qu’un texte en cours de composition ne peut pas être envoyé et que seule la valeur finale est validée après confirmation. Les objets UIKit doivent être utilisés sur le thread principal ; ce test ne dépend pas du clavier affiché à l’écran.
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))
}
}
Ajoutez le type testé à la cible de tests, puis lancez l’action Test de Xcode sur le Mac cloud. Pour l’intégration continue, partagez d’abord le scheme dans Xcode, puis utilisez xcodebuild -showdestinations -scheme <方案名> -project <工程名>.xcodeproj pour trouver un simulateur disponible. Transmettez la destination choisie sous la forme platform=iOS Simulator,id=<设备ID> à xcodebuild test. L’identifiant de l’appareil doit être récupéré sur la machine utilisée, et non repris dans un script depuis une autre machine.
Ce test vérifie la réaction du validateur à l’état du champ, pas l’ordre dans lequel le clavier système émet ses événements. Si l’assertion sur markedTextRange échoue, vérifiez d’abord que le champ de test prend en charge le texte marqué, puis que les appels s’exécutent sur le thread principal. Ne supprimez pas l’assertion sur l’état de composition simplement pour faire passer le test.
Vérifier le parcours réel de saisie dans le simulateur
Une fois les tests unitaires réussis, ouvrez le simulateur iOS cible dans l’interface graphique du Mac cloud. Activez la méthode de saisie visée dans les réglages système et vérifiez que le clavier l’utilise effectivement. Désactivez le réglage qui transmet directement la saisie comme du texte provenant d’un clavier physique, puis touchez le champ du nom :
- Saisissez du pinyin sans choisir de candidat. Observez le message d’erreur et le bouton d’envoi : aucun des deux ne doit traiter le texte non confirmé comme un nom valide.
- Sélectionnez un candidat en caractères chinois et vérifiez la cohérence du message, du bouton et de la valeur du champ. Supprimez ensuite un caractère pour confirmer que la validation suit la valeur définitive.
- Pendant la composition, essayez d’envoyer le formulaire avec la touche Retour du clavier ou un autre point d’entrée. La logique d’envoi doit refuser l’opération, même si le bouton paraît déjà désactivé.
- Refaites les vérifications avec les versions du système et les méthodes de saisie visées. Un script d’automatisation qui se contente de coller des caractères chinois déjà formés contourne la phase de composition, pourtant essentielle ici.
Pour retrouver un événement manquant lorsque « le candidat est sélectionné, mais la validation ne se relance pas », vous pouvez journaliser temporairement l’état lors de .editingChanged, à la fin de l’édition et aux points d’entrée de l’envoi. Réservez ces traces au diagnostic local ; n’écrivez pas le contenu saisi par les utilisateurs dans des journaux persistants.
Garder chaque contrôle à sa place
Un formulaire réel peut aussi recevoir des valeurs par remplissage automatique, collage, dictée vocale ou mise à jour programmée. Ces parcours ne produisent pas nécessairement de texte marqué, mais peuvent tout de même modifier le champ : l’envoi final doit donc effectuer sa propre vérification. À l’inverse, une valeur non nulle de markedTextRange ne justifie pas de supprimer le texte en cours d’édition. Suspendez uniquement la validation métier susceptible de produire un faux résultat et laissez la saisie s’afficher normalement.
Pour la recette, vérifiez séparément trois points : l’absence de fausse erreur avant la sélection d’un candidat, l’actualisation rapide après sa confirmation et le refus d’un envoi qui contourne le bouton sans passer les contrôles. Tester ces trois cas évite qu’une simple capture montrant des caractères chinois saisis masque le problème reproduit sur le Mac cloud.
Questions fréquentes
Quand faut-il valider un champ pendant la saisie IME ?
Attendez que markedTextRange disparaisse, puis validez la valeur confirmée. Refaites la vérification lors de l’envoi du formulaire.
Un test XCTest remplace-t-il une vérification du clavier ?
Non. XCTest vérifie la règle métier ; le choix des candidats et l’ordre des événements doivent aussi être contrôlés dans le simulateur iOS ciblé.
Configurez un Mac cloud pour votre prochain projet
Choisissez votre Mac mini selon la mémoire ou la charge de compilation. Trois configurations sont disponibles à la location à la journée, à la semaine, au mois ou au trimestre. Vérifiez la disponibilité des nœuds en temps réel depuis la console.