SwiftUI: Platform ViewModifier

SwiftUI ist eine tolle Sache, wenn Code für verschiedene Plattformen geschrieben werden soll. Dieser lässt sich sharen. Doch wie unterscheide ich plattformspezifische Code-Teile?

Im aktuellen Projekt meines Arbeitgebers stoßen wir mit SwiftUI hier und da schonmal an die Grenzen des noch "elegant Machbaren". Man merkt, dass es Stand heute einfach noch eine Baustelle ist, bei der Apple sehr viele Fehler zu fixen oder fehlender Features nachzureichen hat.

Unser Projekt wird für iOS (sowohl iPads als auch iPhones) als auch macOS entwickelt. Swift allgemein, und SwiftUI im Besonderen macht es uns wirklich einfach eine gemeinsame Code-Basis für Alles zu haben. Doch hin und wieder muss auch plattformspezifischer Code geschrieben werden. Die Unterscheidung hierfür wird durch so genannte Präprozessor-Macros vorgenommen. Das schaut dann ungefähr so aus:

#if os(iOS)
    // Place your iOS code here...
#elseif os(macOS)
    // Place your macOS code here...
#endif

Doch mit wachsender Code-Basis steigt auch die Anzahl dieser nicht sehr ansehnlichen Code-Weichen. Da kam mir die Idee, das Ganze irgendwie in einem ViewModifier zu verstecken. Die Präprozessor-Weichen verschwinden damit zwar nicht komplett, aber sie lassen sich doch ganz schön reduzieren.

Mein erster Versuch sah dann so aus:

Ich veröffentlichte das auf Twitter, und sogleich entsponn sich daraus ein konstrukiver Thread. Es gab viel Zustimmung und auch Verbesserungsvorschläge. 🤓

Das Resultat schaut schlussendlich so aus:

import SwiftUI

public struct Platform: OptionSet {
    public var rawValue: UInt8

    public static let iOS: Platform     = Platform(rawValue: 1 << 0)
    public static let macOS: Platform   = Platform(rawValue: 1 << 1)
    public static let tvOS: Platform    = Platform(rawValue: 1 << 2)
    public static let watchOS: Platform = Platform(rawValue: 1 << 3)
    public static let all: Platform     = [.iOS, .macOS, .tvOS, .watchOS]

#if os(iOS)
    public static let current: Platform = .iOS
#elseif os(macOS)
    public static let current: Platform = .macOS
#elseif os(tvOS)
    public static let current: Platform = .tvOS
#elseif os(watchOS)
    public static let current: Platform = .watchOS
#endif

    public init(rawValue: UInt8) {
        self.rawValue = rawValue
    }
}

public extension View {
    @ViewBuilder
    func visible(on platforms: Platform) -> some View {
        modifier(PlatformVisibility(platforms: platforms))
    }
}

private struct PlatformVisibility: ViewModifier {
    var platforms: Platform = .current

    func body(content: Content) -> some View {
        platforms.contains(.current) ? content : nil
    }
}

Das Ganze findest du natürlich in ein Gist auf GitHub.

Was denkt ihr? Nützlich oder syntactic shugar?


© Woodbytes