SwiftUI Spickzettel
Im Juni 2025 habe ich angefangen, den 100 Days of SwiftUI Kurs durchzuarbeiten. Es ist ein großartiger Kurs, und ich bin wirklich beeindruckt, wie viel qualitativ hochwertiger Inhalt und Kurse Paul Hudson bereitstellt – und pflegt!! Paul, vielen Dank dafür! 🙏🏼
Aber es ist eine Menge Inhalt, also hier sind meine Notizen – hoffentlich in einem leicht navigierbaren Spickzettel-Format. Ich habe eine grobe Struktur im Kopf, aber ich werde den Inhalt nur dann ausfüllen, wenn ich ihn brauche. Also erwarte keine vollständige Übersicht!
Swift
Für einen umfassenden Überblick siehe Lerne essentielles Swift in einer Stunde.
Im folgenden Kapitel habe ich nur die Teile hinzugefügt, die ich mindestens einmal nachschlagen musste.
struct & berechnete Eigenschaften
struct Employee {
let name: String
var vacationAllocated = 14
var vacationTaken = 0
var vacationRemaining: Int {
vacationAllocated - vacationTaken
}
}
Optionals
- Optionals erlauben es uns, das Fehlen von Daten darzustellen, was bedeutet, dass wir sagen können „dieser Integer hat keinen Wert“ – das ist anders als eine feste Zahl wie 0.
- Beispiel:
var str:String?kann einen String oder nil halten - Alles, was nicht optional ist, hat definitiv einen Wert, selbst wenn das nur ein leerer String ist.
- Das Entpacken eines Optionals ist der Prozess, in eine Box zu schauen, um zu sehen, was sie enthält: Wenn ein Wert darin ist, wird er zur Verwendung zurückgegeben, andernfalls wird nil darin sein.
- Wir können
if letverwenden, um Code auszuführen, wenn das Optional einen Wert hat, oderguard let, um Code auszuführen, wenn das Optional keinen Wert hat – aber mitguardmüssen wir danach immer die Funktion verlassen.
func printSquare(of number: Int?) {
guard let number = number else {
print("Eingabe fehlt")
return
}
print("\(number) x \(number) ist \(number * number)")
}
- Der nil-Zusammenführungsoperator, ??, entpackt und gibt den Wert eines Optionals zurück oder verwendet stattdessen einen Standardwert.
let new = captains["Serenity"] ?? "N/A"
- Optional Chaining ermöglicht es uns, ein Optional innerhalb eines anderen Optionals mit einer praktischen Syntax zu lesen.
- Wenn eine Funktion Fehler werfen könnte, kannst du sie mit
try?in ein Optional umwandeln – du erhältst entweder den Rückgabewert der Funktion oder nil, wenn ein Fehler auftritt.
Protokolle und Erweiterungen
protocol Vehicle {
func estimateTime(for distance: Int) -> Int
func travel(distance: Int)
}
extension String {
func trimmed() -> String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
Arrays & Sortieren
Alle Arrays haben eingebaute sort() und sorted() Methoden, die verwendet werden können, um das Array zu sortieren.
sort()sortiert das Array an Ort und Stellesorted()gibt ein neues, sortiertes Array zurück.
Wenn das Array einfach ist, kannst du einfach sort() direkt aufrufen, um ein Array vor Ort zu sortieren:
var names = ["Jemima", "Peter", "David", "Kelly", "Isabella"]
names.sort()
Wenn du komplexere Strukturen hast, musst du den Vergleich übergeben:
struct User {
var firstName: String
}
var users = [
User(firstName: "Jemima"),
User(firstName: "Peter"),
User(firstName: "David"),
User(firstName: "Kelly"),
User(firstName: "Isabella")
]
users.sort {
$0.firstName < $1.firstName
}
Wir können unsere eigenen Typen Comparable konform machen, und wenn wir das tun, erhalten wir auch eine sorted() Methode ohne Parameter. Das erfordert zwei Schritte:
- Füge die
Comparable-Konformität zur Definition von User hinzu. - Füge eine Methode namens
<hinzu, die zwei Benutzer nimmt und true zurückgibt, wenn der erste vor dem zweiten sortiert werden soll.
So sieht das im Code aus:
struct User: Identifiable, Comparable {
let id = UUID()
var firstName: String
var lastName: String
static func <(lhs: User, rhs: User) -> Bool {
lhs.lastName < rhs.lastName
}
}
Strings
- Sie sind besonders, und es gibt viel zu wissen…
- Das funktioniert nicht:
let name = "Paul"
let firstLetter = name[0]
Daten
Date, DateComponents und DateFormatter
enum
enum werden so erstellt:
enum Weekday {
case monday, tuesday, wednesday, thursday, friday
}
Ein switch-Statement mit enum sieht so aus:
switch loadingState {
case .loading:
LoadingView()
case .success:
SuccessView()
case .failed:
FailedView()
}
SwiftUI
- Interactful ist ein nettes Tool, um die verschiedenen Views & Komponenten zu navigieren und auszuprobieren.
- Human Interfaces Guideline
Views
- Alles ist eine View in SwiftUI 😜
- Code ausführen, wenn eine View angezeigt wird, mit
onAppear().
Sogar ForEach ist eine View, deshalb können wir schreiben
ForEach(0..<5) {
Text("Row \($0)")
}
Hinweis: Wir können nicht ForEach(0..<5) schreiben, weil ForEach einen Range<Int> erwartet, nicht einen ClosedRange<Int>!
ForEach View
ForEach ist eine View, die aus den Sub-Views besteht, die in jeder Schleifeninstanz erstellt werden.
Wir verwenden sie typischerweise, um Sub-Views basierend auf einem Zähler oder einem Array zu erstellen.
ForEach mit einem Array:
import SwiftUI
struct ContentView: View {
let items = ["Apple", "Banana", "Cherry"]
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text(item)
}
}
}
}
Dateneingabe
TextField
Picker
Picker wird verwendet, um eine von vielen möglichen Auswahlen zu treffen.
Ein regulärer Picker sieht so aus:
Picker("Anzahl der Personen", selection: $numberOfPeople) {
ForEach(2 ..< 100 , id: \.self) {
Text("\($0) Personen")
}
}

