[Idea] Availability annotations on closure parameters

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?

8 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.

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.