Om een suggestie in te voeren zonder de waarde van het veld toe te passen, houd je voltooiing en inzending gescheiden. Op macOS 15 of later biedt SwiftUI systeemeigen tekstsuggesties; gebruik submitScope() om voorouder-submit-handlers te blokkeren en een expliciete knop voor de definitieve actie.

Deze tutorial bouwt een kleine labelkiezer in je eigen Mac-app. Als je Bl typt, worden synthetische labels aangeboden zoals Blue Tile. Een suggestie kiezen werkt de bewerkbare conceptversie bij; op Apply Label klikken kopieert dat concept naar de toegepaste waarde. Het onderscheid is belangrijk: een app-actie voorkomen en een Return-toets-commando consumeren zijn verschillende vereisten.

Kies de API die bij de interactie past

Apple's textInputSuggestions(_:)-modifier presenteert keuzes voor een tekstveld. Elke uitvoerbare suggestie heeft textInputCompletion(_:) nodig, wat de vervangstring levert. Beide API's zijn gedocumenteerd voor macOS 15 en later. Een suggestie zonder een completion-modifier kan wel verschijnen, maar reageert niet op klikken. Apple: textInputSuggestions(_:), Apple: textInputCompletion(_:)

Voor een kort labelfeld is het vervangen van het hele concept geschikt. Apple's suggestiedocumentatie beschrijft die vervanging van het hele veld. Neem niet aan dat deze API een caret-gerelateerde tokeneditor of een inline-achtervoegselpreview biedt; die vereisen een specifker ontwerp voor bewerken. Apple: textInputSuggestions(_:)

Jouw vereiste Geschikt startpunt
Complete labels aanbieden op macOS 15 of later SwiftUI systeemeigen tekstsuggesties
Voorkomen dat het veld een voorouder-submit-handler aanroept submitScope() op het veld
Een aangepaste suggestielayout gebruiken of een eerdere deployment target ondersteunen App-eigen suggestieknoppen die de conceptbinding bijwerken
Return een geselecteerd item laten accepteren en dat commando consumeren Een eigen AppKit-control met commandoafhandeling
Een achtervoegsel previewen of het woord bij de invoegpositie vervangen AppKit tekstvoltooiing en een expliciet voltooiingsbereik

De laatste twee rijen zijn architecturale keuzes afgeleid van de gedocumenteerde commando- en voltooiings-API's. Ze zijn nuttig als de interactie meer controle vereist dan een vervangstring en een submit-grens bieden.

Bouw een complete SwiftUI-labelkiezer

Maak in Xcode een macOS App-project met SwiftUI, gebruikmakend van een SDK die macOS 15 API's bevat. Stel de deployment target in op macOS 15 of later. Vervang de gegenereerde app- en content-view-declaraties door het volgende enkele bronbestand, zodat er slechts één @main app-declaratie in de target blijft.

Het voorbeeld houdt draft en appliedLabel gescheiden. De lokale matchregel is bewust simpel: hoofdlettergevoelige voorvoegselmatching, geen suggesties voor lege tekst, en geen exacte-match-suggestie. Swift documenteert hasPrefix(_:) als een string-voorvoegselvergelijking; bredere matchregels zouden een toepassingskeuze zijn. Swift: Strings and Characters

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
        }
    }
}

Dit is een compleet app-voorbeeld, met de voorbeeldlabels en state inbegrepen. Het is gecontroleerd tegen de opgehaalde API-documentatie, maar niet gecompileerd of uitgevoerd hier omdat een Swift-toolchain en macOS UI-runtime niet beschikbaar waren.

Waarom suggestieselectie het label niet toepast

Elke completion levert een string voor het tekstveld, wiens binding wijst naar draft. De enige code die appliedLabel toewijst of applyCount verhoogt, is de Apply Label-knopactie. Het bijwerken van het concept heeft daarom in dit voorbeeld geen route naar die definitieve actie.

