16 Des 2025·6 menit membaca

Validasi form SwiftUI yang terasa native: fokus dan error

Validasi form SwiftUI yang terasa native: kelola fokus, tampilkan error sebaris pada waktu yang tepat, dan tunjukkan pesan server dengan jelas tanpa mengganggu pengguna.

Validasi form SwiftUI yang terasa native: fokus dan error

Seperti apa validasi yang “terasa native” di SwiftUI

Form iOS yang terasa native itu tenang. Ia tidak berdebat dengan pengguna saat mereka mengetik. Ia memberi umpan balik yang jelas saat diperlukan, dan tidak membuat Anda mencari tahu apa yang salah.

Ekspektasi utama adalah prediktabilitas. Tindakan yang sama harus menghasilkan jenis umpan balik yang sama setiap saat. Jika sebuah field tidak valid, form harus menampilkannya di tempat yang konsisten, dengan nada yang konsisten, dan dengan langkah selanjutnya yang jelas.

Kebanyakan form membutuhkan tiga jenis aturan:

  • Aturan field: Apakah nilai tunggal ini valid (kosong, format, panjang)?
  • Aturan lintas-field: Apakah nilai saling cocok atau bergantung satu sama lain (Password dan Confirm Password)?
  • Aturan server: Apakah backend menerimanya (email sudah digunakan, diperlukan undangan)?

Timing lebih penting ketimbang pilihan kata yang pintar. Validasi yang baik menunggu momen yang bermakna, lalu berbicara sekali, dengan jelas. Ritme praktis terlihat seperti ini:

  • Diam saat pengguna sedang mengetik, terutama untuk aturan format.
  • Tampilkan umpan balik setelah meninggalkan field, atau setelah pengguna mengetuk Submit.
  • Biarkan error tetap terlihat sampai diperbaiki, lalu hapus segera.

Validasi harus senyap saat pengguna masih membentuk jawabannya, seperti mengetik email atau password. Menampilkan error pada karakter pertama terasa seperti mengomel, meskipun secara teknis benar.

Validasi menjadi terlihat ketika pengguna memberi sinyal bahwa mereka selesai: fokus berpindah, atau mereka mencoba submit. Saat itu mereka ingin panduan, dan saat itulah Anda bisa membantu mereka menuju field yang tepat yang perlu perhatian.

Jika timing tepat, semuanya jadi lebih mudah. Pesan sebaris bisa tetap singkat, perpindahan fokus terasa membantu, dan error sisi server terasa seperti umpan balik normal daripada hukuman.

Siapkan model status validasi sederhana

Form yang terasa native dimulai dengan pemisahan yang bersih: teks yang diketik pengguna bukanlah opini aplikasi tentang teks itu. Jika Anda mencampurnya, Anda akan menampilkan error terlalu awal atau kehilangan pesan server saat UI refresh.

Pendekatan sederhana adalah memberi setiap field state sendiri dengan empat bagian: nilai saat ini, apakah pengguna telah berinteraksi, error lokal (di perangkat), dan error dari server (jika ada). Lalu UI bisa memutuskan apa yang ditampilkan berdasarkan “touched” dan “submitted”, bukan bereaksi terhadap setiap ketukan tombol.

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

Beberapa aturan kecil menjaga ini tetap prediktabel:

  • Pisahkan error lokal dan server. Aturan lokal (seperti “wajib” atau “email tidak valid”) seharusnya tidak menimpa pesan server seperti “email sudah dipakai”.
  • Hapus serverError ketika pengguna mengedit field itu lagi, agar mereka tidak terpaku menatap pesan lama.
  • Hanya set touched = true ketika pengguna meninggalkan field (atau ketika Anda memutuskan mereka mencoba berinteraksi), bukan pada karakter pertama yang diketik.

Dengan ini, view Anda bisa bind ke value dengan leluasa. Validasi mengubah localError, dan lapisan API Anda mengisi serverError, tanpa saling bertengkar.

Penanganan fokus yang membimbing, bukan mengomel

Validasi SwiftUI yang baik harus terasa seperti keyboard sistem membantu pengguna menyelesaikan tugas, bukan seperti app yang menegur mereka. Fokus adalah bagian besar dari itu.

Polanya sederhana: perlakukan fokus sebagai sumber kebenaran tunggal dengan @FocusState. Definisikan enum untuk field Anda, bind setiap field ke enum itu, lalu pindah ke field berikutnya saat pengguna mengetuk tombol keyboard.

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 }

