Zum Inhalt springen

HandySwiftUI – Neue Typen: Unverzichtbare Views und Typen für die SwiftUI-Entwicklung

Von plattformspezifischen Werten ohne #if-Abfragen über ausgefeilte Auswahl-Controls bis hin zu Async-State-Management – entdecke die unverzichtbaren SwiftUI-Typen, die mir geholfen haben, Apps schneller auszuliefern. Diese praxiserprobten Views und Typen füllen häufige Lücken in der SwiftUI-Entwicklung.

HandySwiftUI – Neue Typen: Unverzichtbare Views und Typen für die SwiftUI-Entwicklung

Nach 4 Jahren Feinschliff an diesen APIs in meinen eigenen Apps freue ich mich, das erste getaggte Release von HandySwiftUI zu teilen. Dieses Paket enthält verschiedene Hilfsfunktionen und Convenience-APIs, die mir enorm dabei geholfen haben, allein im letzten Jahr 10 Apps auszuliefern. Es bietet Komfortfunktionen für die SwiftUI-Entwicklung, ähnlich wie mein HandySwift-Paket für Foundation.

In diesem Artikel stelle ich eine Auswahl der neuen Typen vor, die sich in meiner täglichen Arbeit an Apps wie TranslateKit, FreemiumKit und CrossCraft am meisten bewährt haben. HandySwiftUI enthält zwar noch viele weitere Hilfsfunktionen, aber diese Typen haben sich in der Praxis immer wieder als besonders wertvoll erwiesen und könnten auch für deine SwiftUI-Projekte nützlich sein.

Plattformspezifische Werte

HandySwiftUI bietet eine elegante Möglichkeit, plattformspezifische Werte zu handhaben:

struct AdaptiveView: View {
    enum TextStyle {
        case compact, regular, expanded
    }

    var body: some View {
        VStack {
            // Unterschiedliche Zahlenwerte pro Plattform
            Text("Welcome")
                .padding(Platform.value(default: 20.0, phone: 12.0))

            // Unterschiedliche Farben pro Plattform
            Circle()
                .fill(Platform.value(default: .blue, mac: .indigo, pad: .purple, vision: .cyan))
        }
    }
}

Last30days

Ein ähnliches Erscheinungsbild über Plattformen hinweg für einen Titel in FreemiumKit via .font(Platform.value(default: .title2, phone: .headline)).

Platform.value funktioniert mit jedem Typ – von einfachen Zahlen über Farben und Fonts bis hin zu eigenen Typen. Du gibst einfach einen Standardwert an und überschreibst bei Bedarf bestimmte Plattformen. Das kann enorm nützlich sein, vor allem weil es sogar einen eigenen Case für iPad namens pad gibt – so kannst du Phones und Tablets separat ansprechen.

Das ist mit Abstand mein meistgenutzter HandySwiftUI-Helfer und spart mir massenhaft Boilerplate durch #if-Abfragen. Einfach, aber extrem wirkungsvoll!

Lesbare Preview-Erkennung

Stelle Fake-Daten bereit und simuliere Ladezustände während der Entwicklung:

Task {
   loadState = .inProgress

   if Xcode.isRunningForPreviews {
       // Netzwerkverzögerung in Previews simulieren
       try await Task.sleep(for: .seconds(1))
       self.data = Data()
       loadState = .successful
   } else {
       do {
           self.data = try await loadFromAPI()
           loadState = .successful
       } catch {
           loadState = .failed(error: error.localizedDescription)
       }
   }
}

Xcode.isRunningForPreviews ermöglicht es dir, echte Netzwerk-Requests zu umgehen und stattdessen sofortige oder verzögerte Fake-Antworten nur in SwiftUI-Previews bereitzustellen – perfekt fürs Prototyping und die UI-Entwicklung. Es ist auch nützlich, um begrenzte Ressourcen während der Entwicklung zu schonen, etwa API-Rate-Limits, Analytics-Events, die Statistiken verzerren könnten, oder Services, die pro Request abrechnen – packe diese einfach in eine if !Xcode.isRunningForPreviews-Abfrage.

Effizientes Laden von Bildern

CachedAsyncImage bietet effizientes Laden von Bildern mit eingebautem Caching:

struct ProductView: View {
    let product: Product

    var body: some View {
        VStack {
            CachedAsyncImage(url: product.imageURL)
                .frame(width: 200, height: 200)
                .clipShape(RoundedRectangle(cornerRadius: 10))

            Text(product.name)
                .font(.headline)
        }
    }
}

Beachte, dass .resizable() und .aspectRatio(contentMode: .fill) bereits auf die Image-View im Inneren angewandt werden.

Erweiterte Auswahl-Controls

Mehrere ausgefeilte Picker-Typen für verschiedene Anwendungsfälle:

struct SettingsView: View {
    @State private var selectedMood: Mood?
    @State private var selectedColors: Set<Color> = []
    @State private var selectedEmoji: Emoji?

