Prüfen auf Verfügbarkeit von iOS 15 im SwiftUI-View-Body

von @ralfebert · aktualisiert am 2. November 2021
Xcode 13 & iOS 15
Fortgeschrittene iOS-Entwickler*innen
Deutsch

Die Verwendung von SwiftUI-Modifiern, die neu in iOS 15 sind, werden in Projekten, die noch kompatibel sein müssen mit älteren iOS-Versionen, mit diesem Fehler quittiert:

... is only available in iOS 15.0 or newer

Dieser Artikel zeigt, wie die Verfügbarkeit einer iOS-Version in einem SwiftUI-View-Body geprüft werden kann. So können neue View-Modifier verwendet werden, die in den letzten iOS-Versionen hinzugefügt wurden.

Beispiel

In iOS 15 gibt es zum Beispiel einen neuen Modifier .badge(), mit einem TabItem ein Badge hinzugefügt werden kann:

TabView {
    Color.yellow
        .tabItem {
            Label("Example", systemImage: "hand.raised")
        }
        .badge(5)
}

Prüfen auf API-Verfügbarkeit

Theoretisch könntest du diese Laufzeitprüfung in Projekten mit dem Deployment Target iOS 14 verwenden:

if #available(iOS 15, *) {
    // ...
}

↗ Swift: Checking API Availability

Leider kann eine if-Anweisung nicht in der Mitte einer Reihe von View-Modifiern verwendet werden.

Gab es in Swift 5.5 nicht etwas Neues in dieser Hinsicht? Ja und nein. Man kann jetzt #if innerhalb von Modifier-Aufrufen verwenden. Dabei handelt es sich jedoch um eine Prüfung zur Kompilierzeit. Beispielsweise könnte man damit einen plattformspezifischen Modifier in einer cross-platform App bedingt verwenden:

TextField("Field", text: $name)
#if os(OSX)
    .prefersDefaultFocus(in: namespace)
#endif

↗ #if Conditional Compilation Block
↗ #if for postfix member expressions

Prüfung der API-Verfügbarkeit mit einem eigenen View Modifier

Eine Möglichkeit, eine solche Prüfung zu implementieren, ist einen eigenen View Modifier zu erstellen, der mit der alten iOS-Version kompatibel ist. Es folgt ein Beispiel für einen .withBadge() Modifier, der unter iOS 14 (ohne Funktion) und iOS 15 (Anzeige des Badge) verwendet werden kann:

struct WithBadgeModifier: ViewModifier {
    var count: Int

    func body(content: Content) -> some View {
        if #available(iOS 15.0, *) {
            content.badge(count)
        } else {
            content
        }
    }
}

extension View {
    func withBadge(count: Int) -> some View {
        modifier(WithBadgeModifier(count: count))
    }
}

↗ Building custom View modifiers in SwiftUI

Verbesserte Struktur

Hier ist eine hilfreiche Idee von Dave DeLong: Den Modifier nicht zum View selbst hinzufügen, sondern zu einem Wrapper-View:

struct Backport<Content> {
    let content: Content
}

extension View {
    var backport: Backport<Self> { Backport(content: self) }
}

extension Backport where Content: View {
    @ViewBuilder func badge(_ count: Int) -> some View {
        if #available(iOS 15, *) {
            content.badge(count)
        } else {
            content
        }
    }
}

Dieser Modifier kann dann wie folgt verwendet werden:

TabView {
    Color.yellow
        .tabItem {
            Label("Example", systemImage: "hand.raised")
        }
        .backport.badge(5)
}

Das hat den Vorteil, dass der gleiche Name für den Modifier verwendet werden kann und alle Stellen, an denen ein solcher Modifier verwendet wurde, leicht auffindbar sind. Das ist praktisch, wenn später die alte Version nicht mehr unterstützt werden soll und der Code entfernt werden kann.

Siehe auch

↗ Dave DeLong: Simplifying Backwards Compatibility in Swift
↗ How to use iOS15-specific modifiers in SwiftUI on iOS 14 and earlier?
↗ SwiftUI: using view modifiers between different iOS versions