Checking for availability of iOS 15 in a SwiftUI view body
When you use a SwiftUI modifier that was added in iOS 15, you'll get an error in projects that are configured to be compatible with older iOS versions:
... is only available in iOS 15.0 or newer
This article shows how to check for the availability of an iOS version in a SwiftUI View body to make use of new View modifiers that have been added in recent iOS releases.
Example
For example, there is a new modifier ↗ .badge() in iOS 15 that allows to set a badge to a tabItem:
TabView {
Color.yellow
.tabItem {
Label("Example", systemImage: "hand.raised")
}
.badge(5)
}
Checking for API Availability
Theoretically, you could use this runtime check in projects with deployment target iOS 14:
if #available(iOS 15, *) {
// ...
}
↗ Swift: Checking API Availability
Unfortunately, you cannot use an if-statement in the middle of a chain of modifiers.
Now, wasn't there something new in Swift 5.5 about this? Yes and no. You can now use #if in the middle of a modifier chain. But that's a compile time check, so you can use it to conditionally use a platform-specific modifier in a cross-platform app:
TextField("Field", text: $name)
#if os(OSX)
.prefersDefaultFocus(in: namespace)
#endif
↗ #if Conditional Compilation Block
↗ #if for postfix member expressions
Checking for API Availability with a custom View modifier
One way to implement such a check is to create your own View Modifier that is compatible with the old iOS version. Here is an example for a .withBadge()` modifier that can be used on iOS 14 (where it does nothing) and iOS 15 (where it shows the badge):
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
Improved structure
Here is a helpful idea from Dave DeLong: Don't add the modifier to the View itself but to a 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
}
}
}
This modifier can than be used like this:
TabView {
Color.yellow
.tabItem {
Label("Example", systemImage: "hand.raised")
}
.backport.badge(5)
}
This has the advantage that you can use the same name for the modifier and can easily find all those places where such a modifier was used. This will be handy if later the old version is not to be supported anymore and the code can be removed.
See also
↗ 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