Ein Picker kann mit dem PickerStyle-Modifier modifiziert werden:
Picker("Trinkgeldprozentsatz", selection: $tipPercentage) {
ForEach(tipPercentages, id: \.self) {
Text($0, format: .percent)
}
}
.pickerStyle(.segmented)

Stepper
Ein Stepper ist ein zweigeteiltes Steuerungselement, das Menschen verwenden, um einen inkrementellen Wert zu erhöhen oder zu verringern.
@State private var count: Int = 0
var body: some View {
Stepper("\(count)",
value: $count,
in: 0...100
)
}
DatePickerfür Daten. Verwende dendisplayedComponents-Parameter, um Daten oder Zeiten zu steuern.FormPicker- Navigationsleiste
Alerts & Bestätigungsdialoge
Alerts
Alerts sind eine Erweiterung einer View mit einer Bool-Variablen, die entscheidet, ob sie angezeigt werden oder nicht.
struct ContentView: View {
@State private var showingAlert = false
var body: some View {
Button("Alert anzeigen") {
showingAlert = true
}
.alert("Wichtige Nachricht", isPresented: $showingAlert) {
Button("OK") { }
}
}
}
Bestätigungsdialoge
Verwende sie, wenn viele Schaltflächen/Optionen verfügbar sind. Für Beispielcode siehe dieses Repo.
Hinweis: Vor iOS 26 schoben sie sich von unten herein, in iOS 26 erscheinen sie innerhalb des Bildschirms.
Bestätigungsdialog in iOS 18.5:

Bestätigungsdialog in iOS 26:

Text
Text ist ein Textfeld, das Text beschreibt.
Hinweis: Textfelder mit unterschiedlichem Styling können zusammengefügt werden, um ein großes Textfeld mit unterschiedlich gestylten Teilen zu bilden:
Text(page.title)
.font(.headline)
+ Text(": ") +
Text("Seitenbeschreibung hier")
.italic()
Und du erhältst einen Text mit verschiedenen kombinierten Stylings:

Listen
Scrollende Datentabellen mit List erstellen, insbesondere wie sie Zeilen direkt aus Datenarrays erstellen kann.
List {
Section {
Label("Sonne", systemImage: "sun.max")
Label("Wolke", systemImage: "cloud")
Label("Regen", systemImage: "cloud.rain")
}
}
Schaltflächen in Listen: Wenn du eine Schaltfläche in eine Liste platzierst, wird das GESAMTE Listenelement anklickbar! Wenn es mehr als eine Schaltfläche in einer Liste gibt, wird, wo immer du auf das Listenelement klickst, ALLE Schaltflächen nacheinander angeklickt!
Um das zu beheben und das gewünschte Verhalten zu erhalten, verwende .buttonStyle(.plain)
Das Gleiche gilt für HStack:
HStack {
if label.isEmpty == false {
Text(label)
}
ForEach(1..<maximumRating + 1, id: \.self) { number in
Button {
rating = number
} label: {
image(for: number)
.foregroundStyle(number > rating ? offColor : onColor)
}
}
}
.buttonStyle(.plain)
Bilder
struct ContentView: View
{
var body: some View {
Image (example)
.resizable ()
.scaledToFit ()
.frame(width: 300, height: 300)}
}

Ersetze ScaledToFit durch ScaledToFill und erhalte

struct ContentView: View {
var body: some View {
Image (•example)
.resizable ()
.scaledToFit()
.containerRelativeFrame(horizontal) { size, axis in
size * 0.8
}
}
}

