Pour insérer une suggestion sans appliquer la valeur du champ, séparez la complétion de la soumission. Sous macOS 15 ou version ultérieure, SwiftUI propose des suggestions de texte natives ; utilisez submitScope() pour bloquer les gestionnaires de soumission des vues parentes et un bouton explicite pour l’action finale.
Ce tutoriel crée un petit sélecteur d’étiquettes dans votre application Mac. La saisie de Bl propose des étiquettes synthétiques comme Blue Tile. Le choix d’une suggestion met à jour le brouillon modifiable ; un clic sur Apply Label copie ce brouillon dans la valeur appliquée. Cette distinction est importante : empêcher une action de l’application et intercepter une commande de la touche Retour sont deux exigences différentes.
Choisir l’API adaptée à l’interaction
Le modificateur textInputSuggestions(_:) d’Apple présente des choix pour un champ de texte. Chaque suggestion sélectionnable nécessite textInputCompletion(_:), qui fournit sa chaîne de remplacement. Ces deux API sont documentées pour macOS 15 et les versions ultérieures. Une suggestion sans modificateur de complétion peut s’afficher, mais ne répond pas aux clics. Apple : textInputSuggestions(_:), Apple : textInputCompletion(_:)
Pour un champ d’étiquette court, remplacer tout le brouillon convient. La documentation d’Apple sur les suggestions décrit ce remplacement intégral du champ. Ne supposez pas que cette API fournit un éditeur de jetons dépendant de la position du curseur ou un aperçu de suffixe en ligne ; ces comportements nécessitent une conception d’édition plus spécifique. Apple : textInputSuggestions(_:)
| Votre exigence | Point de départ adapté |
|---|---|
| Proposer des étiquettes complètes sous macOS 15 ou version ultérieure | Suggestions de texte natives de SwiftUI |
| Empêcher le champ d’invoquer un gestionnaire de soumission d’une vue parente | submitScope() sur le champ |
| Utiliser une disposition de suggestions personnalisée ou prendre en charge une cible de déploiement antérieure | Boutons de suggestion gérés par l’application, qui mettent à jour la liaison du brouillon |
| Faire accepter l’élément sélectionné par Retour et intercepter cette commande | Un contrôle AppKit géré par l’application avec gestion des commandes |
| Prévisualiser un suffixe ou remplacer le mot à la position d’insertion | Complétion de texte AppKit et plage de complétion explicite |
Les deux dernières lignes sont des choix d’architecture déduits des API documentées pour les commandes et la complétion. Elles sont utiles lorsque l’interaction exige plus de contrôle qu’une chaîne de remplacement et une limite de soumission.
Créer un sélecteur d’étiquettes SwiftUI complet
Dans Xcode, créez un projet d’app macOS avec SwiftUI, en utilisant un SDK qui inclut les API de macOS 15. Définissez la cible de déploiement sur macOS 15 ou une version ultérieure. Remplacez les déclarations d’app et de vue de contenu générées par le fichier source unique ci-dessous, en ne laissant qu’une seule déclaration d’app @main dans la cible.
L’exemple garde draft et appliedLabel séparés. Sa règle de correspondance locale est volontairement simple : recherche de préfixe sensible à la casse, aucune suggestion si le texte est vide et aucune suggestion en cas de correspondance exacte. Swift décrit hasPrefix(_:) comme une comparaison de préfixes de chaînes ; des règles de correspondance plus larges relèveraient d’un choix de l’application. Swift : chaînes et caractères
import SwiftUI
@main
struct LabelCompletionApp: App {
var body: some Scene {
WindowGroup {
LabelCompletionView()
}
}
}
struct LabelCompletionView: View {
private let labels = [
"Blue Tile",
"Blue Tray",
"Green Tile",
"Round Tray"
]
@State private var draft = ""
@State private var appliedLabel = ""
@State private var applyCount = 0
@FocusState private var fieldIsFocused: Bool
private var matches: [String] {
guard !draft.isEmpty else { return [] }
return labels.filter {
$0.hasPrefix(draft) && $0 != draft
}
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Choose a label")
.font(.headline)
TextField("Type Bl to see suggestions", text: $draft)
.textFieldStyle(.roundedBorder)
.focused($fieldIsFocused)
.textInputSuggestions {
ForEach(matches, id: \.self) { label in
Text(label)
.textInputCompletion(label)
}
}
.submitScope()
Button("Apply Label") {
appliedLabel = draft
applyCount += 1
}
.disabled(draft.isEmpty)
Text("Draft: \(draft)")
Text("Applied: \(appliedLabel)")
Text("Apply count: \(applyCount)")
}
.padding(24)
.frame(width: 420)
.onAppear {
fieldIsFocused = true
}
}
}
Il s’agit d’un exemple d’application complet, avec ses étiquettes de démonstration et son état. Il a été examiné à la lumière de la documentation d’API récupérée, mais n’a pas été compilé ni exécuté ici, car aucun environnement d’exécution de l’interface macOS ni aucune chaîne d’outils Swift n’étaient disponibles.
Pourquoi le choix d’une suggestion n’applique pas l’étiquette
Chaque complétion fournit une chaîne au champ de texte, dont la liaison pointe vers draft. Seul le code de l’action du bouton Apply Label attribue une valeur à appliedLabel ou incrémente applyCount. Dans cet exemple, la mise à jour du brouillon ne peut donc pas déclencher cette action finale.
Le champ comporte également submitScope(). Apple documente ce modificateur comme empêchant les déclencheurs de soumission d’atteindre les modificateurs de soumission situés plus haut dans la hiérarchie des vues. Cela compte lorsque le composant est ensuite intégré à un formulaire plus vaste comportant un onSubmit parent. Apple : submitScope(_:)
Le focus initial est défini avec @FocusState. Apple indique que l’affectation programmatique de la valeur liée déplace le focus vers la vue associée. Ici, le focus facilite l’édition ; il ne détermine pas si l’étiquette est appliquée. Apple : FocusState
Ce que l’exemple autorise volontairement
La liste d’étiquettes propose des suggestions sans restreindre les saisies valides. La saisie de Square Tile permet toujours d’utiliser Apply Label. Si votre interface n’autorise que les étiquettes répertoriées, modifiez explicitement la règle de validation — par exemple, en activant le bouton uniquement lorsque le brouillon correspond à une entrée du catalogue. Ne remplacez pas silencieusement un brouillon sans correspondance par la première suggestion.
L’exemple remplace également l’étiquette entière. Si le brouillon contient plusieurs mots, le choix d’une complétion doit être compris comme le choix de la valeur finale du champ. Un éditeur de jetons qui doit préserver le texte environnant nécessite plutôt un remplacement tenant compte de la sélection.
Comprendre ce que signifie « sans soumettre »
La documentation SwiftUI de onSubmit(of:_:) indique qu’un champ de texte déclenche son action de soumission lorsque l’utilisateur appuie sur Retour. Les actions de soumission peuvent être associées à une vue individuelle ou à une hiérarchie plus vaste. Examinez ces deux emplacements lorsqu’une sélection de saisie automatique semble déclencher une action inattendue de l’application. Apple : onSubmit(of:_:)
submitScope() établit une limite pour les gestionnaires des vues parentes. La documentation ne garantit pas qu’aucun événement de soumission interne ne se produise, que l’édition reste active ou que toutes les commandes clavier soient interceptées. De même, la documentation d’Apple sur les modificateurs de complétion décrit le remplacement de texte, et non la suppression universelle du comportement de la touche Retour. Tenez compte de ces limites lorsque vous choisissez l’interaction. Apple : submitScope(_:), Apple : textInputCompletion(_:)
Pour un composant existant, examinez séparément les chemins suivants :
- Supprimez toute action finale de l’application du
onSubmitpropre au champ si Retour ne doit jamais appliquer sa valeur. - Placez
submitScope()sur le champ pour bloquer une action de soumission parente. - Vérifiez séparément les raccourcis clavier des boutons, en particulier une action par défaut explicite.
- Vérifiez tout observateur de changement ou accesseur de modèle qui exécute l’action finale chaque fois que le brouillon change.
Une fermeture onSubmit locale vide ne constitue pas le remplacement documenté d’une limite de soumission. Apple fournit précisément submitScope() pour arrêter la propagation. Si l’application doit soumettre avec Retour uniquement lorsqu’aucune complétion n’est active, cette politique clavier conditionnelle nécessite un état et une gestion des commandes supplémentaires. Apple : onSubmit(of:_:)
Utiliser AppKit lorsque Retour doit accepter et intercepter
Pour un NSTextField géré par l’application, AppKit fournit control(_:textView:doCommandBy:). Son argument de vue de texte est l’éditeur de champ du contrôle. Le délégué peut gérer une commande liée et renvoyer true ; en renvoyant false, il indique que cette commande n’a pas été gérée par ce délégué. Il s’agit du point d’extension documenté pour personnaliser les commandes d’un champ de texte. Apple : control(_:textView:doCommandBy:)
Un contrôleur de complétion personnalisé peut utiliser ce point d’extension en suivant cette séquence :
- Suivre si sa liste de suggestions est ouverte et quel élément est sélectionné.
- À la réception de la commande Retour, insérer dans le brouillon l’élément sélectionné, s’il est valide.
- Fermer la liste de suggestions et synchroniser le modèle de brouillon de l’application.
- Renvoyer
truepuisque le contrôleur a géré la commande. - Renvoyer
falsepour les commandes qu’il ne gère pas.
Cette séquence est une conception d’implémentation déduite du contrat du délégué, et non une politique de saisie automatique intégrée. Décidez séparément du comportement de Retour lorsque la liste est fermée. Si vous gérez plutôt un NSTextView autonome, le point d’extension correspondant est textView(_:doCommandBy:) ; Apple indique que le renvoi de true empêche la vue de texte d’exécuter elle-même cette commande. Apple : textView(_:doCommandBy:)
Pour la complétion de texte elle-même, NSTextView.insertCompletion(_:forPartialWordRange:movement:isFinal:) prend en charge le remplacement et les changements de sélection. Avec isFinal: false, le suffixe proposé est sélectionné ; avec isFinal: true, la sélection s’effondre après le mot complété. Ce paramètre décrit l’état de complétion, pas la soumission de l’application. L’argument movement indique comment la navigation ou la sélection de la complétion a eu lieu. Apple : insertCompletion(_:forPartialWordRange:movement:isFinal:)
Préserver la plage de texte correcte
La rangeForUserCompletion d’AppKit désigne le mot partiel depuis son début jusqu’à la position d’insertion. Utilisez une plage adaptée au remplacement souhaité et recalculez-la après les modifications ou les déplacements du curseur. Ne réutilisez pas aveuglément une plage capturée avant que le texte ait changé. Apple : rangeForUserCompletion
Lors de la conversion de plages de texte, ne remplacez pas la longueur d’un NSString par le nombre de caractères Swift. La documentation de Swift explique que String compte les graphèmes étendus, tandis que la longueur de NSString compte les unités de code UTF-16. Ces valeurs peuvent différer. Le remplacement de la liaison du champ entier dans l’exemple SwiftUI évite complètement cette conversion de plage. Swift : chaînes et caractères)
Vérifier séparément l’insertion et l’application
Utilisez le brouillon visible, la valeur appliquée et le compteur pour vérifier le résultat sur chaque version de macOS prise en charge. Les éléments suivants sont des vérifications attendues pour l’exemple, et non des résultats de tests rapportés.
| Action | Résultat observable attendu |
|---|---|
Saisir Bl |
Le brouillon est Bl ; des étiquettes correspondantes sont proposées |
Cliquer sur Blue Tile |
Le brouillon devient Blue Tile ; la valeur appliquée et le compteur restent inchangés |
| Appuyer sur Retour pendant l’édition | L’application n’applique pas le brouillon |
Cliquer sur Apply Label |
La valeur appliquée reçoit le contenu du brouillon ; le compteur augmente de un |
| Modifier le brouillon ensuite | La valeur appliquée précédente reste visible |
| Effacer le brouillon | Aucune suggestion n’est proposée et Apply Label est désactivé |
Saisir bl |
Aucune correspondance selon la règle sensible à la casse de l’exemple |
Testez également la sélection au clavier dans la liste native de suggestions et répétez les vérifications après avoir intégré le champ dans sa vue parente réelle. Apple indique que des événements tels que le déplacement d’une fenêtre macOS peuvent fermer l’interface native de suggestions ; le brouillon doit donc rester utilisable même sans cette liste. Apple : textInputSuggestions(_:)
Si votre cible de déploiement est antérieure à macOS 15, proposez une vue native protégée par un contrôle de disponibilité et une solution de repli, ou utilisez des boutons de suggestion gérés par l’application partout. Un bouton de repli peut affecter directement son étiquette au brouillon et laisser l’action finale à Apply Label. La séparation entre brouillon et application est ainsi préservée, tandis que la navigation au clavier et la présentation restent à la charge de votre composant.
Pour ce sélecteur d’étiquettes, la limite utile est observable : accepter une complétion modifie le brouillon éditable, et seule l’action finale explicite modifie l’étiquette appliquée. Utilisez les suggestions natives de SwiftUI et la limite de soumission pour obtenir ce résultat ; passez à la gestion des commandes AppKit lorsque l’exigence inclut l’interception de la touche Retour.
Sources
- textInputSuggestions(_:) — Apple
- textInputCompletion(_:) — Apple
- submitScope(_:) — Apple
- onSubmit(of:_:) — Apple
- FocusState — Apple
- control(_:textView:doCommandBy:) — Apple
- textView(_:doCommandBy:) — Apple
- insertCompletion(_:forPartialWordRange:movement:isFinal:) — Apple
- rangeForUserCompletion — Apple
- Chaînes et caractères — Swift.org