Lektion 2.14 · Stufe 2 – SwiftUI-Einstieg

Formulare

ca. 15 Minuten Lesezeit

Lernziel

Nach dieser Lektion kannst du Einstellungs- und Eingabebildschirme mit Form und Section bauen, Abschnitte mit Überschriften und Fußnoten versehen, Werte mit LabeledContent anzeigen und Formulare abhängig vom Zustand ein- und ausblenden.

Das Problem: Viele Eingaben, wenig Ordnung

In den letzten Lektionen hast du Textfelder, Schalter, Picker und Stepper kennengelernt. Packst du zehn davon in einen VStack, entsteht ein unübersichtlicher Haufen: Abstände passen nicht, Beschriftungen stehen mal links, mal oben, und nichts sieht aus wie die Einstellungen-App des iPhones.

Genau dafür gibt es Form: einen Container, der Eingabeelemente automatisch im typischen iOS-Stil anordnet. Gruppiert, sauber ausgerichtet, scrollbar und vertraut.

Das erste Formular

Swift
struct ProfileForm: View {
    @State private var name = ""
    @State private var notificationsOn = true
    @State private var theme = "System"

    var body: some View {
        Form {
            TextField("Name", text: $name)
            Toggle("Benachrichtigungen", isOn: $notificationsOn)
            Picker("Design", selection: $theme) {
                Text("System").tag("System")
                Text("Hell").tag("Hell")
                Text("Dunkel").tag("Dunkel")
            }
        }
    }
}

Erwartetes Ergebnis

Ein grauer Hintergrund mit einer weißen, abgerundeten Gruppe, darin drei Zeilen durch feine Linien getrennt: ein Textfeld, ein Schalter und ein Picker mit dem gewählten Wert rechts.

Screenshot folgt: Einfaches Formular mit drei Zeilen

Ohne einen einzigen Layout-Modifier sieht das schon aus wie eine echte iOS-App. Das Formular kümmert sich um:

  • den gruppierten Stil mit Hintergrund und Trennlinien,
  • die Ausrichtung: Beschriftung links, Wert rechts,
  • das Scrollen, wenn der Inhalt länger wird als der Bildschirm,
  • den passenden Stil für jedes Element: Textfelder ohne Rahmen, Picker als Menü.

Abschnitte mit Section

Ein Formular wird übersichtlich, wenn zusammengehörige Einträge gruppiert sind:

Swift
Form {
    Section("Profil") {
        TextField("Vorname", text: $firstName)
        TextField("Nachname", text: $lastName)
    }

    Section("Benachrichtigungen") {
        Toggle("Push-Nachrichten", isOn: $pushOn)
        Toggle("E-Mails", isOn: $mailOn)
    }

    Section {
        Button("Abmelden", role: .destructive) {
            // abmelden
        }
    }
}

Jede Section ist eine eigene Gruppe mit Abstand zur nächsten. Der Text in den Klammern wird als Überschrift darüber angezeigt. Eine Section ohne Überschrift ist auch möglich, wie hier beim Abmelden-Button.

Fußnoten

Manchmal braucht ein Abschnitt eine Erklärung. Dafür gibt es eine Fußnote:

Swift
Section {
    Toggle("Standort teilen", isOn: $shareLocation)
} header: {
    Text("Datenschutz")
} footer: {
    Text("Dein Standort wird nur verwendet, um Cafés in deiner Nähe zu finden.")
}

Hier hat Section drei Closures: den Inhalt, header: und footer:. Die Fußnote erscheint klein und grau unter der Gruppe, genau wie in den iPhone-Einstellungen.

Werte anzeigen mit LabeledContent

Nicht jede Zeile ist eine Eingabe. Oft willst du einfach einen Wert anzeigen:

Swift
Section("Über die App") {
    LabeledContent("Version", value: "1.2.0")
    LabeledContent("Entwickler", value: "Anna Berger")
    LabeledContent("Gespeicherte Einträge") {
        Text(42, format: .number)
    }
}

LabeledContent zeigt eine Beschriftung links und den Wert rechts, gedämpft, wie in den Systemeinstellungen. Die zweite Form mit Closure erlaubt beliebige Views als Wert.

Elemente in Formularen

Viele Elemente passen sich in einem Form automatisch an. Ein paar Besonderheiten:

ElementVerhalten im Formular
TextFieldkein Rahmen, füllt die Zeile. Der Platzhalter dient als Beschriftung.
Pickerstandardmäßig .menu: Wert rechts, Menü beim Tippen
Picker mit .navigationLinkeigene Seite mit Liste (braucht einen NavigationStack, Lektion 2.17)
Picker mit .inlinealle Optionen direkt als Zeilen mit Häkchen
Stepper, Toggle, DatePickerBeschriftung links, Bedienelement rechts
ButtonText in Akzentfarbe über die ganze Zeile antippbar

Tipp

Für Textfelder in Formularen hat sich ein Muster bewährt: ein TextField mit LabeledContent drumherum, wenn die Beschriftung auch nach dem Ausfüllen sichtbar bleiben soll:

Swift
LabeledContent("Name") {
    TextField("Pflichtfeld", text: $name)
        .multilineTextAlignment(.trailing)
}

Zustandsabhängige Formulare

Weil Form ein ganz normaler Teil der Beschreibung ist (Lektion 2.1), kannst du Abschnitte und Zeilen mit if ein- und ausblenden:

Swift
struct DeliveryForm: View {
    @State private var wantsDelivery = false
    @State private var street = ""
    @State private var city = ""

    var body: some View {
        Form {
            Section {
                Toggle("Lieferung", isOn: $wantsDelivery)
            }

            if wantsDelivery {
                Section("Lieferadresse") {
                    TextField("Straße", text: $street)
                    TextField("Ort", text: $city)
                }
            }
        }
        .animation(.default, value: wantsDelivery)
    }
}

Schaltet der Nutzer „Lieferung“ ein, erscheint der Adressabschnitt. Mit .animation(.default, value: wantsDelivery) gleitet er sanft ein, statt plötzlich aufzutauchen. Animationen lernst du ausführlich in Lektion 3.23.

Formulare mit Struct-Daten

Bei vielen Feldern werden die vielen @State-Variablen unübersichtlich. Besser: eine Struct (Lektion 1.12) und ein @State. Mit $profile.name bekommst du ein Binding direkt auf eine Eigenschaft (Lektion 2.10, 🟡-FAQ):

Swift
struct Profile {
    var name = ""
    var email = ""
    var newsletter = false
    var birthday = Date.now
}

struct ProfileEditor: View {
    @State private var profile = Profile()

    var body: some View {
        Form {
            Section("Person") {
                TextField("Name", text: $profile.name)
                DatePicker("Geburtstag", selection: $profile.birthday, in: ...Date.now, displayedComponents: .date)
            }
            Section("Kontakt") {
                TextField("E-Mail", text: $profile.email)
                    .keyboardType(.emailAddress)
                    .textInputAutocapitalization(.never)
                Toggle("Newsletter", isOn: $profile.newsletter)
            }
        }
    }
}

Alle Daten des Formulars stecken jetzt in einem Wert. Den kannst du später mit einem Klick speichern oder an eine andere View übergeben. Genau so arbeiten wir in Projekt C.

Validierung

Ein Formular sollte erst abgeschickt werden können, wenn alles stimmt:

Swift
struct SignUpForm: View {
    @State private var name = ""
    @State private var email = ""
    @State private var acceptsTerms = false

    var isValid: Bool {
        !name.trimmingCharacters(in: .whitespaces).isEmpty
            && email.contains("@")
            && acceptsTerms
    }

    var body: some View {
        Form {
            Section {
                TextField("Name", text: $name)
                TextField("E-Mail", text: $email)
            } footer: {
                if !email.isEmpty && !email.contains("@") {
                    Text("Bitte gib eine gültige E-Mail-Adresse ein.")
                        .foregroundStyle(.red)
                }
            }

            Section {
                Toggle("Ich akzeptiere die AGB", isOn: $acceptsTerms)
            }

            Section {
                Button("Registrieren") {
                    // registrieren
                }
                .disabled(!isValid)
            }
        }
    }
}

isValid ist eine berechnete Eigenschaft: Sie fasst alle Bedingungen an einer Stelle zusammen. Der Button ist nur aktiv, wenn alles passt, und die Fußnote zeigt einen Hinweis, sobald die Eingabe erkennbar falsch ist.

Tipp

Zeig Fehler erst, wenn der Nutzer etwas eingegeben hat. Ein Formular, das schon beim Öffnen rote Fehlermeldungen zeigt, wirkt abweisend. Deshalb steht oben !email.isEmpty && vor der Prüfung.

Typische Fehler

Formular sieht leer aus

Ein TextField in einem Form hat keinen Rahmen. Ohne Platzhalter ist die Zeile leer. Gib jedem Textfeld einen aussagekräftigen Platzhalter oder ein LabeledContent.

Dieser Stil braucht einen NavigationStack um das Formular, sonst ist der Picker ausgegraut. Navigation kommt in Lektion 2.17.

Ganze Zeile reagiert auf einen Button

Stehen in einer Zeile zwei Buttons, etwa in einem HStack, löst ein Tipp oft beide aus, weil die ganze Zeile antippbar ist. Gib beiden Buttons .buttonStyle(.borderless). Dann reagiert jeder nur auf seinen eigenen Bereich.

Formular in einer ScrollView

Ein Form scrollt selbst. In eine ScrollView gepackt, kann es auf null Höhe zusammenschrumpfen und unsichtbar werden. Lass die ScrollView weg.

FAQ

Anfänger

Was bedeutet das überhaupt?

Was ist der Unterschied zwischen Form und List? Optisch fast keiner, beide sehen gruppiert aus. Form ist für Eingaben gedacht und passt die Elemente entsprechend an. List ist für Daten, also viele gleichartige Einträge (nächste Lektion). Im Zweifel: Einstellungen → Form, Inhalte → List.