Toolbar
Bundle
Dateien aus unserem App-Bundle lesen, indem wir ihren Pfad mit der Bundle-Klasse nachschlagen, einschließlich dem Laden von Strings von dort.
Animationen
Behandelt in Tag 32-34. TODO Ich muss die Clips noch einmal ansehen, um meine Notizen/Spickzettel zu extrahieren.
- Animationen implizit mit dem
animation()-Modifier erstellen. - Animationen mit Verzögerungen und Wiederholungen anpassen und zwischen Ease-in-Ease-out und Spring-Animationen wählen.
- Den
animation()-Modifier an Bindungen anhängen, um Änderungen direkt von UI-Steuerelementen zu animieren. withAnimation()verwenden, um explizite Animationen zu erstellen.- Mehrere
animation()-Modifier an eine einzelne View anhängen, um den Animationsstapel zu steuern.
Daten laden
Wenn es synchron ist:
View...
.onAppear(loadIt)
?? Wie wird es gemacht, wenn loadIt asynchron ist??
Werte an Views übergeben und von ihnen zurückgeben
Wie in Auswählen und Bearbeiten von Kartenanmerkungen gesehen
Stell dir vor, ich habe eine View, die als Sheet geöffnet wird und ein Location (ein selbst definiertes struct) erhält:
struct EditView: View {
@Environment(\.dismiss) var dismiss
var location: Location
@State private var name: String
@State private var description: String
var body: some View {
NavigationStack {
Form {
Section {
TextField("Ortsname", text: $name)
TextField("Beschreibung", text: $description)
}
}
.navigationTitle("Ortsdetails")
.toolbar {
Button("Speichern") {
dismiss()
}
}
}
}
}
Um die Location zu übergeben, mache ich einen zusätzlichen Initialisierer:
init(location: Location) {
self.location = location
_name = State(initialValue: location.name)
_description = State(initialValue: location.description)
}
Um Daten an den aufrufenden Code zurückzugeben, übergebe ich eine Methode onSave. Zuerst erstelle ich eine zusätzliche Variable in meiner View-Struktur:
var onSave: (Location) -> Void
und erweitere dann den Initialisierer so:
init(location: Location, onSave: @escaping (Location) -> Void) {
self.location = location
self.onSave = onSave
_name = State(initialValue: location.name)
_description = State(initialValue: location.description)
}
Dieser @escaping-Teil ist wichtig und bedeutet, dass die Funktion für die spätere Verwendung gespeichert wird, anstatt sofort aufgerufen zu werden, und es ist hier notwendig, weil die onSave-Funktion erst aufgerufen wird, wenn der Benutzer auf Speichern drückt.
Sheets & NavigationStacks
Um eine View als Sheet zu öffnen:
struct ContentView: View {
@State private var showingAddExpense = false
var body: some View {
NavigationStack {
VStack {
// Hier kommt Code
}
.navigationTitle("iExpense")
.toolbar {
Button("Ausgabe hinzufügen", systemImage: "plus") {
showingAddExpense = true
}
}
.sheet(isPresented: $showingAddExpense) {
AddView(expenses: expenses)
}
}
}
}
Networking
So sendest du etwas an einen HTTPS-Endpunkt:
func placeOrder() async {
guard let encoded = try? JSONEncoder().encode(order) else {
print("Bestellung konnte nicht kodiert werden")
return
}
let url = URL(string: "https://reqres.in/api/cupcakes")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
do {
let (data, other) = try await URLSession.shared.upload(for: request, from: encoded)
let decodedOrder = try JSONDecoder().decode(Order.self, from: data)
confirmationMessage = "Deine Bestellung über \(decodedOrder.quantity)x \(Order.types[decodedOrder.type].lowercased()) Cupcakes ist auf dem Weg!"
showingConfirmation = true
} catch {
print("Checkout fehlgeschlagen: \(error.localizedDescription)")
}
}
Daten speichern
Ich kenne 3 Möglichkeiten, Daten in Swift/UI zu speichern:
- UserDefaults: Am besten geeignet, um kleine Datenmengen zu speichern. Zum Beispiel App-Einstellungen.
- Schreiben in das Dokumentenverzeichnis
- SwiftData
Schreiben & Lesen in UserDefaults
Wir brauchen ein paar Dinge:
- Unsere Daten müssen
Codablesein, damit wir späterJSONEncodererstellen können - UserDefaults, um unsere Daten zu speichern und zu laden
- Einen benutzerdefinierten Initialisierer für die Datenklasse, damit sie automatisch geladen wird
- Ein
didSetfür die Daten, damit sie immer automatisch gespeichert werden, wenn Daten hinzugefügt oder geändert werden.
Die Daten Codable zu machen, ist meistens nicht allzu schwer: Solange die Komponenten Codable sind, ist es auch die gesamte Klasse.
So kann das Speichern der Daten in einem didSet aussehen:
var items = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
Und so könnte ein Initialisierer aussehen, der die Daten lädt:
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Items") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
items = decodedItems
return
}
}
items = []
}
Schreiben & Lesen im Dokumentenverzeichnis
So schreiben wir:
let data = Data("Testnachricht".utf8)
let url = URL.documentsDirectory.appending(path: "message.txt")
do {
try data.write(to: url, options: [.atomic, .completeFileProtection])
} catch {
print(error.localizedDescription)
}
..und so lesen wir:
let url = URL.documentsDirectory.appending(path: "message.txt")
do {
let input = try String(contentsOf: url)
print(input)
} catch {
print(error.localizedDescription)
}