16 dic 2025·8 min di lettura

Validazione dei moduli SwiftUI che sembra nativa: focus ed errori

Validazione dei moduli SwiftUI che sembra nativa: gestisci il focus, mostra gli errori inline al momento giusto e visualizza chiaramente i messaggi del server senza infastidire gli utenti.

Validazione dei moduli SwiftUI che sembra nativa: focus ed errori

Come dovrebbe apparire una validazione “convenzionale” in SwiftUI

Un modulo iOS che sembra nativo è calmo. Non contraddice l'utente mentre scrive. Fornisce un feedback chiaro quando è necessario e non ti fa cercare disperatamente cosa è andato storto.

La principale aspettativa è la prevedibilità. Le stesse azioni dovrebbero produrre lo stesso tipo di feedback ogni volta. Se un campo è invalido, il modulo dovrebbe mostrarlo nello stesso posto, con lo stesso tono e con un passaggio successivo chiaro.

La maggior parte dei moduli ha bisogno di tre tipi di regole:

  • Regole sul singolo campo: Questo valore singolo è valido (obbligatorio, formato, lunghezza)?
  • Regole tra campi: I valori corrispondono o dipendono l'uno dall'altro (Password e Conferma Password)?
  • Regole server: Il backend lo accetta (email già usata, invito richiesto)?

Il timing conta più delle frasi intelligenti. Una buona validazione aspetta un momento significativo, quindi parla una volta, chiaramente. Un ritmo pratico è questo:

  • Resta silenzioso mentre l'utente scrive, specialmente per le regole di formato.
  • Mostra feedback dopo aver lasciato un campo, o dopo che l'utente ha premuto Invia.
  • Mantieni gli errori visibili finché non vengono risolti, poi rimuovili immediatamente.

La validazione dovrebbe essere silenziosa mentre l'utente sta ancora formando la risposta, come quando scrive un'email o una password. Mostrare un errore al primo carattere sembra fastidioso, anche se tecnicamente corretto.

La validazione dovrebbe diventare visibile quando l'utente segnala di aver finito: il focus si sposta via, oppure prova a inviare. Quello è il momento in cui vogliono una guida, e puoi aiutarli a individuare esattamente il campo che necessita attenzione.

Se azzecchi il timing, tutto il resto diventa più semplice. I messaggi inline possono restare brevi, il movimento del focus sembra utile e gli errori lato server appaiono come feedback normale invece che come punizione.

Imposta un semplice modello di stato di validazione

Un modulo che sembra nativo parte da una separazione chiara: il testo digitato dall'utente non è la stessa cosa dell'opinione dell'app su quel testo. Se li mescoli, mostrerai errori troppo presto o perderai i messaggi del server quando l'interfaccia si aggiorna.

Un approccio semplice è dare a ogni campo il proprio stato con quattro parti: il valore corrente, se l'utente ha interagito, l'errore locale (sul dispositivo) e l'errore del server (se presente). Poi l'interfaccia può decidere cosa mostrare basandosi su “touch” e “submitted”, invece di reagire ad ogni battuta.

struct FieldState {
    var value: String = ""
    var touched: Bool = false
    var localError: String? = nil
    var serverError: String? = nil

    // One source of truth for what the UI displays
    func displayedError(submitted: Bool) -> String? {
        guard touched || submitted else { return nil }
        return localError ?? serverError
    }
}

struct FormState {
    var submitted: Bool = false
    var email = FieldState()
    var password = FieldState()
}

Alcune piccole regole mantengono questo prevedibile:

  • Mantieni separati gli errori locali e quelli del server. Le regole locali (come “obbligatorio” o “email non valida”) non dovrebbero sovrascrivere un messaggio del server come “email già utilizzata”.
  • Azzera serverError quando l'utente modifica di nuovo quel campo, così non restano bloccati a fissare un vecchio messaggio.
  • Imposta touched = true solo quando l'utente lascia il campo (o quando decidi che ha provato ad interagire), non al primo carattere digitato.

Con questo schema, la tua view può legarsi liberamente a value. La validazione aggiorna localError e il tuo layer API imposta serverError, senza che si contrastino a vicenda.

Gestione del focus che guida, non infastidisce

Una buona validazione SwiftUI dovrebbe dare l'idea che la tastiera di sistema stia aiutando l'utente a completare un compito, non che l'app lo stia rimproverando. Il focus è una parte importante di questo.

