Form Components
PowerSyncForm provides React Hook Form-like APIs for building type-safe, validated forms in SwiftUI.
Basic Form Setup
Section titled “Basic Form Setup”import SwiftUIimport ZyraForm
struct UserFormView: View { @StateObject var form = PowerSyncForm<UserFormValues>(schema: UsersSchema)
var body: some View { Form { // Form fields here } }}Form Values Protocol
Section titled “Form Values Protocol”Define your form values struct:
struct UserFormValues: FormValues { var email: String = "" var name: String = "" var age: Int? var website: String?
init() {}}Form Binding
Section titled “Form Binding”Create bindings for TextField components:
// String binding (validates on change)if let emailBinding = form.binding(for: "email") { TextField("Email", text: emailBinding)}
// String binding with onBlur validationif let emailBinding = form.bindingWithBlur(for: "email") { TextField("Email", text: emailBinding) .onBlur { form.handleBlur("email") }}
// Integer bindingif let ageBinding = form.intBinding(for: "age") { TextField("Age", text: ageBinding) .keyboardType(.numberPad)}
// Double bindingif let priceBinding = form.doubleBinding(for: "price") { TextField("Price", text: priceBinding) .keyboardType(.decimalPad)}
// Boolean bindingif let isActiveBinding = form.boolBinding(for: "is_active") { Toggle("Is Active", isOn: isActiveBinding)}Error Display
Section titled “Error Display”Show validation errors:
TextField("Email", text: form.binding(for: "email") ?? .constant(""))
if form.hasError("email") { Text(form.getError("email") ?? "") .foregroundColor(.red) .font(.caption)}Form State
Section titled “Form State”Access form state properties:
form.isValid // Bool: Is form valid?form.isDirty // Bool: Has form been modified?form.isSubmitting // Bool: Is form submitting?form.errors // FormErrors: All form errorsValidation Modes
Section titled “Validation Modes”Choose when validation occurs:
// Validate on every change (default)@StateObject var form = PowerSyncForm<UserFormValues>( schema: UsersSchema, mode: .onChange)
// Validate on blur@StateObject var form = PowerSyncForm<UserFormValues>( schema: UsersSchema, mode: .onBlur)
// Validate on submit only@StateObject var form = PowerSyncForm<UserFormValues>( schema: UsersSchema, mode: .onSubmit)
// Validate once field is touched@StateObject var form = PowerSyncForm<UserFormValues>( schema: UsersSchema, mode: .onTouched)Form Submission
Section titled “Form Submission”Handle form submission with validation:
// Synchronous handlerButton("Submit") { form.handleSubmit { values in print("Form values:", values) // Save to database }}
// Async handlerButton("Submit") { form.handleSubmit { values async throws in try await saveToDatabase(values) }}
// Result-based submissionButton("Submit") { let result = form.submit() switch result { case .success(let values): print("Success:", values) case .failure(let errors): print("Errors:", errors.allErrors) }}Form Actions
Section titled “Form Actions”// Reset to initial valuesform.reset()
// Reset to new valuesform.reset(to: UserFormValues(email: "new@example.com"))
// Manually validatelet isValid = form.validate()
// Validate single fieldform.validateField("email")
// Set field valueform.setValue("test@example.com", for: "email")
// Set multiple valuesform.setValues([ "email": "user@example.com", "name": "John Doe"])
// Get field valuelet email = form.getValue(for: "email")
// Get all valueslet allValues = form.getValues()
// Update values from dictionary (useful for server responses)form.updateValues([ "email": "updated@example.com", "name": "Updated Name"])
// Change validation mode dynamicallyform.setMode(.onBlur)Field Watching
Section titled “Field Watching”Watch field values for conditional logic:
// Watch single fieldlet email = form.watch("email")
// Watch multiple fieldslet fields = form.watch(["email", "name"])
// Watch all fieldslet allFields = form.watchAll()Complete Example
Section titled “Complete Example”struct RegistrationFormView: View { @StateObject var form = PowerSyncForm<RegistrationFormValues>( schema: UsersSchema, mode: .onChange )
var body: some View { Form { Section("Account Information") { if let emailBinding = form.binding(for: "email") { TextField("Email", text: emailBinding) .textContentType(.emailAddress) .autocapitalization(.none)
if form.hasError("email") { Text(form.getError("email") ?? "") .foregroundColor(.red) .font(.caption) } }
if let nameBinding = form.binding(for: "name") { TextField("Name", text: nameBinding)
if form.hasError("name") { Text(form.getError("name") ?? "") .foregroundColor(.red) .font(.caption) } } }
Section { Button(action: { form.handleSubmit { values in Task { await saveUser(values) } } }) { Text("Register") .frame(maxWidth: .infinity) } .disabled(!form.isValid || form.isSubmitting)
Button("Reset") { form.reset() } } } .navigationTitle("Registration") }
private func saveUser(_ values: RegistrationFormValues) async { // Save logic here }}Reusable Form Field Component
Section titled “Reusable Form Field Component”Create a reusable form field component:
struct FormField<Content: View>: View { let field: String @ObservedObject var form: PowerSyncForm<GenericFormValues> let content: (Binding<String>) -> Content
var body: some View { VStack(alignment: .leading, spacing: 4) { if let binding = form.binding(for: field) { content(binding) .textFieldStyle(.roundedBorder)
if form.hasError(field) { Text(form.getError(field) ?? "") .foregroundColor(.red) .font(.caption) } } } }}
// UsageFormField(field: "email", form: form) { binding in TextField("Email", text: binding)}Next Steps
Section titled “Next Steps”- Validation - Learn about validation rules
- Conditional Fields - Show/hide fields dynamically
- CRUD Operations - Save form data to database