Houd een numeriek veld bewerkbaar nadat de optionele waarde nil is geworden door de bewerkbare tekst apart van het getal te bewaren. Controleer eerst de parsing, de uitgeschakelde status en de focus; gebruik daarna een conceptwaarde op basis van een tekenreeks wanneer je app expliciet herstelgedrag nodig heeft.
Deze handleiding behandelt een numeriek veld in een macOS-app die je ontwikkelt: een synthetisch Tile count invoeren, wissen en een ander getal invoeren. Het doel is een betekenisvolle lege waarde te behouden en tegelijk de volgende bewerking toe te staan.
Waarom werd het optionele getal nil?
Apple biedt specifiek voor optionele waarden een SwiftUI-initialisator: TextField(value:format:prompt:label:). Volgens de gedocumenteerde werking wordt de binding ingesteld op nil wanneer de invoer niet met de opmaakstijl kan worden geparseerd. Een mislukte conversie is daarom een verwachte toestand van deze API. Apple beschrijft die toestand niet als het uitschakelen van het veld. Deze initialisator voor optionele waarden met een opmaakstijl vereist macOS 12 of nieuwer. Apple’s TextField-initialisator voor optionele waarden
Controleer welke initialisator je app daadwerkelijk gebruikt. De oudere initialisator formatter: heeft ander gedocumenteerd gedrag bij fouten: als de conversie mislukt, blijft de gebonden waarde ongewijzigd. formatter: vervangen door format: verandert dat contract; beschouw dit dus als een bewuste gedragswijziging en niet als een universele oplossing. Apple’s formattergebaseerde TextField-initialisator
Het onderliggende ontwerpprobleem is dat een optioneel getal niet elke invoertoestand kan weergeven. Een leeg veld, een gedeeltelijk ingevoerd minteken en een ongeldige tekenreeks kunnen allemaal geen bruikbaar geheel getal opleveren, maar het zijn verschillende tekstconcepten. Daarom is een aparte conceptwaarde nuttig: het veld kan precies behouden wat iemand aan het bewerken is, terwijl de app bepaalt wanneer een numerieke waarde wordt geaccepteerd.
Stel de oorzaak vast voordat je het veld wijzigt
Volg deze stappen in het venster van je eigen app. Zo kun je een conversieprobleem onderscheiden van een interactieprobleem, zonder uit te gaan van een bepaald defect in de macOS-implementatie.
- Controleer of de tekst daadwerkelijk verandert. Klik in het veld, selecteer de inhoud en typ
24. Als de tekens veranderen maar het numerieke model niet, controleer dan de conversie en de afhandeling bij bevestiging. - Controleer de interactievoorwaarden. Zoek in het veld en de bovenliggende containers naar voorwaarden zoals
.disabled(tileCount == nil). Die voorwaarde verhindert precies de bewerking die nodig is om van een lege waarde te herstellen. - Controleer of het veld nog bestaat. Als je weergave de editor alleen binnen een
if let-vertakking aanmaakt, kan het instellen van de waarde opnilde editor verwijderen. Houd de conceptwaarde en editor beschikbaar, onafhankelijk van het optionele getal. - Controleer de focus apart. Als het veld een klik accepteert en typen daarna werkt, herstel dan de focus via de juiste publieke API.
- Maak het voorbeeld kleiner. Verwijder tijdelijk aangepaste bindingstransformaties en modelsynchronisatie. Reproduceer de overgang van
12naar leeg en vervolgens naar24in een kleine weergave.
Een uitgeschakelde bovenliggende weergave is relevant: Apple documenteert dat .disabled(true) op een container .disabled(false) op een onderliggend element overschrijft. Pas de voorwaarde van de bovenliggende weergave aan in plaats van het veld telkens afzonderlijk in te schakelen. Apple’s documentatie over disabled(_:)
Controleer ook aangepaste setters voor bindings. Een setter die meteen terugkeert zodra het bestaande getal nil is, kan geen vervangend getal accepteren. Ook gaat het onderscheid tussen een ontbrekende waarde en een echte nul verloren als elke mislukte conversie wordt omgezet naar 0. Dat zijn beslissingen over de toepassingslogica; het wijzigen van de focus lost ze niet op.
Bewaar een bewerkbare conceptwaarde en bevestig het getal expliciet
Een veld dat aan een tekenreeks is gebonden geeft je controle over drie afzonderlijke toestanden: de huidige tekst, het geaccepteerde optionele getal en een validatiemelding. Apple documenteert dat een aan een tekenreeks gebonden TextField de binding tijdens het bewerken voortdurend bijwerkt. Daardoor is het geschikt om onvoltooide invoer te bewaren. Apple’s overzicht van TextField
Het volledige voorbeeld hieronder hanteert dit beleid: lege invoer betekent nil; een geldig geheel getal in grondtal 10 vervangt het geaccepteerde getal; ongeldige invoer blijft zichtbaar en laat het geaccepteerde getal ongewijzigd. De knop Clear maakt beide toestanden leeg en vraagt om focus, zodat een ander getal kan worden ingevoerd.
Maak in Xcode een macOS SwiftUI App-project met een implementatiedoel van macOS 12 of nieuwer. Vervang de gegenereerde app- en content-viewdeclaraties door dit ene bestand en behoud in het target slechts één @main-declaratie. Het voorbeeld gebruikt alleen het eigen venster en synthetische labels. Swift en de macOS SDK waren niet beschikbaar in de onderzoeksomgeving, dus de code is gecontroleerd maar niet gecompileerd of uitgevoerd.
import SwiftUI
@main
struct NumericEditingDemoApp: App {
var body: some Scene {
WindowGroup {
NumericEditingView()
}
}
}
struct NumericEditingView: View {
@State private var tileCount: Int? = 12
@State private var draft = "12"
@State private var message: String? = nil
@FocusState private var fieldIsFocused: Bool
private var acceptedDescription: String {
if let value = tileCount {
return String(value)
}
return "None"
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
TextField("Tile count", text: $draft)
.textFieldStyle(.roundedBorder)
.focused($fieldIsFocused)
.onSubmit {
applyDraft()
}
HStack {
Button("Apply") {
applyDraft()
}
Button("Clear") {
tileCount = nil
draft = ""
message = nil
fieldIsFocused = true
}
Button("Edit") {
fieldIsFocused = true
}
}
Text("Accepted count: \(acceptedDescription)")
if let message = message {
Text(message)
.foregroundColor(.secondary)
}
}
.padding(20)
.frame(width: 380)
}
private func applyDraft() {
if draft.isEmpty {
tileCount = nil
message = nil
return
}
guard let parsed = Int(draft, radix: 10) else {
message = "Enter a whole number without spaces, or clear the field."
fieldIsFocused = true
return
}
tileCount = parsed
draft = String(parsed)
message = nil
}
}
Het veld is altijd gebonden aan draft, dus het instellen van tileCount op nil verwijdert de bewerkbare teksttoestand niet. Conversie vindt alleen plaats wanneer iemand op Apply klikt of op Return drukt. Na een ongeldige invoer blijft de melding staan tot de volgende invoerbevestiging of Clear; de conceptwaarde blijft beschikbaar om te corrigeren.
guard voorkomt dat een mislukte conversie het geaccepteerde getal vervangt. Geslaagde invoer wordt genormaliseerd via String(parsed): bijvoorbeeld, invoer van +024 resulteert in 24. Dit is het expliciete beleid van het voorbeeld. Verwijder deze normalisatietoewijzing als het nuttig is om de ingevoerde schrijfwijze te behouden.
Kies bewust de grammatica voor getallen
Dit voorbeeld accepteert gehele getallen in grondtal 10 met een optioneel teken, zonder scheidingstekens voor duizendtallen. Spaties, decimale breuken en scheidingstekens voor duizendtallen worden afgewezen. Swift documenteert dat Int(_:radix:) bij ongeldige invoer of een waarde die het type integer niet kan weergeven, nil retourneert. Een geplakt getal dat te groot is, volgt dus ook het validatiepad. De tekenreeksinitialisator van Swift voor Int
Voor een veld dat gelokaliseerde getalnotatie vereist, behoud je dezelfde structuur met conceptwaarde en bevestiging, maar vervang je de conversie door een geschikte parse-strategie uit Foundation. Een ParseableFormatStyle biedt een parseStrategy, en met de methode parse(_:) van een strategie wordt de conversie uitgevoerd, waarbij een fout kan worden gegenereerd. Handel fouten af en behoud de conceptwaarde. Kies bij elkaar passende conventies voor opmaak en parsing en test vervolgens de beoogde landinstellingen. Apple’s documentatie over ParseableFormatStyle, Apple’s documentatie over ParseStrategy
Als een ander deel van je app het geaccepteerde getal kan vervangen, bepaal dan wat die update doet met een onvoltooide conceptwaarde. Zo kan een bewuste actie Load value beide toestanden bijwerken. Bouw de conceptwaarde tijdens het bewerken niet voortdurend opnieuw op uit het geaccepteerde getal: daardoor zou de onvoltooide tekst verloren gaan die dit ontwerp juist bewaart.
Herstel de focus via SwiftUI of AppKit
Koppel in SwiftUI .focused($fieldIsFocused) aan de editor en stel fieldIsFocused = true in vanuit een lokale actie, zoals het voorbeeld doet. Apple documenteert dat het toewijzen van een waarde aan de focusstatus de focus verplaatst naar de bijbehorende weergave. Geef afzonderlijke velden verschillende focusbindingen; dezelfde focuswaarde aan meerdere velden toewijzen veroorzaakt onduidelijkheid. Apple’s documentatie over FocusState
De focus bepaalt waar de bewerking plaatsvindt. De focus verandert de parser niet, schakelt een uitgeschakelde bovenliggende weergave niet opnieuw in en maakt geen editor opnieuw aan die door conditionele weergavelogica is verwijderd. Los die voorwaarden op voordat je focusherstel toevoegt.
Controleer bij een AppKit-veld dat je app beheert NSTextField.isEditable. Apple omschrijft deze eigenschap als de instelling die bepaalt of de gebruiker de waarde van het veld kan bewerken. Zet die terug op true als je eigen code de waarde ten onrechte op false heeft gezet, met behoud van eventuele bewuste beperkingen in de app. Apple’s documentatie over NSTextField.isEditable
Controleer bij het expliciet verplaatsen van de AppKit-focus acceptsFirstResponder en roep makeFirstResponder(_:) van het venster dat eigenaar is aan. De uitkomst vereist aandacht: false kan betekenen dat de huidige responder heeft geweigerd de responderstatus op te geven, terwijl true ook kan optreden als de voorgestelde responder weigert en het venster de first responder wordt. Controleer de uiteindelijke bewerkingstoestand. Om het hele veld te selecteren zodat het kan worden vervangen, is selectText(_:) de gedocumenteerde bewerking; deze heeft geen effect voordat het veld deel uitmaakt van de weergavehiërarchie van een venster. Apple’s documentatie over makeFirstResponder(_:), Apple’s documentatie over selectText(_:)
Houd rekening met de levenscyclus van de field editor
AppKit’s currentEditor() retourneert de huidige field editor van het besturingselement, of nil als die er niet is. Apple beschrijft deze editor als een NSTextView die door besturingselementen in een venster wordt gedeeld en zo nodig wordt aangemaakt. Een nil-editor buiten een actieve bewerksessie bewijst daarom niet dat numerieke invoer defect is. Apple’s documentatie over currentEditor()
Op het niveau van het tekstsysteem behandelt een field editor Tab, Shift-Tab en Return als signalen om de bewerking te beëindigen en mogelijk de first responder te wijzigen. Wijzig isFieldEditor niet als oplossing voor numerieke conversie: die eigenschap regelt het bewerkingsgedrag, niet het parsen van optionele getallen. Beperk deze controles voor AppKit en TextKit tot besturingselementen die je app beheert. Apple’s documentatie over NSTextView.isFieldEditor
Controleer of wissen en opnieuw invoeren allebei werken
Voer deze handmatige reeks uit met het volledige voorbeeld op elke macOS-versie die je app ondersteunt. Dit zijn verwachte resultaten op basis van de logica van het voorbeeld, geen gerapporteerde testuitvoeringen.
| Actie | Verwachte geaccepteerde waarde | Verwacht bewerkingsgedrag |
|---|---|---|
| Starten | 12 |
De conceptwaarde bevat 12 |
Klik op Clear |
nil, weergegeven als None |
De conceptwaarde is leeg; focus wordt aangevraagd |
Typ 24 en klik vervolgens op Apply |
24 |
De conceptwaarde bevat 24 |
Vervang dit door - en druk vervolgens op Return |
Vorige geaccepteerde waarde | De conceptwaarde blijft -; er verschijnt een melding |
Vervang dit door -8 en druk vervolgens op Return |
-8 |
De melding verdwijnt |
Voer 0 in en klik vervolgens op Apply |
0 |
Nul blijft onderscheiden van afwezigheid |
Voer 2.5 in en klik vervolgens op Apply |
Vorige geaccepteerde waarde | De breuknotatie blijft staan om te corrigeren |
Maak het veld leeg en klik vervolgens op Apply |
nil |
Het veld blijft beschikbaar voor een volgende bewerking |
Test ook het plakken van een geheel getal dat groter is dan Int.max, wegklikken en terugkeren, en het herhaaldelijk wissen van het veld. Neem in een geïntegreerde app ook elke actie mee die het model elders wijzigt. Met deze controles oefen je conversiefouten, focusherstel en synchronisatie van de conceptwaarde afzonderlijk.
De herstelvoorwaarde is concreet: nadat het geaccepteerde getal nil is geworden, blijft de editor aanwezig en interactief, en levert een latere geldige invoer een nieuw getal op. Door de conceptwaarde onafhankelijk te houden wordt dit gedrag expliciet, zonder afhankelijk te zijn van ongedocumenteerde wijzigingen aan de onderliggende besturingselementen van SwiftUI.
Bronnen
- init(value:format:prompt:label:) — Apple
- init(_:value:formatter:) — Apple
- TextField — Apple
- disabled(_:) — Apple
- init(_:radix:) — Apple, Swift Standard Library
- ParseableFormatStyle — Apple
- ParseStrategy — Apple
- FocusState — Apple
- NSTextField.isEditable — Apple
- makeFirstResponder(_:) — Apple
- selectText(_:) — Apple
- currentEditor() — Apple
- NSTextView.isFieldEditor — Apple