Het veld heeft ook submitScope(). Apple documenteert deze modifier als het voorkomen dat submit-triggers submit-modifiers hoger in de view-hiërarchie bereiken. Dit is van belang wanneer de component later in een groter formulier wordt ingebed met een voorouder onSubmit. Apple: submitScope(_:)

Initiële focus komt van @FocusState. Apple documenteert dat het programmatisch toewijzen van de gebonden waarde de focus verplaatst naar de geassocieerde view. Focus is hier een bewerkingsgemak; het bepaalt niet of het label wordt toegepast. Apple: FocusState

Wat het voorbeeld bewust toestaat

De labellijst biedt suggesties in plaats van geldige invoer te beperken. Square Tile typen schakelt nog steeds Apply Label in. Als je interface alleen gelisteerde labels toestaat, verander de validatieregel expliciet — bijvoorbeeld, de knop alleen inschakelen wanneer het concept gelijk is aan een catalogusvermelding. Vervang een niet-matchend concept niet stilzwijgend door de eerste suggestie.

Het voorbeeld vervangt ook het hele label. Als het concept meerdere woorden bevat, moet het kiezen van een completion begrepen worden als het kiezen van de definitieve veldwaarde. Een tokeneditor die omringende tekst moet behouden, heeft selectie-bewuste vervanging nodig.

Begrijp wat "zonder in te dienen" betekent

SwiftUI's onSubmit(of:_:)-documentatie zegt dat een tekstveld zijn submit-actie triggert wanneer de gebruiker Return indient. Submit-acties kunnen aan een individuele view of een grotere hiërarchie worden gekoppeld. Controleer beide locaties als een autocompleteselectie een onverwachte app-actie lijkt te triggeren. Apple: onSubmit(of:_:)

submitScope() stelt een grens in voor voorouder-handlers. Het documenteert geen belofte dat er geen interne submit-gebeurtenis plaatsvindt, dat bewerken actief blijft, of dat elk toetsenbordcommando wordt geconsumere. Ook documenteren Apple's completion-modifiers tekstvervanging, geen universele onderdrukking van Return-toetsgedrag. Houd die limieten in gedachten bij het kiezen van de interactie. Apple: submitScope(_:), Apple: textInputCompletion(_:)

Voor een bestaand component, inspecteer deze paden apart:

  1. Verwijder elke definitieve toepassingsactie uit het veld's eigen onSubmit als Return de waarde nooit mag toepassen.
  2. Zet submitScope() op het veld om een voorouder-submit-actie te blokkeren.
  3. Controleer knop-toetsenbordsneltoetsen apart, met name een expliciete standaardactie.
  4. Controleer elke change-observer of model-setter die de definitieve actie uitvoert wanneer het concept verandert.

Een lege lokale onSubmit-closure is niet de gedocumenteerde vervanging voor een submit-grens. Apple biedt specifiek submitScope() om propagatie te stoppen. Als de app alleen op Return moet indienen wanneer geen completion actief is, heeft dat conditionele toetsenbordbeleid extra state en commandoafhandeling nodig. Apple: onSubmit(of:_:)

Gebruik AppKit wanneer Return moet accepteren en stoppen

Voor een eigen NSTextField biedt AppKit control(_:textView:doCommandBy:). Het text-view-argument is de veldeditor van het control. De delegate kan een gebonden commando afhandelen en true teruggeven; false teruggeven laat het onafgehandeld door die delegate. Dit is het gedocumenteerde uitbreidingspunt voor het aanpassen van commando's in een tekstveld. Apple: control(_:textView:doCommandBy:)

Een aangepaste completion-controller kan dat uitbreidingspunt gebruiken met deze volgorde:

  1. Houd bij of de suggestielijst open is en welk item gehighlight is.
  2. Wanneer het Return-commando aankomt met een geldig gehighlight item, voeg dat item in het concept in.
  3. Sluit de suggestielijst en synchroniseer het conceptmodel van de app.
  4. Geef true terug omdat de controller het commando heeft afgehandeld.
  5. Geef false terug voor commando's die de controller niet afhandelt.