Wie viele Sections sollte ein Formular haben? So viele, wie es sinnvolle Gruppen gibt. Faustregel: drei bis sieben Zeilen pro Section. Wird ein Formular sehr lang, lohnt es sich, es auf mehrere Seiten aufzuteilen.

Kann ich das Aussehen eines Formulars ändern? Begrenzt. Mit .formStyle(.grouped) oder .columns wählst du einen Grundstil. Für den Hintergrund gibt es .scrollContentBackground(.hidden) und dann .background(...). Für ganz eigene Designs baut man eher selbst mit Stacks.

Fortgeschritten

Warum verhält es sich so?

Wie mache ich ein Feld zum Pflichtfeld? SwiftUI hat dafür keine eingebaute Markierung. Üblich ist ein Hinweis im Platzhalter („Pflichtfeld“), eine Fußnote und ein deaktivierter Speichern-Button, solange es leer ist, wie im Validierungsbeispiel.

Kann ich eine Section ein- und ausklappen? Ja, mit Section("Titel", isExpanded: $isExpanded) { ... } bekommt die Überschrift einen Pfeil zum Auf- und Zuklappen. Das funktioniert vor allem in Listen mit Seitenleisten-Stil gut.

Profi

Was passiert unter der Haube?

Wie weiß ein Toggle, dass er in einem Formular steht? Form setzt Informationen in die Umgebung, die die enthaltenen Elemente auslesen, etwa welchen Stil sie verwenden sollen. Deshalb sieht derselbe Picker in einem Form anders aus als in einem VStack. Eigene Stile (Lektion 3.28) können diese Informationen ebenfalls nutzen.

Was passiert mit einem Formular auf dem Mac? Mit .formStyle(.grouped) sieht es dem iPhone ähnlich. Der Standardstil auf dem Mac ordnet Beschriftungen und Elemente dagegen in zwei Spalten an, wie in den Systemeinstellungen des Mac. Derselbe Code passt sich der Plattform an.

Übungen

Anfänger

Übung 1: Wecker

Baue ein Formular für einen Wecker: eine Section „Zeit“ mit einem DatePicker nur für die Uhrzeit, eine Section „Optionen“ mit einem Textfeld für die Bezeichnung und einem Toggle „Schlummern“, und eine Section mit einem roten „Wecker löschen“-Button.

Lösung Übung 1Erst selbst probieren!
Swift
struct AlarmForm: View {
    @State private var time = Date.now
    @State private var label = "Wecker"
    @State private var snooze = true

    var body: some View {
        Form {
            Section("Zeit") {
                DatePicker("Uhrzeit", selection: $time, displayedComponents: .hourAndMinute)
            }
            Section("Optionen") {
                TextField("Bezeichnung", text: $label)
                Toggle("Schlummern", isOn: $snooze)
            }
            Section {
                Button("Wecker löschen", role: .destructive) { }
            }
        }
    }
}
Fortgeschritten

Übung 2: Kaffeebestellung als Formular

Nimm die Kaffeebestellung aus Lektion 2.13 und bau sie als Form um. Größe, Milch und Anzahl kommen in eine Section „Bestellung“, die Abholzeit in „Abholung“. Eine letzte Section zeigt mit LabeledContent den Gesamtpreis (3,50 € pro Tasse, +0,50 € für Pflanzenmilch) und hat einen Button „Bestellen“.

Profi

Übung 3: Bedingtes Formular

Baue ein Formular für eine Veranstaltungsanmeldung: Name und E-Mail, ein Picker „Teilnahme“ mit „Vor Ort“ und „Online“. Bei „Vor Ort“ erscheint eine zusätzliche Section mit einem Toggle „Mittagessen“ und, wenn dieser an ist, einem Picker für vegetarisch/vegan/alles. Der „Anmelden“-Button ist nur aktiv, wenn Name und E-Mail ausgefüllt sind. Verwende eine Struct für alle Daten.

Zusammenfassung

  • Form ordnet Eingabeelemente automatisch im iOS-Stil an und scrollt selbst.
  • Section gruppiert Zeilen, mit optionalem header: und footer:.
  • LabeledContent zeigt Beschriftung und Wert nebeneinander.
  • Mit if blendest du Abschnitte abhängig vom Zustand ein, .animation macht es sanft.
  • Eine Struct mit einem @State hält alle Formulardaten zusammen. $profile.name bindet einzelne Felder.
  • Eine berechnete Eigenschaft isValid steuert, ob abgeschickt werden darf.

Spickzettel

Swift
Form {
    Section("Profil") {
        TextField("Name", text: $profile.name)
        Toggle("Newsletter", isOn: $profile.newsletter)
    }
    Section {
        Picker("Design", selection: $theme) { ... }
    } footer: {
        Text("Erklärung")
    }
    Section {
        LabeledContent("Version", value: "1.0")
        Button("Speichern") { save() }.disabled(!isValid)
    }
}
.animation(.default, value: someToggle)

Lektion geschafft?

Markiere sie als erledigt, damit du deinen Fortschritt siehst.