iTranslated by AI
Handling OS version checks effectively in SwiftUI
As OS updates occur, the number of available modifiers continues to grow.
While it becomes more convenient, if you wanted to add a "Liquid Glass" modifier (recently introduced) to an existing app, for example, I feel like it would end up looking like this:
import SwiftUI
if #available(iOS 26.0, *) {
Button {
// do some action
} label: {
Text("Do some action")
}
.glassEffect()
} else {
Button {
// do some action
} label: {
Text("Do some action")
}
}
I have a feeling it would end up looking like that (right? right??).
However, this approach results in two Views being created in various places, making it redundant and difficult to read. Therefore, I think the following technique is commonly used:
import SwiftUI
extension View {
@ViewBuilder
func glassEffectIfOver26() -> some View {
if #available(iOS 26.0, *) {
self
.glassEffect()
} else {
self
}
}
}
Button {
// do some action
} label: {
Text("Do some action")
}
.glassEffectIfOver26()
While this seems clean, there is a risk that similar extensions will multiply endlessly.
Fundamentally, what we ideally want is to write it like this:
Button {
// do some action
} label: {
Text("Do some action")
}
if #available(iOS 26.0, *) {
.glassEffect()
}
As of now (late December 2025), due to SwiftUI's syntax, you cannot directly write if #available in the middle of a View modifier chain.
Therefore, I came up with the following extension.
extension View {
/// Conditionally applies modifiers based on the current OS version.
///
/// SwiftUI does not allow writing `#available` checks directly inside a
/// modifier chain. `osCondition(_:)` acts as an escape hatch that enables
/// OS-specific branching while keeping the modifier-style syntax.
///
/// This is especially useful when adopting new system-provided modifiers
/// (such as those introduced in newer iOS releases) without duplicating views
/// or creating many one-off extension methods.
///
/// Example:
/// ```swift
/// Text("Hello")
/// .osCondition {
/// if #available(iOS 26.0, *) {
/// $0.glassEffect()
/// } else {
/// $0
/// }
/// }
/// ```
///
/// - Parameter modifier: A closure that receives the current view and returns
/// a modified view. The closure is evaluated unconditionally, so OS version
/// checks should be performed inside using `#available`.
@ViewBuilder public func osCondition<Content: View>(
@ViewBuilder modifier: (Self) -> Content
) -> some View {
modifier(self)
}
}
This is a simple extension that merely executes the closure passed as an argument.
However, at the call site, it can be written as follows:
Button {
// do some action
} label: {
Text("Do some action")
}
.osCondition { view in
if #available(iOS 26.0, *) {
view
.glassEffect()
} else {
view
}
}
Wait, isn't this good enough?
With that in mind, I looked around and found the following library:
I think the concept is similar.
While using a library is a good option if you want a more feature-rich and systematic solution, personally, I feel that this approach is well-balanced for when you just want to perform a bit of OS-specific branching.
If anyone knows of a better way, please let me know!
Discussion
私もほぼ同じ手法で、
YusukeHosonuma/SwiftUI-Commonのextend()をよく使います。ref: https://github.com/YusukeHosonuma/SwiftUI-Common/blob/6d36ee0233f23ca327134d1fd11a03455ee0530e/Sources/SwiftUICommon/Extension/View%2B.swift#L35-L37
おー、なるほど。こういうライブラリがあるんだ。あざます!