Un pattern semplice è trattare il focus come una singola fonte di verità usando @FocusState. Definisci un enum per i tuoi campi, lega ogni campo a quell'enum e poi passa al campo successivo quando l'utente preme il tasto della tastiera.

enum Field: Hashable { case email, password, confirm }

@FocusState private var focused: Field?

TextField("Email", text: $email)
  .textContentType(.emailAddress)
  .keyboardType(.emailAddress)
  .textInputAutocapitalization(.never)
  .submitLabel(.next)
  .focused($focused, equals: .email)
  .onSubmit { focused = .password }

SecureField("Password", text: $password)
  .submitLabel(.next)
  .focused($focused, equals: .password)
  .onSubmit { focused = .confirm }

Ciò che mantiene questa sensazione “nativa” è la moderazione. Sposta il focus solo su azioni chiare dell'utente: premendo Avanti, Fine o il pulsante primario. Al submit, imposta il focus sul primo campo invalido (e scorri fino a esso se necessario). Non rubare il focus mentre l'utente sta digitando, anche se il valore è attualmente invalido. Mantieni anche coerenza con le etichette della tastiera: Avanti per i campi intermedi, Fine per l'ultimo campo.

Un esempio comune è la registrazione. L'utente preme Crea account. Validi una volta, mostri gli errori e poi imposti il focus sul primo campo che fallisce (spesso Email). Se l'utente stava digitando la Password, non riportarlo a Email mentre è ancora nel mezzo della digitazione. Questo piccolo dettaglio spesso fa la differenza tra “form iOS rifinito” e “form fastidioso”.

Errori inline che appaiono al momento giusto

Gli errori inline dovrebbero sembrare un suggerimento discreto, non un rimprovero. La differenza più grande tra “nativo” e “fastidioso” è quando mostri il messaggio.

Regole di timing

Se un errore appare nel momento in cui qualcuno inizia a digitare, interrompe. Una regola migliore è: aspetta che l'utente abbia avuto una ragionevole possibilità di completare il campo.

Momenti buoni per rivelare un errore inline:

  • Dopo che il campo perde il focus
  • Dopo che l'utente preme Invia
  • Dopo una breve pausa durante la digitazione (solo per controlli ovvi, come il formato email)

Un approccio affidabile è mostrare un messaggio solo quando il campo è stato toccato o quando è stato tentato il submit. Un modulo fresco resta calmo, ma l'utente riceve una guida chiara una volta che interagisce.

Layout e stile

Niente sembra meno iOS-like del layout che salta quando appare un errore. Riserva spazio per il messaggio, oppure anima la sua comparsa in modo che non spinga giù il campo successivo in modo brusco.

Mantieni il testo d'errore breve e specifico, con una sola azione per messaggio. “La password deve avere almeno 8 caratteri” è azionabile. “Input non valido” no.

Per lo stile, punta al sottile e coerente. Un font piccolo sotto il campo (come footnote), un colore d'errore coerente e un leggero evidenziamento del campo solitamente risultano migliori di sfondi pesanti. Pulisci il messaggio non appena il valore diventa valido.

Un esempio realistico: in un modulo di registrazione, non mostrare “Email non valida” mentre l'utente sta ancora digitando nome@. Mostralo dopo aver lasciato il campo o dopo una breve pausa, e rimuovilo non appena l'indirizzo diventa valido.

Flusso di validazione locale: digitazione, uscita dal campo, submit

Tieni le regole in un unico posto
Usa strumenti visuali per definire le regole e distribuirle alle UI web e mobile.
Crea app

Un buon flusso locale ha tre velocità: suggerimenti leggeri mentre si digita, controlli più severi quando si lascia un campo e regole complete al submit. Questo ritmo è ciò che rende la validazione nativa.

Mentre l'utente digita, mantieni la validazione leggera e discreta. Pensa “è ovviamente impossibile?” non “è perfetto?”. Per un campo email, potresti controllare solo che contenga @ e nessuno spazio. Per una password, potresti mostrare un piccolo aiuto come “8+ caratteri” una volta che hanno iniziato a digitare, ma evita errori in rosso al primo carattere.

