Gebruik voor een Mac-app met bewerkbare rijen van één regel Return om af te handelen met onSubmit, voeg direct onder de huidige rij een nieuw model in en geef het veld daarvan focus met FocusState. Geef elke rij een stabiele ID, zodat bij het invoegen de koppeling tussen tekst, rij en focus behouden blijft.
Deze handleiding bouwt een kleine, lokale lijsteditor met synthetische items zoals Item A. Als je op Return drukt, wordt onder het veld dat je bewerkt een lege rij aangemaakt, blijft de bestaande tekst behouden en verplaatst de toetsenbordfocus naar het nieuwe veld. De tekst wordt niet gesplitst op de invoegpositie.
Welke tekstbesturing ondersteunt dit gedrag?
Gebruik een TextField van één regel wanneer elke rij één korte invoer bevat. Apple documenteert expliciet dat een TextField de actie onSubmit uitvoert wanneer de gebruiker op de fysieke of softwarematige Return-toets drukt. Apple documenteert ook dat het toewijzen van een waarde aan FocusState de focus verplaatst naar de bijbehorende weergave. Beide API's zijn beschikbaar vanaf macOS 12. Apple: onSubmit(of:_:), Apple: FocusState.
Kies de besturing op basis van wat de gebruiker bewerkt:
| Bewerkingstaak | Uitgangspunt | Te implementeren Return-gedrag |
|---|---|---|
| Eén korte invoer per rij | TextField van één regel |
Verzenden, eronder invoegen, nieuwe rij focus geven |
| Een alinea in elke rij | TextEditor of een AppKit-tekstweergave |
Bepalen hoe het invoegen van een rij verschilt van het invoegen van een nieuwe regel |
| Een document met aangepaste tekstcommando's | NSTextView ingesloten in SwiftUI |
Het relevante tekstcommando afhandelen en modelwijzigingen coördineren |
Apple beschrijft TextEditor als een editor met meerdere regels en schuifmogelijkheid. In de documentatie staat niet dat Return hetzelfde gedrag heeft als de gedocumenteerde verzendactie van TextField. Als je het voorbeeld daarom omzet naar TextEditor, moet je de afhandeling van commando's opnieuw bekijken; alleen dezelfde modifier toevoegen is geen gedocumenteerd equivalent. Apple: TextEditor, Apple: onSubmit(of:_:).
Houd de identiteit van een rij gescheiden van de positie
Drie waarden hebben elk een ander doel: de ID van de rij identificeert de invoer, de array-index bepaalt waar je invoegt en de gefocuste ID identificeert het veld dat toetsenbordinvoer moet ontvangen.
Apple's ForEach bouwt weergaven op basis van gegevens met een identiteit. Gebruik een Identifiable-model met een ID die wordt opgeslagen wanneer de rij wordt aangemaakt. In dit ontwerp dient dezelfde ID ook als focuswaarde voor de rij. Daardoor behoudt een invoer zijn identiteit wanneer er een andere invoer erboven wordt ingevoegd. Apple: ForEach.
Gebruik de bewerkbare tekst niet als ID: twee lege rijen zouden dan dezelfde identiteit hebben en bewerken zou die identiteit veranderen. Ook arrayposities zijn voor deze invoegtaak geen goede identiteiten, omdat posities verschuiven. Dit zijn gevolgen van het voorgestelde gegevensmodel, geen aanvullende vereisten van SwiftUI.
Elk veld heeft een eigen focuswaarde nodig. Apple waarschuwt dat dezelfde focuswaarde aan meerdere weergaven koppelen een ambigue focusbinding oplevert. Een opgeslagen UUID per rij biedt een eenvoudige manier om de velden van elkaar te onderscheiden. Apple: FocusState.
Bouw het complete macOS-voorbeeld
Maak in Xcode een macOS App-project met SwiftUI en stel het implementatiedoel in op macOS 12 of hoger. Vervang het gegenereerde app-ingangspunt en de contentweergave door het volgende bestand en verwijder de oorspronkelijke appdeclaratie, zodat het doel slechts één @main-ingangspunt bevat.
Het voorbeeld gebruikt een gewone VStack in een schuifweergave. Het markeert de nieuwe rij als wachtend focusdoel en vraagt vervolgens om focus wanneer dat veld verschijnt. Dit is een implementatiestrategie op basis van Apple's focus- en verschijnings-API's; Apple documenteert niet dat verschijningscallbacks universeel garanderen dat een weergave klaar is voor focus. De precieze timing van onAppear is afhankelijk van de weergave. Apple: onAppear(perform:).
import SwiftUI
import Foundation
import Dispatch
private struct EditableRow: Identifiable {
let id = UUID()
var text: String
}
@main
struct RowEntryDemoApp: App {
var body: some Scene {
WindowGroup {
RowEditor()
}
}
}
private struct RowEditor: View {
@State private var rows = [
EditableRow(text: "Item A"),
EditableRow(text: "Item B")
]
@State private var pendingFocusID: UUID?
@FocusState private var focusedRowID: UUID?
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Local row editor")
.font(.headline)
Text("Press Return in a field to insert a row below it.")
.font(.subheadline)
ScrollViewReader { proxy in
ScrollView {
VStack(spacing: 10) {
ForEach($rows) { $row in
TextField("New item", text: $row.text)
.textFieldStyle(.roundedBorder)
.focused($focusedRowID, equals: row.id)
.onSubmit {
insertRow(after: row.id)
}
.id(row.id)
.onAppear {
focusIfPending(row.id) {
proxy.scrollTo(row.id, anchor: .center)
}
}
}
}
.padding(4)
}
}
}
.padding(20)
.frame(minWidth: 420, minHeight: 280)
}
private func insertRow(after id: UUID) {
guard pendingFocusID == nil,
let index = rows.firstIndex(where: { $0.id == id })
else { return }
let newRow = EditableRow(text: "")
pendingFocusID = newRow.id
rows.insert(newRow, at: index + 1)
}
private func focusIfPending(
_ id: UUID,
scroll: @escaping () -> Void
) {
guard pendingFocusID == id else { return }
DispatchQueue.main.async {
guard pendingFocusID == id,
rows.contains(where: { $0.id == id })
else { return }
scroll()
focusedRowID = id
pendingFocusID = nil
}
}
}
Het model bevat alle voorbeeldgegevens en het bestand definieert elk type en elke functie waarnaar wordt verwezen. In de onderzoeksomgeving waren geen Swift-compiler en macOS-runtime beschikbaar. Het programma is daarom nagekeken, maar hier niet gecompileerd of uitgevoerd.
Hoe invoegen en focus samenwerken
De stringbinding van het veld werkt de bijbehorende rij bij terwijl de gebruiker typt. De verzendhandler kan daarom een nieuwe rij invoegen zonder de huidige invoer te vervangen of te wissen. Apple documenteert dat stringgebonden tekstvelden continu worden bijgewerkt. Apple: TextField.
De invoegingshandler zoekt de verzendende rij op ID en voegt in op index + 1. Bij een rij in het midden komt de nieuwe invoer vóór de volgende bestaande invoer. Bij de laatste rij is de positie gelijk aan endIndex van de array, wat een geldige invoegpositie is en het element aan het einde toevoegt. Apple: insert(_:at:).
De verschijningscallback van het nieuwe veld controleert of de ID overeenkomt met het wachtende verzoek. Het uitgestelde blok controleert dat opnieuw voordat het scrollt en focus toewijst. Zo kan een niet-gerelateerde verschijningscallback het verzoek niet afhandelen. Het voorbeeld negeert ook extra verzendingen zolang er een focusverzoek wacht.
De extra dispatch stelt het werk uit tot buiten de verschijningscallback; het is geen gedocumenteerde garantie voor de weergavevolgorde. Controleer deze planningskeuze op de macOS-versies die je ondersteunt en binnen de daadwerkelijke hiërarchie van de container.
Scrollen en focus zijn afzonderlijke bewerkingen. scrollTo vindt een onderliggend element aan de hand van de weergave-ID en scrolt ernaartoe; door focusedRowID toe te wijzen, wordt toetsenbordfocus aangevraagd. De expliciete .id(row.id) geeft het scrolldoel op. Apple: scrollTo(_:anchor:).
Controleer het resultaat en spoor veelvoorkomende problemen op
Doorloop deze acceptatiestappen met de voorbeelditems:
- Klik op
Item A, typ extra tekst en druk één keer opReturn. - Controleer of er één leeg veld tussen de twee oorspronkelijke items verschijnt.
- Typ meteen
Item Cen controleer of het in het nieuwe veld verschijnt. - Druk in het laatste veld op
Returnen controleer of er aan het einde een nieuwe rij verschijnt. - Druk in een leeg veld op
Returnen controleer of er nog een lege rij kan worden toegevoegd. - Ga door totdat scrollen nodig is en controleer vervolgens of het ingevoegde veld zichtbaar wordt en tekst accepteert.
Dit zijn verwachte resultaten die je lokaal moet controleren. Lege invoeren zijn in het voorbeeld toegestaan en de volledige huidige tekenreeks blijft behouden, ook als de invoegpositie midden in de tekst staat. Als je de tekenreeks in twee invoeren wilt splitsen, zijn selectie- en tekstbereikafhandeling aanvullende vereisten.
Als er een rij verschijnt maar typen in het oude veld blijft, controleer dan de gefocuste ID, de verschijningscallback van het nieuwe veld en of het venster actief is. Controleer ook of een andere callback de focus opnieuw instelt. Genereer geen nieuwe rij-ID's tijdens het berekenen van de weergave.
Als er twee keer wordt ingevoegd, inspecteer dan de verzendhandlers hoger in de weergavehiërarchie. Apple staat onSubmit toe op afzonderlijke besturingselementen en omvattende weergaven, en documenteert submitScope als een manier om te voorkomen dat verzenden wordt doorgegeven aan handlers hoger in de hiërarchie. Laat één handler verantwoordelijk zijn voor het invoegen. Apple: onSubmit(of:_:).
Het wijzigen van het verzendlabel zorgt niet voor navigatie of invoegen. Apple definieert submitLabel als het instellen van het semantische verzendlabel. De modelwijziging en focusinstelling vereisen nog steeds eigen code. Apple: submitLabel(_:).
Bekijk voor een veel grotere verzameling opnieuw of een eager stack geschikt is. Apple merkt op dat containers zoals List en LazyVStack elementen van ForEach op verzoek opvragen. Een veld buiten beeld moet daarom mogelijk eerst in beeld worden gebracht voordat de verschijningscallback het focusverzoek kan voltooien. Vervang de container pas nadat je die volgorde hebt getest. Apple: ForEach.
Wanneer gebruik je in plaats daarvan AppKit en TextKit?
Gebruik het SwiftUI-voorbeeld voor korte, onafhankelijke invoeren. Wanneer rijen tekst met meerdere regels bevatten of nauwkeurige afhandeling van tekstcommando's vereisen, kun je als ondersteund alternatief je eigen NSTextView insluiten via NSViewRepresentable. Apple documenteert representables als de brug waarmee je AppKit-weergaven in SwiftUI maakt en bijwerkt, met een coordinator die delegate-interacties doorgeeft. Apple: NSViewRepresentable.
Behoud voor dat alternatief hetzelfde model met rij-ID's en implementeer deze volgorde:
- Maak de tekstweergave aan en wijs een coordinator aan als delegate.
- Geef tekstwijzigingen door aan het model van de bijbehorende rij.
- Implementeer
textView(_:doCommandBy:)en herken het commandoinsertNewline(_:)volgens het gedrag dat je editor moet hebben. - Als je dat commando afhandelt als het invoegen van een rij, behoud dan de huidige tekst, voeg de nieuwe rij in en retourneer
trueom het commando af te handelen. Retourneerfalsevoor commando's die de delegate niet afhandelt. - Vraag, zodra de nieuwe tekstweergave aan het omvattende venster is gekoppeld, focus aan via de methode
makeFirstResponder(_:)van dat venster.
Apple documenteert zowel het newline-commando als het Boolean-contract van de delegate: true betekent dat de delegate het commando heeft afgehandeld, terwijl false de tekstweergave toestaat het zelf te proberen. Dit biedt een expliciet pad voor de afhandeling van commando's in een editor die de app zelf beheert. Apple: insertNewline(_:), Apple: textView(_:doCommandBy:).
Behoud actieve tekstinvoer wanneer je aangepaste commandoafhandeling toevoegt. AppKit biedt hasMarkedText() om aan te geven of er gemarkeerde tekst is; gebruik die status om te bepalen of je delegate een commando aan het tekstsysteem moet overlaten. Test tekstinvoer in de uiteindelijke editor in plaats van aan te nemen dat elke interactie met Return een rij moet invoegen. Apple: hasMarkedText().
Voor focus geeft Apple expliciet de instructie om makeFirstResponder(_:) van het omvattende venster te gebruiken, in plaats van rechtstreeks becomeFirstResponder() van de tekstweergave aan te roepen. Toegang tot de TextKit-indeling heeft ook een versieafhankelijk gevolg: op macOS 12 en hoger schakelt expliciete toegang tot layoutManager van een tekstweergave deze over naar de compatibiliteitsmodus met NSLayoutManager. Voor het invoegen van rijen is die toegang niet nodig. Apple: NSTextView.
Begin voor de taak met één regel met het volledige SwiftUI-voorbeeld en controleer de volgorde van invoegen, zichtbaar maken en typen in je Mac-app. Stabiele identiteit, een expliciete invoegpositie en een gericht focusverzoek leveren het kerngedrag; stap over op een AppKit-tekstweergave wanneer de bewerkingstaak de commando- en selectieopties daarvan vereist.
Bronnen
- onSubmit(of:_:) — Apple
- FocusState — Apple
- TextField — Apple
- TextEditor — Apple
- ForEach — Apple
- onAppear(perform:) — Apple
- insert(_:at:) — Apple
- scrollTo(_:anchor:) — Apple
- submitLabel(_:) — Apple
- NSViewRepresentable — Apple
- insertNewline(_:) — Apple
- textView(_:doCommandBy:) — Apple
- hasMarkedText() — Apple
- NSTextView — Apple