SwiftUI Spickzettel
Im Juni 2025 habe ich angefangen, 100 Days of SwiftUI 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 zu navigierenden 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 essenzielles Swift in einer Stunde.
Im folgenden Kapitel habe ich nur die Teile hinzugefügt, die ich mindestens einmal überprüfen musste.
struct & berechnete Eigenschaften
struct Employee {
let name: String
var vacationAllocated = 14
var vacationTaken = 0
var vacationRemaining: Int {
vacationAllocated - vacationTaken
}
}
Optionals
- Optionals ermöglichen 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 enthalten. - Alles, was nicht optional ist, hat definitiv einen Wert, selbst wenn es 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 Nutzung zurückgegeben, andernfalls ist nil darin.
- 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-koaleszierende Operator, ??, 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 & Sortierung
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 an Ort und Stelle zu sortieren:
var names = ["Jemima", "Peter", "David", "Kelly", "Isabella"]
names.sort()
Wenn du komplexere Strukturen hast, musst du den Vergleich mitgeben:
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
ComparableKonformitä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
}
Eine switch Anweisung 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("Zeile \($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 es 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 = ["Apfel", "Banane", "Kirsche"]
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 Auswahlmöglichkeiten 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 Modifikator modifiziert werden:
Picker("Trinkgeld Prozentsatz", 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 dendisplayedComponentsParameter, 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. Beispielcode siehe dieses Repo.
Hinweis: Vor iOS 26 rutschten sie 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 Teilen mit unterschiedlichem Styling zu bilden:
Text(page.title)
.font(.headline)
+ Text(": ") +
Text("Seitenbeschreibung hier")
.italic()
Und du erhältst einen Text mit kombiniertem Styling:

Listen
Erstellen von scrollbaren Datentabellen mit List, 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, klickst du, wo auch immer du auf das Listenelement klickst, ALLE Schaltflächen nacheinander!
Um das zu beheben und das gewünschte Verhalten zu erhalten, verwende .buttonStyle(.plain)
Dasselbe 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
Lesen von Dateien aus unserem App-Bundle, 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.
- Erstellen von Animationen implizit mit dem
animation()Modifikator. - Anpassen von Animationen mit Verzögerungen und Wiederholungen und die Wahl zwischen Ease-in-Ease-out und Federanimationen.
- Anhängen des animation() Modifikators an Bindungen, sodass wir Änderungen direkt von UI-Steuerelementen animieren können.
- Verwenden von
withAnimation(), um explizite Animationen zu erstellen. - Anhängen mehrerer
animation()Modifikatoren an eine einzelne View, um den Animationsstapel zu steuern.
Laden von Daten
Wenn es synchron ist:
View...
.onAppear(loadIt)
?? Wie wird es gemacht, wenn loadIt asynchron ist??
Werte an Views übergeben & zurückgeben
Wie gesehen in Auswählen und Bearbeiten von Kartenanmerkungen
Stell dir vor, ich habe eine View, die als Sheet geöffnet wird und eine Location (eine selbst definierte 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 später gespeichert wird, anstatt sofort aufgerufen zu werden, und es ist hier notwendig, weil die onSave Funktion nur 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 {
// Irgendein Code hier
}
.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 codiert 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 für \(decodedOrder.quantity)x \(Order.types[decodedOrder.type].lowercased()) Cupcakes ist unterwegs!"
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, sodass 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 in das 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:
“`swift let url = URL.documentsDirectory.appending(path: “message.txt”)
do { let input = try String(contentsOf: url) print(input) } catch { print(error.localized