Quando l'utente lascia un campo, esegui controlli più severi sul singolo campo e mostra gli errori inline se necessario. Qui è dove vanno “Obbligatorio” e “Formato non valido”. È anche un buon momento per troncare gli spazi e normalizzare l'input (come rendere l'email in minuscolo) in modo che l'utente veda cosa verrà inviato.

Al submit, valida tutto di nuovo, incluse le regole tra campi che prima non potevi decidere. L'esempio classico è Password e Conferma Password che devono corrispondere. Se fallisce, porta il focus sul campo che necessita correzione e mostra un messaggio chiaro vicino ad esso.

Usa il bottone di submit con cautela. Lascialo abilitato mentre l'utente sta ancora compilando il modulo. Disabilitalo solo quando premendolo non farebbe nulla (per esempio, mentre è già in invio). Se lo disabiliti per input non validi, mostra comunque cosa correggere nelle vicinanze.

Durante l'invio, mostra uno stato di caricamento chiaro. Sostituisci l'etichetta del pulsante con una ProgressView, previeni doppi tocchi e mantieni il modulo visibile così gli utenti capiscono cosa sta succedendo. Se la richiesta impiega più di un secondo, un'etichetta breve come “Creazione account...” riduce l'ansia senza aggiungere rumore.

Validazione lato server senza frustrare gli utenti

I controlli lato server sono la fonte di verità finale, anche se i tuoi controlli locali sono forti. Una password potrebbe superare le tue regole ma fallire perché è troppo comune, o un'email potrebbe già essere presa.

Il più grande guadagno in UX è separare “il tuo input non è accettabile” da “non siamo riusciti a contattare il server”. Se la richiesta scade o l'utente è offline, non contrassegnare i campi come invalidi. Mostra un banner o un alert calmo tipo “Impossibile connettersi. Riprova.” e lascia il modulo esattamente com'è.

Quando il server dice che la validazione è fallita, mantieni l'input dell'utente intatto e punta ai campi esatti. Cancellare il modulo, azzerare una password o spostare il focus via fa sentire le persone punite per aver provato.

Un pattern semplice è parsare una risposta di errore strutturata in due cestini: errori per campo e errori a livello di form. Poi aggiorni lo stato dell'interfaccia senza cambiare i binding del testo.

struct ServerValidation: Decodable {
  var fieldErrors: [String: String]
  var formError: String?
}
// Map keys like "email" or "password" to your local field IDs.

Quello che solitamente sembra nativo:

  • Metti i messaggi dei campi inline, sotto il campo, usando il wording del server quando è chiaro.
  • Muovi il focus sul primo campo con un errore solo dopo il submit, non a metà digitazione.
  • Se il server ritorna più problemi, mostra il primo per campo per mantenere la leggibilità.
  • Se hai dettagli sul campo, non ricorrere a “Qualcosa è andato storto.”

Esempio: l'utente invia un modulo di registrazione e il server ritorna “email già in uso”. Mantieni l'email digitata, mostra il messaggio sotto Email e porta il focus su quel campo. Se il server è giù, mostra un unico messaggio di riprova e lascia tutti i campi intatti.

Come mostrare i messaggi del server nel posto giusto

Standardizza i pattern di validazione
Crea stati di errore e flussi di invio coerenti senza riscrivere la stessa logica per ogni schermo.
Crea ora

Gli errori del server sembrano “ingiusti” quando appaiono in un banner casuale. Metti ogni messaggio il più vicino possibile al campo che lo ha causato. Usa un messaggio generale solo quando davvero non puoi legarlo a un singolo input.

Inizia traducendo il payload di errore del server negli identificatori dei campi SwiftUI. Il backend potrebbe restituire chiavi come email, password o profile.phone, mentre la tua UI usa un enum come Field.email e Field.password. Fai la mappatura una sola volta, subito dopo la risposta, così il resto della view può restare coerente.

Un modo flessibile per modellare questo è mantenere serverFieldErrors: [Field: [String]] e serverFormErrors: [String]. Conserva array anche se di solito mostri un solo messaggio. Quando mostri un errore inline, scegli prima il messaggio più utile. Per esempio, “Email già in uso” è più utile di “Email non valida” se compaiono entrambi.

Più errori per campo sono comuni, ma mostrarli tutti è rumoroso. Quasi sempre mostra solo il primo messaggio inline e tieni gli altri per una vista dettagliata solo se strettamente necessario.

Per errori non legati a un campo (sessione scaduta, limiti di rate, “Riprova più tardi”), posizionali vicino al pulsante di submit così l'utente li vede quando agisce. Assicurati anche di cancellare gli errori vecchi al successo così l'interfaccia non sembra “bloccata”.

Infine, cancella gli errori del server quando l'utente cambia il campo correlato. In pratica, un handler onChange per email dovrebbe rimuovere serverFieldErrors[.email] così l'interfaccia riflette immediatamente “Ok, stai correggendo”.

Accessibilità e tono: scelte minori che sembrano native

Riduci il lavoro rifatto al cambiare dei requisiti
Mantieni allineate la validazione client e server mentre i requisiti cambiano e l'app viene rigenerata.
Inizia

Una buona validazione non riguarda solo la logica. Riguarda anche come si legge, si ascolta e si comporta con Dynamic Type, VoiceOver e lingue diverse.

Rendi gli errori facili da leggere (non solo con il colore)

Assumi che il testo possa diventare grande. Usa stili compatibili con Dynamic Type (come .font(.footnote) o .font(.caption) senza dimensioni fisse) e lascia che le etichette di errore vadano a capo. Mantieni lo spazio coerente così il layout non salta troppo quando appare un errore.

Non fare affidamento solo sul rosso. Aggiungi un'icona chiara, un prefisso “Errore:” o entrambi. Questo aiuta persone con problemi di visione dei colori e velocizza la scansione.

Un breve elenco di controlli che normalmente reggono:

  • Usa uno stile di testo leggibile che scala con Dynamic Type.
  • Consenti il wrapping e evita la troncatura dei messaggi d'errore.
  • Aggiungi un'icona o una label come “Errore:” insieme al colore.
  • Mantieni un buon contrasto sia in Light Mode che in Dark Mode.

Fai sì che VoiceOver legga la cosa giusta

Quando un campo è invalido, VoiceOver dovrebbe leggere l'etichetta, il valore corrente e l'errore insieme. Se l'errore è un Text separato sotto il campo, potrebbe essere saltato o letto fuori contesto.

Due pattern aiutano:

  • Combina il campo e il suo errore in un unico elemento di accessibilità, così l'errore viene annunciato quando l'utente mette il focus sul campo.
  • Imposta un hint o value di accessibilità che includa il messaggio d'errore (per esempio, “Password, obbligatoria, deve avere almeno 8 caratteri”).

Anche il tono conta. Scrivi messaggi chiari e facili da localizzare. Evita slang, battute e frasi vaghe come “Ops”. Preferisci indicazioni specifiche come “Email mancante” o “La password deve includere un numero”.

Esempio: un form di registrazione con regole locali e server

Immagina un form di registrazione con tre campi: Email, Password e Conferma Password. L'obiettivo è un modulo che resta silenzioso mentre l'utente digita, poi diventa d'aiuto quando prova ad andare avanti.

Ordine del focus (cosa fa Return)

Con SwiftUI FocusState, ogni pressione del tasto Return dovrebbe sembrare un passo naturale.

  • Return su Email: sposta il focus su Password.
  • Return su Password: sposta il focus su Conferma Password.
  • Return su Conferma Password: chiudi la tastiera e prova a inviare.
  • Se il submit fallisce: sposta il focus sul primo campo che necessita attenzione.

Quest'ultimo passaggio è importante. Se l'email è invalida, il focus torna su Email, non solo su un messaggio rosso da qualche parte.

Quando compaiono gli errori

Una regola semplice mantiene l'interfaccia calma: mostra i messaggi dopo che un campo è stato toccato (l'utente lo ha lasciato) o dopo un tentativo di submit.

  • Email: mostra “Inserisci un'email valida” dopo aver lasciato il campo o al submit.
  • Password: mostra le regole (per esempio lunghezza minima) dopo aver lasciato il campo o al submit.
  • Conferma Password: mostra “Le password non corrispondono” dopo aver lasciato il campo o al submit.

