Om du vill att ett numeriskt fält ska gå att redigera även när dess valfria värde blir nil, bevara den redigerbara texten separat från talet. Kontrollera först parsning, inaktiverat läge och fokus. Använd sedan ett strängbaserat utkast om appen behöver ett uttryckligt sätt att återställa redigeringen.

Den här guiden handlar om ett numeriskt fält i en macOS-app som du utvecklar: ange ett syntetiskt Tile count, rensa det och ange ett nytt tal. Målet är att bevara ett meningsfullt tomt värde samtidigt som nästa redigering är möjlig.

Varför blev det valfria talet nil?

Apple tillhandahåller en SwiftUI-initialiserare särskilt för valfria värden: TextField(value:format:prompt:label:). Enligt dokumentationen sätts bindningen till nil när formatstilen inte kan parsa indata. En misslyckad konvertering är alltså ett förväntat tillstånd för det här API:t. Apple beskriver inte det tillståndet som att fältet inaktiveras. Den här initialiseraren med valfri formatstil kräver macOS 12 eller senare. Apples TextField-initialiserare för valfria värden

Kontrollera vilken initialiserare appen faktiskt använder. Den äldre initialiseraren formatter: har ett annat dokumenterat beteende vid fel: om konverteringen misslyckas lämnas det bundna värdet oförändrat. Om du byter ut formatter: mot format: ändras det beteendet. Se därför bytet som en avsiktlig beteendeförändring, inte som en universallösning. Apples formatterbaserade TextField-initialiserare

Det underliggande designproblemet är att ett valfritt tal inte kan representera alla redigeringstillstånd. Ett tomt fält, ett ofullständigt minustecken och en ogiltig sträng kan alla sakna ett användbart heltal, men textutkasten är olika. Därför är ett separat utkast användbart: fältet kan behålla exakt det som personen redigerar, medan appen avgör när ett numeriskt värde ska godtas.

Diagnostisera felet innan du ändrar fältet

Följ stegen nedan i appens eget fönster. De skiljer ett konverteringsproblem från ett interaktionsproblem utan att förutsätta att det finns ett visst fel i macOS-implementationen.

  1. Kontrollera om texten faktiskt ändras. Klicka i fältet, markera innehållet och skriv 24. Om tecknen ändras men den numeriska modellen inte gör det, undersök konverteringen och hanteringen av bekräftelse.
  2. Kontrollera interaktionsvillkoren. Sök igenom fältet och dess containrar efter villkor som .disabled(tileCount == nil). Ett sådant villkor förhindrar själva redigeringen som behövs för att återhämta sig från ett tomt värde.
  3. Kontrollera att fältet fortfarande finns. Om vyn skapar redigeraren endast inuti en if let-gren kan ett värde på nil ta bort redigeraren. Behåll utkastet och redigeraren oberoende av det valfria talet.
  4. Kontrollera fokus separat. Om fältet tar emot ett klick och det sedan går att skriva, återställ fokus med lämpligt publikt API.
  5. Förenkla exemplet. Ta tillfälligt bort anpassade bindningstransformeringar och synkronisering av modellen. Återskapa övergången från 12 till tomt till 24 i en liten vy.

En inaktiverad förälder spelar roll: Apple dokumenterar att en containers .disabled(true) åsidosätter .disabled(false) på ett underordnat element. Åtgärda förälderns villkor i stället för att gång på gång aktivera bara fältet. Apples dokumentation för disabled(_:)

Granska även anpassade bindningssetters. En setter som returnerar direkt när det befintliga talet är nil kan inte ta emot ett nytt tal. På samma sätt förloras skillnaden mellan ett frånvarande värde och en faktisk nolla om varje misslyckad konvertering mappas till 0. Det här är beslut i appens logik; de går inte att åtgärda genom att ändra fokus.

Behåll ett redigerbart utkast och bekräfta talet uttryckligen

Med ett strängbaserat fält kan du hantera tre separata tillstånd: aktuell text, det godtagna valfria talet och ett valideringsmeddelande. Apple dokumenterar att ett strängbundet TextField uppdaterar sin bindning löpande under redigeringen. Därför passar det bra för att behålla ofärdiga indata. Apples översikt över TextField

Det fullständiga exemplet nedan följer den här principen: tom inmatning betyder nil; ett giltigt heltal i bas 10 ersätter det godtagna talet; ogiltig inmatning visas fortfarande och lämnar det godtagna talet oförändrat. Knappen Clear tömmer båda tillstånden och begär fokus så att ett nytt tal kan anges.

Skapa ett macOS SwiftUI App-projekt i Xcode med macOS 12 eller senare som distributionsmål. Ersätt de genererade app- och innehållsvydeklarationerna med den här enda filen och behåll bara en @main-deklaration i målet. Exemplet använder endast sitt eget fönster och syntetiska etiketter. Swift och macOS SDK saknades i forskningsmiljön, så programmet granskades men kompilerades eller kördes inte.

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

Fältet är alltid bundet till draft, så att sätta tileCount till nil tar inte bort dess redigerbara texttillstånd. Konverteringen sker först när personen klickar på Apply eller trycker på returtangenten. Efter en ogiltig inmatning finns meddelandet kvar tills nästa inmatning eller Clear; utkastet finns kvar så att det kan korrigeras.

guard förhindrar att en misslyckad konvertering ersätter det godtagna talet. Vid lyckad inmatning normaliseras stavningen genom String(parsed): om du till exempel anger +024 blir resultatet 24. Det här är en uttrycklig princip för exemplet. Ta bort normaliseringstilldelningen om det är användbart att bevara den inmatade stavningen.

Välj talformat medvetet

