SwiftUI is fantastic when you need to write code for different platforms. The code can be shared. But how do I distinguish platform-specific code parts?
In my employer's current project, SwiftUI is sometimes pushed to the limits of what is still "elegantly feasible". You realize that it is still a work in progress, where Apple has a lot of bugs to fix or missing features to add.
Our project is being developed for iOS (both iPads and iPhones) as well as macOS. Swift in general, and SwiftUI in particular makes it really easy for us to have a common code base for everything. But every now and then, platform-specific code needs to be written. The distinction for this is made by so-called preprocessor macros. It looks something like this:
#if os(iOS)
// Place your iOS code here...
#elseif os(macOS)
// Place your macOS code here...
#endif
But as the code base grows, so does the number of these not-so-pretty code switches. So I had the idea to hide the whole thing in a ViewModifier. The preprocessor switches do not disappear completely, but they can be reduced quite nicely.
My first attempt looked like this:
I posted this on Twitter, and it immediately triggered a constructive thread. There was a lot of agreement and also suggestions for improvement. đ¤
The result finally looks like this:
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
}
}
Of course, you can find the whole thing in a gist on GitHub. What do you think? Useful or syntactic shugar?