Ora il lato server. Supponiamo che l'utente invii e la tua API ritorni qualcosa come:

{
  "errors": {
    "email": "That email is already in use.",
    "password": "Password is too weak. Try 10+ characters."
  }
}

Quello che l'utente vede: Email mostra il messaggio del server proprio sotto di essa e Password mostra il suo messaggio sotto Password. Conferma Password resta silenzioso a meno che non fallisca localmente.

Quello che fanno dopo: il focus va su Email (il primo errore del server). Cambiano l'email, premono Return per andare su Password, sistemano la password e inviano di nuovo. Poiché i messaggi sono inline e il focus si muove con intento, il form sembra collaborativo, non rimproverante.

Trappole comuni che rendono la validazione “non iOS”

Aggiungi validazione tra campi
Crea controlli sui campi e tra campi con logica business drag-and-drop.
Crea modulo

Un modulo può essere tecnicamente corretto ma comunque dare una sensazione sbagliata. La maggior parte dei problemi “non iOS” deriva dal timing: quando mostri un errore, quando sposti il focus e come reagisci al server.

Un errore comune è parlare troppo presto. Se mostri un errore al primo carattere, le persone si sentono rimproverate mentre digitano. Aspettare che il campo sia stato toccato (lo lasciano o provano a inviare) di solito risolve il problema.