Exemplet godtar signerade heltal i bas 10 utan gruppering. Det avvisar blanksteg, decimalbråk och grupperingstecken. Swift-dokumentationen anger att Int(_:radix:) returnerar nil för ogiltig inmatning eller ett värde som heltalstypen inte kan representera. Därför leder även ett inklistrat tal som är för stort till valideringsflödet. Swifts stränginitialiserare för Int

För ett fält som kräver lokaliserad talnotation behåller du samma struktur med utkast och bekräftelse men byter ut konverteringen mot en lämplig parsstrategi från Foundation. En ParseableFormatStyle exponerar en parseStrategy, och strategins metod parse(_:) utför konverteringen och kan kasta ett fel. Hantera fel utan att ta bort utkastet. Välj formaterings- och parsningsregler som passar ihop och testa sedan de avsedda språkinställningarna. Apples dokumentation om ParseableFormatStyle, Apples dokumentation om ParseStrategy

Om en annan del av appen kan ersätta det godtagna talet bör du bestämma hur en sådan uppdatering påverkar ett ofärdigt utkast. En uttrycklig åtgärd som Load value kan till exempel uppdatera båda tillstånden. Undvik att hela tiden återskapa utkastet från det godtagna talet under redigering, eftersom det skulle kasta bort den ofärdiga text som den här lösningen bevarar.

Återställ fokus med SwiftUI eller AppKit

I SwiftUI kopplar du .focused($fieldIsFocused) till redigeraren och anger fieldIsFocused = true från en lokal åtgärd, som i exemplet. Apple dokumenterar att ett tilldelat värde för fokusläget flyttar fokus till den tillhörande vyn. Ge separata fält olika fokusbindningar; om flera fält tilldelas samma fokusvärde uppstår tvetydighet. Apples dokumentation om FocusState

Fokus styr vart redigeringen går. Det ändrar inte parsaren, aktiverar inte en inaktiverad förälder och återskapar inte en redigerare som tagits bort genom villkorlig vylogik. Åtgärda de villkoren innan du lägger till återställning av fokus.

För ett AppKit-fält som appen äger kontrollerar du NSTextField.isEditable. Apple definierar den här egenskapen som den som styr om användaren kan redigera fältets värde. Återställ den till true om din egen kod av misstag har satt den till false, samtidigt som du bevarar avsiktliga begränsningar i appen. Apples dokumentation om NSTextField.isEditable

När du uttryckligen flyttar AppKit-fokus kontrollerar du acceptsFirstResponder och anropar det ägande fönstrets makeFirstResponder(_:). Resultatet kräver eftertanke: false kan betyda att den aktuella svararen vägrade att lämna över fokus, medan true också kan inträffa om den föreslagna svararen vägrar och fönstret blir första svarare. Kontrollera vilket redigeringstillstånd som faktiskt uppnåddes. För att markera hela fältet inför ersättning är selectText(_:) den dokumenterade åtgärden; den har ingen effekt innan fältet tillhör en fönstervys hierarki. Apples dokumentation om makeFirstResponder(_:), Apples dokumentation om selectText(_:)

Ta hänsyn till fältredigerarens livscykel

AppKits currentEditor() returnerar kontrollens aktuella fältredigerare, eller nil om den saknar en. Apple beskriver redigeraren som en NSTextView som delas mellan kontroller i ett fönster och skapas vid behov. Därför visar en nil-redigerare utanför en aktiv redigeringssession inte att numerisk inmatning är trasig. Apples dokumentation om currentEditor()

På textsystemnivå behandlar en fältredigerare Tab, Skift-Tab och Retur som signaler om att avsluta redigeringen och eventuellt ändra första svarare. Ändra inte isFieldEditor som en lösning på numerisk konvertering: den egenskapen styr redigeringsbeteendet, inte parsningen av valfria tal. Begränsa dessa AppKit- och TextKit-kontroller till element som appen äger. Apples dokumentation om NSTextView.isFieldEditor

Kontrollera att både rensning och ny inmatning fungerar

Kör den här manuella sekvensen mot det fullständiga exemplet på varje macOS-version som appen stöder. Resultaten nedan är förväntningar utifrån exemplets logik, inte rapporterade testkörningar.

Åtgärd Förväntat godtaget värde Förväntat redigeringsbeteende
Starta 12 Utkastet innehåller 12
Klicka på Clear nil, visas som None Utkastet är tomt; fokus begärs
Skriv 24 och klicka sedan på Apply 24 Utkastet innehåller 24
Ersätt med - och tryck sedan på Retur Föregående godtagna värde Utkastet förblir -; ett meddelande visas
Ersätt med -8 och tryck sedan på Retur -8 Meddelandet rensas
Ange 0 och klicka sedan på Apply 0 Noll förblir åtskild från frånvaro
Ange 2.5 och klicka sedan på Apply Föregående godtagna värde Bråktalet finns kvar för korrigering
Töm fältet och klicka sedan på Apply nil Fältet finns kvar och kan redigeras igen

Testa även att klistra in ett heltal som är större än Int.max, klicka bort från fältet och tillbaka igen samt rensa fältet upprepade gånger. I en integrerad app bör du ta med alla åtgärder som ändrar modellen på andra ställen. Dessa kontroller testar konverteringsfel, återställning av fokus och synkronisering av utkast var för sig.

Återställningsvillkoret är konkret: när det godtagna talet blir nil finns redigeraren kvar och går att använda, och en senare giltig inmatning ger ett nytt tal. Om utkastet hålls oberoende blir det beteendet tydligt utan att förlita sig på odokumenterade ändringar i SwiftUI:s underliggande kontroller.

Källor