bjhomer
(BJ Homer)
1
The Swift compiler is aware of API availability within an if #available() block:
// Compiling with a deployment target of iOS 16:
if #available(iOS 17, *) {
someiOS17API()
}
else {
olderAPI()
}
The compiler allows us to call the iOS 17 API within the if #available block, because it knows that block will only run on iOS 17 or later. However, we cannot currently do the same thing with closure arguments. For example, here's how I might use it with SwiftUI:
extension View {
/// Modifies a view only if running on iOS 17 or later
func iOS17(_ block: @available(iOS 17, *) (Self)->some View) -> some View {
if #available(iOS 17, *) {
return block(self)
} else {
return self
}
}
}
// Usage:
Text("Hello")
.iOS17 { view in
// The compiler would know that this block only runs on
// iOS 17+, so it allows us to use this modifier here
view.someiOS17Modifier()
}
Because of the chained nature of SwiftUI APIs, it's not easy to use an if #available block here directly. Being able to annotate the closure parameters would make for a significantly nicer API.
What do you think?
7 Likes
The typical workaround I use is an extension method like
extension View {
func apply<T: View>(@ViewBuilder body: (Self) -> T) -> T {
body(self)
}
}
// at the use site
view.apply { view in
if #available(iOS 17, *) {
view.someNewModifier()
} else {
view // or fallback
}
}
essentially moving the if condition out a level.
Jon889
(Jonathan Bailey)
3
The problem with the workaround from @bbrk24 is that it’s easy to forget the else and then on versions lower that the availability check the whole view won’t appear.
I think the suggestion for availability checks on closures is really smooth and makes sense.