Le risposte asincrone del server possono anche rompere il flusso. Se una richiesta di registrazione ritorna e improvvisamente salti il focus su un altro campo, sembra casuale. Mantieni il focus dove l'utente era e spostalo solo quando tocca Avanti o quando gestisci un tentativo di submit.

Un'altra trappola è cancellare tutto ad ogni modifica. Azzerare tutti gli errori appena cambia un carattere può nascondere il problema reale, specialmente con i messaggi del server. Pulisci solo l'errore del campo che viene modificato e lascia il resto finché non è effettivamente risolto.

Evita i pulsanti di submit che “falliscono silenziosamente”. Disabilitare il submit per sempre senza spiegare cosa correggere costringe gli utenti a indovinare. Se lo disabiliti, abbinalo a suggerimenti specifici, oppure permetti il submit e poi guida l'utente verso il primo problema.

Richieste lente e tocchi duplicati sono facili da perdere. Se non mostri progresso e non previeni doppie submission, gli utenti toccheranno due volte, riceveranno due risposte e finiranno con errori confusi.

Ecco un rapido controllo di sanità mentale:

  • Ritarda gli errori fino al blur o al submit, non al primo carattere.
  • Non muovere il focus dopo una risposta del server a meno che l'utente non lo abbia chiesto.
  • Pulisci gli errori per campo, non tutto insieme.
  • Spiega perché il submit è bloccato (o permetti il submit con guida).
  • Mostra caricamento e ignora tocchi extra mentre aspetti.

Esempio: se il server dice “email già in uso” (magari da un backend che hai costruito con AppMaster), mantieni il messaggio sotto Email, lascia Password intatta e permetti all'utente di modificare Email senza riavviare tutto il modulo.

Checklist rapida e prossimi passi

Un'esperienza di validazione che sembra nativa riguarda soprattutto timing e moderazione. Puoi avere regole severe e comunque far sentire la schermata calma.

Prima di pubblicare, controlla questi punti:

  • Valida al momento giusto. Non mostrare errori al primo carattere a meno che non sia chiaramente utile.
  • Muovi il focus con uno scopo. Al submit, salta al primo campo invalido e rendi ovvio cosa non va.
  • Mantieni il wording breve e specifico. Di' cosa fare dopo, non cosa l'utente ha fatto “di sbagliato”.
  • Rispetta caricamenti e retry. Disabilita il pulsante di submit mentre invii e mantieni i valori digitati se la richiesta fallisce.
  • Tratta gli errori del server come feedback di campo quando possibile. Mappa i codici server a un campo e usa un messaggio in alto solo per problemi veramente globali.

Poi testalo come una persona reale. Tieni un piccolo telefono in una mano e prova a completare il modulo con il pollice. Dopo, attiva VoiceOver e assicurati che l'ordine del focus, gli annunci degli errori e le etichette dei pulsanti abbiano ancora senso.

Per il debugging e il supporto, conviene registrare i codici di validazione del server (non i messaggi grezzi) insieme allo schermo e al nome del campo. Quando un utente dice “non riesco a registrarmi”, puoi rapidamente capire se era email_taken, weak_password o un timeout di rete.

Per mantenere coerenza in tutta l'app, standardizza il tuo modello di campo (value, touched, local error, server error), la posizione degli errori e le regole di focus. Se vuoi costruire moduli iOS nativi più velocemente senza scrivere ogni schermo a mano, AppMaster (appmaster.io) può generare app SwiftUI insieme ai servizi backend, il che può facilitare l'allineamento di regole client e server.

Facile da avviare
Creare qualcosa di straordinario

Sperimenta con AppMaster con un piano gratuito.
Quando sarai pronto potrai scegliere l'abbonamento appropriato.

Iniziare