    var body: some View {
        Form {
            // Vertikaler Option-Picker mit Icons
            VPicker("Select Mood", selection: $selectedMood)

            // Horizontaler Picker mit individuellem Styling
            HPicker("Rate your experience", selection: $selectedMood)

            // Mehrfachauswahl mit plattformadaptiver UI
            MultiSelector(
                label: { Text("Colors") },
                optionsTitle: "Select Colors",
                options: [.red, .blue, .green],
                selected: $selectedColors,
                optionToString: \.description
            )

            // Durchsuchbarer Grid-Picker für Emoji- oder SF-Symbol-Auswahl
            SearchableGridPicker(
                title: "Choose Emoji",
                options: Emoji.allCases,
                selection: $selectedEmoji
            )
        }
    }
}

Settings view

HandySwiftUI enthält Emoji- und SFSymbol-Enums mit gängigen Emojis und Symbolen. Du kannst auch eigene Enums erstellen, indem du SearchableOption konformierst und searchTerms für jeden Case angibst, um die Suchfunktion zu unterstützen.

Async-State-Management

Verfolge asynchrone Operationen mit typsicherem State-Handling über ProgressState:

struct DocumentView: View {
    @State private var loadState: ProgressState<String> = .notStarted

    var body: some View {
        Group {
            switch loadState {
            case .notStarted:
                AsyncButton("Load Document") {
                    loadState = .inProgress
                    try await loadDocument()
                    loadState = .successful
                } catchError: { error in
                    loadState = .failed(error: error.localizedDescription)
                }

            case .inProgress:
                ProgressView("Loading document...")

            case .failed(let errorMessage):
                VStack {
                    Text("Failed to load document:")
                        .foregroundStyle(.secondary)
                    Text(errorMessage)
                        .foregroundStyle(.red)

                  AsyncButton("Try Again") {
                      loadState = .inProgress
                      try await loadDocument()
                      loadState = .successful
                  } catchError: { error in
                      loadState = .failed(error: error.localizedDescription)
                  }
                }

            case .successful:
                VStack {
                    DocumentContent()
                }
            }
        }
    }
}

Das Beispiel zeigt, wie alle Zustände typsicher behandelt werden:

  • .notStarted zeigt den initialen Lade-Button

  • .inProgress zeigt einen Ladeindikator

  • .failed zeigt den Fehler mit einer Wiederholungsoption

  • .successful zeigt den geladenen Inhalt

NSOpenPanel in SwiftUI nutzen

Eine Brücke von nativem macOS-Dateizugriff zu SwiftUI, besonders nützlich für den Umgang mit sicherheitsbezogenen Ressourcen:

struct SecureFileLoader {
    @State private var apiKey = ""

    func loadKeyFile(at fileURL: URL) async {
        #if os(macOS)
        // Auf macOS brauchen wir die Zustimmung des Nutzers für den Dateizugriff
        let panel = OpenPanel(
            filesWithMessage: "Provide access to read key file",
            buttonTitle: "Allow Access",
            contentType: .data,
            initialDirectoryUrl: fileURL
        )
        guard let url = await panel.showAndAwaitSingleSelection() else { return }
        #else
        let url = fileURL
        #endif

        guard url.startAccessingSecurityScopedResource() else { return }
        defer { url.stopAccessingSecurityScopedResource() }

        do {
            apiKey = try String(contentsOf: url)
        } catch {
            print("Failed to load file: \(error.localizedDescription)")
        }
    }
}

Open panel

Das Beispiel direkt aus FreemiumKit zeigt, wie OpenPanel den Umgang mit sicherheitsbezogenem Dateizugriff für gezogene Elemente auf macOS vereinfacht und dabei die plattformübergreifende Kompatibilität beibehält.

Vertikale Tab-Navigation

Eine Alternative zu SwiftUIs TabView, die eine Sidebar-Navigation implementiert, wie sie häufig in macOS- und iPadOS-Apps zu sehen ist:

struct MainView: View {
    enum Tab: String, CaseIterable, Identifiable, CustomLabelConvertible {
        case documents, recents, settings

        var id: Self { self }
        var description: String {
            rawValue.capitalized
        }
        var symbolName: String {
            switch self {
            case .documents: "folder"
            case .recents: "clock"
            case .settings: "gear"
            }
        }
    }

    @State private var selectedTab: Tab = .documents

    var body: some View {
        SideTabView(
            selection: $selectedTab,
            bottomAlignedTabs: 1  // Platziert Einstellungen unten
        ) { tab in
            switch tab {
            case .documents:
                DocumentList()
            case .recents:
                RecentsList()
            case .settings:
                SettingsView()
            }
        }
    }
}

Side tab view

SideTabView bietet eine vertikale Sidebar mit Icons und Labels, optimiert für größere Bildschirme mit Unterstützung für am unteren Rand angeordnete Tabs. Die View übernimmt automatisch das plattformspezifische Styling und Hover-Effekte.

Leg noch heute los

Ich hoffe, du findest diese Typen genauso nützlich in deinen Projekten wie ich in meinen. Wenn du Ideen für Verbesserungen oder zusätzliche Typen hast, die der SwiftUI-Community zugutekommen könnten, trage gerne auf GitHub bei:

github.comFlineDev / HandySwiftUIHandy SwiftUI features that didn’t make it into SwiftUI (yet)

Dies ist der erste von vier Artikeln, die die Features von HandySwiftUI erkunden. Bleib dran für die kommenden Beiträge über View Modifier, Extensions und Styles!

Hat dir dieser Beitrag gefallen? Folge mir auf Bluesky und Mastodon für mehr Swift-Tipps und Indie-Dev-Updates.