Every OS-bridging wrapper you write, code that picks a new API on the new OS and a fallback on
the old one, becomes dead code the day you raise the deployment target. Annotate it with
@available(deprecated:obsoleted:) at creation time, and the compiler finds it for you later.
The wrapper that will die
A typical bridge: iOS 26 toolbars show symbols, iOS 18 toolbars show text. A LabelStyle
switches on availability.
struct ToolbarLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
if #available(iOS 26, *) {
Label(configuration).labelStyle(.iconOnly)
} else {
Label(configuration).labelStyle(.titleOnly)
}
}
}
extension LabelStyle where Self == ToolbarLabelStyle {
static var toolbar: Self { .init() }
}
Once the app targets iOS 26 the whole type is pointless, and there may be dozens of such helpers scattered around.
Annotate the expiry when you write it
Add the availability annotation to the convenience entry point. When the deployment target
reaches the deprecated version, every use site becomes a warning with your message; at the
obsoleted version it becomes a compile error.
extension LabelStyle where Self == ToolbarLabelStyle {
@available(iOS, deprecated: 26, obsoleted: 27, message: "You don't need .toolbar anymore")
static var toolbar: Self { .init() }
}
Deprecate first, then obsolete
Setting obsoleted: 26 directly produces errors on the target bump, which can block unrelated
work. Prefer the two-step ramp: deprecate at the version that makes the wrapper redundant, and
obsolete one version later. Then treat the warnings as the cleanup queue rather than letting
them accumulate.