Yang menjaga supaya ini terasa native adalah menahan diri. Pindahkan fokus hanya pada aksi pengguna yang jelas: mengetuk Next, Done, atau tombol utama. Saat submit, fokus ke field pertama yang invalid (dan scroll ke sana bila perlu). Jangan merebut fokus saat pengguna sedang mengetik, walau nilainya saat ini tidak valid. Juga konsisten dengan label keyboard: Next untuk field menengah, Done untuk field terakhir.

Contoh umum adalah Sign Up. Pengguna mengetuk Create Account. Anda validasi sekali, tunjukkan error, lalu set fokus ke field pertama yang gagal (seringkali Email). Jika mereka sedang di field Password dan masih mengetik, jangan loncatkan mereka kembali ke Email di tengah ketikan. Detail kecil itu sering jadi pembeda antara “form iOS yang halus” dan “form yang mengganggu”.

Error sebaris yang muncul di waktu yang tepat

Error sebaris harus terasa seperti petunjuk lembut, bukan teguran. Perbedaan terbesar antara “native” dan “mengganggu” adalah kapan Anda menampilkan pesan.

Aturan timing

Jika error muncul saat seseorang baru mulai mengetik, itu mengganggu. Aturan yang lebih baik: tunggu sampai pengguna punya kesempatan yang adil untuk menyelesaikan field.

Momen yang baik untuk memperlihatkan error sebaris:

  • Setelah field kehilangan fokus
  • Setelah pengguna mengetuk Submit
  • Setelah jeda singkat saat mengetik (hanya untuk pemeriksaan yang jelas, seperti format email)

Pendekatan yang dapat diandalkan adalah menampilkan pesan hanya ketika field disentuh atau ketika submit dicoba. Form yang baru tetap tenang, tapi pengguna mendapat panduan yang jelas setelah mereka berinteraksi.

Tata letak dan gaya

Tidak ada yang terasa kurang iOS-like daripada tata letak yang lompat saat error muncul. Sisihkan ruang untuk pesan, atau animasikan kemunculannya agar tidak mendorong field berikutnya turun secara mendadak.

Jaga teks error singkat dan spesifik, dengan satu perbaikan per pesan. “Password harus minimal 8 karakter” dapat ditindaklanjuti. “Input tidak valid” tidak.

Untuk styling, tujuannya halus dan konsisten. Font kecil di bawah field (seperti footnote), satu warna error konsisten, dan highlight lembut pada field biasanya terbaca lebih baik daripada latar berat. Hapus pesan segera saat nilai menjadi valid.

Contoh realistis: di form signup, jangan tampilkan “Email tidak valid” saat pengguna masih mengetik name@. Tampilkan setelah mereka meninggalkan field, atau setelah jeda singkat, dan hapus saat alamat menjadi valid.

Alur validasi lokal: mengetik, meninggalkan field, submit

Go from no code to code
Dapatkan source code siap produksi untuk iOS, Android, web, dan backend saat Anda membutuhkannya.
Hasilkan Kode

Alur lokal yang baik punya tiga kecepatan: petunjuk lembut saat mengetik, pemeriksaan lebih tegas saat meninggalkan field, dan aturan penuh saat submit. Ritme itu yang membuat validasi terasa native.

Saat pengguna mengetik, jaga validasi tetap ringan dan tenang. Pikirkan “apakah ini jelas mustahil?” bukan “apakah ini sempurna?” Untuk field email, Anda mungkin hanya memeriksa bahwa ada @ dan tidak ada spasi. Untuk password, Anda bisa menampilkan helper kecil seperti “8+ karakter” setelah mereka mulai mengetik, tapi hindari error merah pada ketukan pertama.

Saat pengguna meninggalkan field, jalankan aturan stricter untuk field tunggal dan tampilkan error sebaris bila perlu. Di sinilah tempat “Wajib” dan “Format tidak valid”. Ini juga waktu yang baik untuk memotong spasi dan menormalisasi input (seperti lowercasing email) sehingga pengguna melihat apa yang akan dikirim.

Pada submit, validasi semuanya lagi, termasuk aturan lintas-field yang tidak bisa diputuskan lebih awal. Contoh klasik adalah Password dan Confirm Password yang harus cocok. Jika gagal, pindahkan fokus ke field yang perlu diperbaiki dan tampilkan satu pesan jelas di dekatnya.

