Skip to content

Handling different iOS versions in a View body

When using a SwiftUI modifier that was added in a new iOS version, you'll get an error in projects that are configured to be compatible with older iOS versions:

... is only available in iOS 26.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.

Checking for iOS version / API Availability

In regular Swift code, you can use this to check for an iOS version at runtime:

swift
if #available(iOS 26, *) {
    // ...
}

if #unavailable(iOS 26) {
    // ...
}

WARNING

if #available(iOS 26.0, *) { ... } will fail to build with a non-beta Xcode while iOS 26 is in beta / until the final Xcode version for iOS 26 is released. Make sure to keep those changes in a separate branch if you need to do maintenance releases in parallel.

↗ Swift: Checking API Availability

if #available in modifier chains

You cannot use such an if #available-statement directly in the middle of a chain of modifiers. As a solution, you can use this View extension:

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Articles/Handling-different-iOS-versions-in-a-View-body

import SwiftUI

public extension View {
    func modify(@ViewBuilder transform: (Self) -> some View) -> some View {
        transform(self)
    }
}

Then use it like this:

swift
List {
    Section {
    }
    .modify { content in
        if #available(iOS 26, *) {
            content
                .sectionIndexLabel(Text("..."))
        } else {
            content
        }
    }
}

WARNING

  • Do NOT forget to return content in the else block or the View will be missing in the old iOS version
  • Do NOT use this for other conditions that can change during runtime (static things like iOS version, "iPhone or iPad" are fine though). Otherwise the View hierarchy will change and state will be thrown away / transitions will break.

Tweaking values

If you only want to tweak values like paddings between different iOS versions, a generic function like this is handy:

swift
public func value<T>(_ ios26: T, ios18: T) -> T {
    if #available(iOS 26, *) {
        ios26
    } else {
        ios18
    }
}

Then you can go:

.padding(.trailing, value(0, ios18: 16))

Custom View extensions, .backport

If you need the same modifier in many places, a reusable view extension comes in handy:

swift
extension View {
    @ViewBuilder func compatibleSectionIndexLabel(_ label: Text) -> some View {
        if #available(iOS 26, *) {
            self.sectionIndexLabel(label)
        } else {
            self
        }
    }
}

Here is a helpful idea about that from Dave DeLong: Don't add the modifier to the View itself but to a wrapper View called backport:

↗ Dave DeLong: Simplifying Backwards Compatibility in Swift

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Articles/Handling-different-iOS-versions-in-a-View-body

import SwiftUI

struct Backport<Content> {
    let content: Content
}

extension View {
    var backport: Backport<Self> { Backport(content: self) }
}

extension Backport where Content: View {
    @ViewBuilder func sectionIndexLabel(_ label: Text) -> some View {
        if #available(iOS 26, *) {
            content.sectionIndexLabel(label)
        } else {
            content
        }
    }
}

Then use it like this:

swift
List {
    Section {
    }
    .backport.sectionIndexLabel(Text("..."))
}

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.

Common implementations of such modifiers can be found here as package (thanks @jordanmorgan)

↗ SwiftUI-Backports