author: Wesley Matlock read_time: 4 —
WWDC drops a new Swift version and fresh APIs? Cue the dev turbulence. One minute you’re shipping iOS 17 support like a seasoned captain. The next, you’re staring at iOS 18 SDK changes, Swift 6 macros, and the question: “Can I actually ship this without grounding older devices?”
Let’s walk through how @available, #if swift(…), and Swift macros play together — and why this matters a lotmore now that Swift macros and new OS previews are the new normal.
And yes, we’re building an airline-themed demo app to show how to actually pull this off.
🧠 What Is @available Really Doing?
swift
@available(iOS 18, *)
func showUpgradedCabinExperience() {
print("Welcome to iOS 18 First Class")
}
swift
That’s the declaration. But here’s the gotcha: Just using @available doesn’t stop a crash. You still need to wrap the call site:
swift
if #available(iOS 18, *) {
showUpgradedCabinExperience()
}
swift
If you call that function without the check on an iOS 17 device? Fasten your seatbelt.
🧠 Rule 1: Match @available with #available(…) unless you know the call is already guarded.
🧬 Bridging Swift Versions: #if swift(…)
Say hello to Swift 6 macros:
swift
#if swift(>=6.0)
print("Swift 6 macros, let’s fly!")
#else
print("Still cruising with Swift 5.9")
#endif
swift
This is critical if you want to:
- Support Swift 5.9/Swift 6 simultaneously
- Use macros without breaking older toolchains
- Keep CI green when mixing SDK targets
💡 Pro Tip: Combine this with @available to double-wrap Swift 6 features, like macros or new syntax, that require both compiler and OS-level support.
🛠️ Macro Attributes in the Wild
Even if you’re not building custom macros (yet), you’ll see stuff like this all over Swift 6:
swift
@freestanding(expression)
macro trackFlight() = #externalMacro(module: "FlightAnalyticsKit", type: "TrackFlightMacro")
swift
And in the wild:
swift
@Observable class BoardingPassModel { ... }
swift
Those @Observable/@AddAsync macros under the hood? Yep. Those are attached macros. And they only work with Swift 6.
⚠️ Gotchas
- You must enable macro expansion with the right build flag: -enable-experimental-feature Macros
- Macros won’t even get off the runway in older Swift toolchains — fence them off properly or expect build turbulence.
- SPM targets importing macros? Check compatibility, or enjoy cryptic build errors
✈️ The Demo App: FlightDeck Cabin Upgrade Mode
Our demo app simulates a Frontier flight view. On iOS 18, we unlock a new feature: Cabin Upgrade Mode — premium options like priority boarding, more legroom, and an upgraded UI.
swift
@available(iOS 18, *)
struct CabinUpgradeView: View {
var body: some View {
Text("Now Boarding: FlightDeck Pro Cabin Upgrade ✈️")
}
}
swift
And in the main view:
swift
if #available(iOS 18, *) {
CabinUpgradeView()
} else {
StandardCabinView()
}
swift
✅ Best Practices for Swift Availability and Macros
✳️ Always double up: Use @available on declarations and #available at call sites. Belt and suspenders.
✳️ Fence your features: Use #if swift(…) to guard Swift 6-only code, especially around macros or new syntactic sugar.
✳️ Test across targets: Manually test iOS 17 and iOS 18 builds if your deployment range spans them. Previews are great — actual installs are better.
✳️ Isolate macro impact: When using macros, prefer opt-in exposure. Don’t sprinkle them across unrelated modules until your macro code is stable.
✳️ Centralize version logic: Put common @available or #available logic into helpers, especially when it involves toggling views or services.
swift
enum DeploymentChecks {
static var isUpgradeAvailable: Bool {
if #available(iOS 18, *) { return true }
return false
}
}
swift
swift
if DeploymentChecks.isUpgradeAvailable {
CabinUpgradeView()
}
swift
✳️ Don’t trust autocomplete: Xcode may show you APIs and macros that aren’t safe yet. Always confirm against your deployment target and SDK.
✳️ Use build settings: Make sure macros are supported via OTHER_SWIFT_FLAGS=-enable-experimental-feature Macros or your project won’t compile in CI/CD.
✳️ Watch WWDC sessions: Stay up to date by checking out “What’s New in Swift”. Apple often slips in new macro and availability behavior in these talks.
- Consider wrapping older features with canImport(LegacyModule)
- Avoid relying on Swift macros unless your deployment target starts at iOS 17+
- Be explicit in SPM versioning to prevent macro-related build failures
🚨 Real-World Gotchas
- Xcode lies: Autocomplete will show you APIs your current deployment target can’t use. Trust the compiler, not the IDE.
- Beta SDK macros break: Even Apple’s own stuff can fail silently when you mismatch SDK and Swift version.
- SPM macros in CI: You might build fine locally, but CI will explode if you don’t specify the right Swift toolchain and flags.
🎯 Bonus: More Real-World iOS Survival Stories
If you’ve ever shipped a beta feature too early or crashed a whole test flight because of missing a version check — hey, same. But with @available, #if swift(…), and Swift macros done right, you can build forward-thinking features without causing mid-air failure.
Check out the other posts (https://medium.com/@wesleymatlock) on SwiftUI, concurrency, macros, and real-world debugging — or send me your stories. Always up for a chat about Swift, airline app design, and shipping features that actually make it to the gate. 🛫
Originally published on Medium