Gunakan tombol submit dengan hati-hati. Biarkan aktif saat pengguna masih mengisi form. Nonaktifkan hanya ketika mengetuk tidak akan berbuat apa-apa (misalnya saat sedang mengirim). Jika Anda menonaktifkannya untuk input yang tidak valid, tetap tunjukkan apa yang harus diperbaiki di dekatnya.

Saat pengiriman, tampilkan status loading yang jelas. Ganti label tombol dengan ProgressView, cegah double tap, dan pertahankan form terlihat agar pengguna mengerti apa yang sedang terjadi. Jika permintaan lebih dari satu detik, label singkat seperti “Membuat akun...” mengurangi kecemasan tanpa menambah kebisingan.

Validasi sisi server tanpa membuat pengguna frustrasi

Pemeriksaan sisi server adalah sumber kebenaran terakhir, meskipun pemeriksaan lokal Anda kuat. Password mungkin lolos aturan lokal tapi gagal karena terlalu umum, atau email mungkin sudah dipakai.

Kemenangan UX terbesar adalah memisahkan “input Anda tidak diterima” dari “kita tidak bisa menjangkau server.” Jika permintaan timeout atau pengguna sedang offline, jangan tandai field sebagai invalid. Tampilkan banner atau alert tenang seperti “Tidak bisa terhubung. Coba lagi.” dan biarkan form tetap seperti semula.

Saat server mengatakan validasi gagal, biarkan input pengguna tetap dan tunjuk ke field yang tepat. Menghapus form, membersihkan password, atau memindahkan fokus menjauh membuat orang merasa dihukum karena mencoba.

Polanya sederhana: parse response error terstruktur menjadi dua ember: field errors dan form-level errors. Lalu perbarui state UI tanpa mengubah binding teks.

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

Yang biasanya terasa native:

  • Letakkan pesan field secara sebaris, di bawah field, menggunakan kata-kata server saat jelas.
  • Pindahkan fokus ke field pertama yang error hanya setelah submit, bukan di tengah mengetik.
  • Jika server mengembalikan beberapa masalah, tampilkan satu pesan pertama per field agar bisa dibaca.
  • Jika Anda punya detail field, jangan fallback ke “Terjadi kesalahan.”

Contoh: pengguna submit signup, dan server mengembalikan “email already in use.” Biarkan email yang mereka ketik, tampilkan pesan di bawah Email, dan fokus field itu. Jika server down, tampilkan satu pesan retry dan biarkan semua field tetap.

Cara menampilkan pesan server di tempat yang tepat

Ship auth flows faster
Tambah autentikasi dan dasar pendaftaran dengan cepat, lalu sesuaikan detail UX seperti timing dan fokus.
Mulai Membangun

Error server terasa “tidak adil” ketika muncul di banner acak. Letakkan setiap pesan sedekat mungkin dengan field yang membuatnya. Gunakan pesan umum hanya saat benar-benar tidak bisa mengaitkannya ke input tertentu.

Mulai dengan menerjemahkan payload error server ke identifier field SwiftUI Anda. Backend mungkin mengembalikan key seperti email, password, atau profile.phone, sementara UI Anda menggunakan enum seperti Field.email dan Field.password. Lakukan pemetaan sekali, segera setelah response, supaya sisa view tetap konsisten.

Cara fleksibel memodelkannya adalah menyimpan serverFieldErrors: [Field: [String]] dan serverFormErrors: [String]. Simpan array walau biasanya Anda hanya menampilkan satu pesan. Saat menampilkan error sebaris, pilih pesan yang paling membantu terlebih dahulu. Misalnya, “Email already in use” lebih berguna daripada “Invalid email” jika keduanya muncul.

Banyak error per field itu umum, tapi menampilkan semuanya berisik. Sebagian besar waktu, tampilkan hanya pesan pertama sebaris dan simpan sisanya untuk tampilan detail bila benar-benar diperlukan.

Untuk error yang tidak terkait field (session kedaluwarsa, rate limits, “Coba lagi nanti”), letakkan dekat tombol submit supaya pengguna melihatnya saat mereka bertindak. Juga pastikan Anda menghapus error lama saat sukses agar UI tidak terlihat “tersangkut”.

Akhirnya, hapus server error saat pengguna mengubah field terkait. Praktiknya, handler onChange untuk email sebaiknya menghapus serverFieldErrors[.email] sehingga UI langsung mencerminkan, “Oke, Anda sedang memperbaikinya.”