Deze volgorde is een implementatieontwerp afgeleid van het delegate-contract, geen ingebouwd autocomplete-beleid. Beslis apart wat Return moet doen wanneer de lijst gesloten is. Als je een zelfstandige NSTextView bezit, is de corresponderende hook textView(_:doCommandBy:); Apple stelt dat het teruggeven van true voorkomt dat de tekstview dat commando zelf uitvoert. Apple: textView(_:doCommandBy:)

Voor tekstvoltooiing zelf ondersteunt NSTextView.insertCompletion(_:forPartialWordRange:movement:isFinal:) vervanging en selectiewijzigingen. Met isFinal: false selecteert het de voorgestelde achtervoegsel; met isFinal: true vouwt het de selectie samen na het voltooide woord. De vlag beschrijft de voltooiingsstaat, niet toepassingsinzending. Het movement-argument identificeert hoe navigatie of selectie van voltooiing plaatsvond. Apple: insertCompletion(_:forPartialWordRange:movement:isFinal:)

Behoud het juiste tekstbereik

AppKit's rangeForUserCompletion identificeert het gedeeltelijke woord van het begin tot de invoegpositie. Gebruik een bereik dat past bij de beoogde vervanging en bereken het opnieuw na bewerkingen of caret-verplaatsing. Een eerder vastgelegde bereik mag niet blindelings hergebruikt worden nadat de tekst veranderd is. Apple: rangeForUserCompletion

Bij het overbruggen van tekstbereiken, vervang niet Swift's tekentelling door een NSString-lengte. Swift's documentatie legt uit dat String extended grapheme clusters telt, terwijl NSString length UTF-16-codeunits telt. Die waarden kunnen verschillen. Vervanging van het hele veld via binding in het SwiftUI-voorbeeld vermijdt deze bereikconversie volledig. Swift: Strings and Characters)

Controleer invoering en toepassing apart

Gebruik het zichtbare concept, de toegepaste waarde en de teller om het resultaat te controleren op elke ondersteunde macOS-versie. De volgende zijn verwachte controles voor het voorbeeld, geen gerapporteerde testresultaten.

Actie Verwacht waarneembaar resultaat
Typ Bl Het concept is Bl; matchende labels worden aangeboden
Klik Blue Tile Het concept wordt Blue Tile; toegepaste waarde en telling blijven ongewijzigd
Druk Return tijdens bewerken De app past het concept niet toe
Klik Apply Label De toegepaste waarde kopieert het concept; telling neemt met één toe
Bewerk het concept daarna De vorige toegepaste waarde blijft zichtbaar
Wis het concept Suggesties zijn leeg en Apply Label is uitgeschakeld
Typ bl Geen matches onder de hoofdlettergevoelige regel van het voorbeeld

Oefen ook toetsenbordselectie in de systeemeigen suggestielijst en herhaal de controles na het inbedden van het veld in zijn echte parent-view. Apple merkt op dat gebeurtenissen zoals het verplaatsen van een macOS-venster de systeemeigen suggestie-interface kunnen sluiten, dus het concept moet bruikbaar blijven zonder die lijst. Apple: textInputSuggestions(_:)

Als je deployment target voor macOS 15 ligt, bied een beschikbaarheids-gegate native view en een fallback, of gebruik app-eigen suggestieknoppen doorheen. Een fallback-knop kan het label direct aan het concept toewijzen en de definitieve actie op Apply Label laten. Dat biedt dezelfde scheiding tussen concept en toepassing, terwijl toetsenbordnavigatie en presentatie werk blijven voor je component.

Voor deze labelkiezer-taak is de nuttige grens observeerbaar: een completion accepteren verandert het bewerkbare concept, en alleen de expliciete definitieve actie verandert het toegepaste label. Gebruik SwiftUI's systeemeigen suggesties en submit-scope voor die grens; ga naar AppKit-commandoafhandeling wanneer het consumeren van Return zelf onderdeel is van de vereiste.

Bronnen