Aksesibilitas dan nada: pilihan kecil yang terasa native

Design SwiftUI style screens
Desain layar form di UI builder dan pertahankan urutan fokus yang dapat diprediksi.
Lihat Builder

Validasi yang baik bukan hanya soal logika. Ini juga soal bagaimana cara bacanya, terdengar, dan berperilaku dengan Dynamic Type, VoiceOver, dan bahasa yang berbeda.

Buat error mudah dibaca (bukan hanya dengan warna)

Asumsikan teks bisa menjadi besar. Gunakan style yang ramah Dynamic Type (seperti .font(.footnote) atau .font(.caption) tanpa ukuran tetap), dan biarkan label error membungkus. Jaga spasi konsisten agar tata letak tidak terlalu melompat saat error muncul.

Jangan hanya mengandalkan teks merah. Tambahkan ikon jelas, prefix “Error:”, atau keduanya. Ini membantu orang dengan masalah penglihatan warna dan mempercepat pemindaian.

Pemeriksaan cepat yang biasanya tahan lama:

  • Gunakan style teks yang terbaca dan menyesuaikan dengan Dynamic Type.
  • Biarkan pesan membungkus dan hindari pemotongan.
  • Tambahkan ikon atau label seperti “Error:” bersama warna.
  • Jaga kontras tinggi di Light Mode dan Dark Mode.

Buat VoiceOver membaca hal yang tepat

Saat field tidak valid, VoiceOver harus membaca label, nilai saat ini, dan error bersama-sama. Jika error adalah Text terpisah di bawah field, bisa jadi terlewat atau dibaca di luar konteks.

Dua pola membantu:

  • Gabungkan field dan error menjadi satu elemen aksesibilitas, sehingga error diumumkan saat pengguna fokus pada field.
  • Set hint atau value aksesibilitas yang menyertakan pesan error (misalnya, “Password, wajib, minimal 8 karakter”).

Nada juga penting. Tulis pesan yang jelas dan mudah dilokalisasi. Hindari slang, lelucon, dan kalimat samar seperti “Ups”. Pilih panduan spesifik seperti “Email kosong” atau “Password harus menyertakan angka”.

Contoh: form signup dengan aturan lokal dan server

Bayangkan form signup dengan tiga field: Email, Password, dan Confirm Password. Tujuannya adalah form yang tetap tenang saat pengguna mengetik, lalu menjadi membantu saat mereka mencoba melanjutkan.

Urutan fokus (apa yang dilakukan Return)

Dengan SwiftUI FocusState, setiap tekan Return harus terasa seperti langkah alami.

  • Return di Email: pindah fokus ke Password.
  • Return di Password: pindah fokus ke Confirm Password.
  • Return di Confirm Password: sembunyikan keyboard dan coba Submit.
  • Jika Submit gagal: pindah fokus ke field pertama yang butuh perhatian.

Langkah terakhir itu penting. Jika email tidak valid, fokus kembali ke Email, bukan hanya menampilkan pesan merah di tempat lain.

Kapan pesan muncul

Aturan sederhana menjaga UI tetap tenang: tampilkan pesan setelah field disentuh (pengguna meninggalkannya) atau setelah percobaan submit.

  • Email: tampilkan “Masukkan email yang valid” setelah meninggalkan field, atau saat Submit.
  • Password: tampilkan aturan (seperti panjang minimal) setelah meninggalkan, atau saat Submit.
  • Confirm Password: tampilkan “Password tidak cocok” setelah meninggalkan, atau saat Submit.

Sekarang sisi server. Misalkan pengguna submit dan API Anda mengembalikan seperti:

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

Apa yang dilihat pengguna: Email menampilkan pesan server tepat di bawahnya, dan Password menampilkan pesannya di bawah Password. Confirm Password tetap tenang kecuali juga gagal secara lokal.

Apa yang mereka lakukan selanjutnya: fokus mendarat di Email (error server pertama). Mereka ubah email, tekan Return untuk pindah ke Password, menyesuaikan password, lalu submit lagi. Karena pesan inline dan fokus berpindah dengan maksud, form terasa kooperatif, bukan mengomel.

Perangkap umum yang membuat validasi terasa “bukan iOS”

Keep rules in one place
Gunakan alat visual untuk mendefinisikan aturan, lalu kirimkan ke UI web dan mobile.
Buat App

Form bisa secara teknis benar tapi tetap terasa salah. Sebagian besar masalah validasi “bukan iOS” berasal dari timing: kapan Anda menampilkan error, kapan Anda memindahkan fokus, dan bagaimana Anda merespons server.

Kesalahan umum adalah berbicara terlalu awal. Jika Anda menunjukkan error pada ketukan pertama, orang merasa dimarahi saat mengetik. Menunggu sampai field disentuh (mereka meninggalkannya, atau mencoba submit) biasanya memperbaiki itu.

Respons async dari server juga bisa merusak alur. Jika permintaan signup kembali dan Anda tiba-tiba melompatkan fokus ke field lain, itu terasa acak. Pertahankan fokus di tempat terakhir pengguna berada, dan hanya pindahkan saat mereka mengetuk Next atau saat Anda menangani submit.

Perangkap lain adalah menghapus semua state setiap kali ada edit. Menghapus semua error saat sebuah karakter berubah bisa menyembunyikan masalah nyata, terutama dengan pesan server. Hapus hanya error untuk field yang sedang diedit, dan biarkan yang lain sampai benar-benar diperbaiki.

Hindari tombol submit yang “gagal diam-diam”. Menonaktifkan Submit selamanya tanpa menjelaskan apa yang harus diperbaiki memaksa pengguna menebak. Jika Anda menonaktifkannya, pasangkan dengan petunjuk spesifik, atau izinkan submit lalu pandu mereka ke masalah pertama.

Permintaan lambat dan ketukan ganda mudah terlewat. Jika Anda tidak menampilkan progres dan mencegah double submit, pengguna akan mengetuk dua kali, mendapat dua respons, dan berakhir dengan error membingungkan.

Berikut pemeriksaan cepat:

  • Tunda error sampai blur atau submit, bukan karakter pertama.
  • Jangan pindahkan fokus setelah respons server kecuali pengguna meminta.
  • Hapus error per field, bukan semuanya sekaligus.
  • Jelaskan mengapa submit diblokir (atau izinkan submit dengan panduan).
  • Tampilkan loading dan abaikan ketukan ekstra saat menunggu.

Contoh: jika server mengatakan “email already in use” (mungkin dari backend yang Anda buat di AppMaster), biarkan pesan di bawah Email, biarkan Password tak tersentuh, dan izinkan pengguna mengedit Email tanpa memulai ulang seluruh form.

Daftar periksa cepat dan langkah selanjutnya

Pengalaman validasi yang terasa native sebagian besar soal timing dan menahan diri. Anda bisa memiliki aturan ketat dan tetap membuat layar terasa tenang.

Sebelum rilis, periksa ini:

  • Validasi pada waktu yang tepat. Jangan tampilkan error pada ketukan pertama kecuali jelas membantu.
  • Pindahkan fokus dengan tujuan. Saat submit, lompat ke field pertama yang invalid dan jelaskan apa yang salah.
  • Gunakan kata-kata singkat dan spesifik. Katakan apa yang harus dilakukan selanjutnya, bukan apa yang “salah” dengan pengguna.
  • Hormati loading dan retry. Nonaktifkan tombol submit saat mengirim, dan pertahankan nilai yang diketik jika permintaan gagal.
  • Perlakukan error server sebagai umpan balik field bila memungkinkan. Petakan kode server ke field, dan gunakan pesan atas hanya untuk isu global.

Lalu uji seperti orang sungguhan. Pegang ponsel kecil dengan satu tangan dan coba selesaikan form dengan ibu jari. Setelah itu, nyalakan VoiceOver dan pastikan urutan fokus, pengumuman error, dan label tombol masih masuk akal.

Untuk debugging dan dukungan, membantu untuk mencatat kode validasi server (bukan pesan mentah) bersama nama layar dan field. Ketika pengguna berkata “tidak bisa daftar”, Anda bisa cepat tahu apakah itu email_taken, weak_password, atau timeout jaringan.

Untuk menjaga konsistensi di seluruh aplikasi, standarkan model field Anda (value, touched, local error, server error), penempatan error, dan aturan fokus. Jika Anda ingin membuat form iOS native lebih cepat tanpa menulis tiap layar, AppMaster (appmaster.io) dapat menghasilkan aplikasi SwiftUI beserta layanan backend, yang mempermudah menyelaraskan aturan validasi klien dan server.

Mudah untuk memulai
Ciptakan sesuatu yang menakjubkan

Eksperimen dengan AppMaster dengan paket gratis.
Saat Anda siap, Anda dapat memilih langganan yang tepat.

Memulai