How to Fall Back Gracefully When Apple Intelligence Isn't Available
Source: Dev.to
Introduction
Apple Intelligence is one of the most exciting developments for iOS developers in years. The Foundation Models framework gives you direct, on‑device access to a large language model—no API costs, no network calls, and full privacy.
But the hard truth is that a large chunk of your users can’t run it. If you just drop a LanguageModelSession() into your app without any checks, you’ll ship broken experiences to many devices.
Below is a clean, ready‑to‑copy guide that shows:
- Which devices can’t use Apple Intelligence
- The three availability states the framework reports
- How to handle each case in the UI
- A simple, reusable architecture for keeping your feature code tidy
Who Can’t Use Apple Intelligence?
| iOS version | Minimum hardware | Apple Intelligence requirement |
|---|---|---|
| iOS 26.3 (Feb 11 2026) | iPhone 11 + (A13 or newer) | A17 Pro or newer |
Only the following devices meet the hardware requirement:
- iPhone 15 Pro / iPhone 15 Pro Max
- iPhone 16 / 16 Plus / 16 Pro / 16 Pro Max
- iPhone 16e
- iPhone 17 / 17 Pro / 17 Pro Max / iPhone Air
All other devices—including the standard iPhone 15—run iOS 26.3 but receive zero Foundation Models access.
Even on eligible hardware you still need:
- Apple Intelligence enabled in Settings (opt‑in)
- ≥ 7 GB of free storage
- Device and Siri language set to a supported language
- The model fully downloaded (it downloads in the background after enablement)
Bottom line: You cannot assume the model is available just because the OS version is recent.
The Three Unavailability Cases
SystemLanguageModel.default.availability can be one of three unavailable cases (plus the happy‑path .available):
import FoundationModels
switch SystemLanguageModel.default.availability {
case .available:
// Good to go
case .unavailable(.deviceNotEligible):
// A13 or older chip – Foundation Models will never work here
case .unavailable(.appleIntelligenceNotEnabled):
// Compatible device, but user hasn't turned on Apple Intelligence
case .unavailable(.modelNotReady):
// Compatible + enabled, but model is still downloading
@unknown default:
// Future‑proof: handle any new cases Apple might add
break
}
Each case requires a different UX response.
Building a Proper Fallback Strategy
Think of the three cases as three separate UX problems.
Case 1 – Device Not Eligible
Permanent limitation. The hardware will never support Apple Intelligence.
Do not show spinners or “check back later” messages. Instead, present a fully functional, non‑AI version of the feature.
case .unavailable(.deviceNotEligible):
// Serve a non‑AI version of the feature
showBasicTextSummarizer()
Example: In a smart‑journaling app you might skip auto‑tagging and let the user tag manually. In a writing assistant you could offer preset templates instead of generated suggestions.
Case 2 – Apple Intelligence Not Enabled
Hardware is capable, but the user hasn’t opted in. You can prompt them, but be gentle:
case .unavailable(.appleIntelligenceNotEnabled):
showEnablementBanner(
message: "Enable Apple Intelligence in Settings to unlock AI‑powered suggestions.",
settingsURL: URL(string: UIApplication.openSettingsURLString)!
)
// Still show the basic version of the feature below the banner
- The banner appears once and explains the benefit clearly.
- Tapping it opens Settings → Apple Intelligence & Siri via
UIApplication.openSettingsURLString. - You cannot deep‑link directly to that sub‑screen, so the user must navigate there themselves.
Case 3 – Model Not Ready
Temporary state while the model is downloading after enablement. Here you should wait and retry, not fall back permanently.
case .unavailable(.modelNotReady):
showLoadingState(message: "AI features are warming up. This only takes a moment.")
scheduleAvailabilityCheck()
A simple retry implementation:
func scheduleAvailabilityCheck() {
Task {
try? await Task.sleep(for: .seconds(10)) // 10‑second delay
await checkAndUpdateAvailability()
}
}
Poll no more often than every 10–30 seconds while the user stays on the screen.
Putting It Together: A Clean Architecture
Below is a minimal, reusable pattern that centralises all availability handling and keeps your UI code tidy.
import FoundationModels
import SwiftUI
// MARK: - Availability State
enum AIAvailabilityState {
case available
case unsupportedDevice
case notEnabled
case modelLoading
}
// MARK: - Feature Manager
@Observable
final class AIFeatureManager {
private(set) var state: AIAvailabilityState = .modelLoading
init() {
refreshAvailability()
}
/// Checks the system availability and updates `state`.
func refreshAvailability() {
switch SystemLanguageModel.default.availability {
case .available:
state = .available
case .unavailable(.deviceNotEligible):
state = .unsupportedDevice
case .unavailable(.appleIntelligenceNotEnabled):
state = .notEnabled
case .unavailable(.modelNotReady):
state = .modelLoading
@unknown default:
state = .modelLoading
}
}
/// Periodically re‑checks availability (used for the “model not ready” case).
func scheduleRecheck() {
Task {
try? await Task.sleep(for: .seconds(15))
await MainActor.run { refreshAvailability() }
}
}
}
// MARK: - SwiftUI View Example
struct AIFeatureView: View {
@StateObject private var manager = AIFeatureManager()
var body: some View {
VStack {
switch manager.state {
case .available:
AIEnabledView() // Your full AI‑powered UI
case .unsupportedDevice:
BasicFeatureView() // Non‑AI fallback
case .notEnabled:
VStack {
BasicFeatureView()
EnableBanner()
}
case .modelLoading:
LoadingView(message: "AI is warming up…")
.onAppear { manager.scheduleRecheck() }
}
}
.animation(.default, value: manager.state) // smooth transitions
}
}
// MARK: - UI Helpers (simplified)
struct EnableBanner: View {
var body: some View {
Button {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
} label: {
Text("Enable Apple Intelligence in Settings")
.font(.subheadline)
.padding()
.background(Color.blue.opacity(0.1))
.cornerRadius(8)
}
}
}
struct LoadingView: View {
let message: String
var body: some View {
VStack(spacing: 12) {
ProgressView()
Text(message)
.font(.footnote)
.foregroundColor(.secondary)
}
.padding()
}
}
How it works
AIFeatureManagerencapsulates all system checks and exposes a singlestatevalue.- The SwiftUI view reacts to
stateand shows the appropriate UI:- Full AI feature (
AIEnabledView) - Basic fallback (
BasicFeatureView) - Enable‑banner + basic UI (
EnableBanner) - Loading spinner while the model downloads (
LoadingView)
- Full AI feature (
- When the model is still downloading, the view triggers
scheduleRecheck()to poll again after a short delay.
You can reuse AIFeatureManager across multiple screens or even wrap it in a @EnvironmentObject if many parts of your app need the same availability information.
TL;DR Checklist
| Situation | What to show | Action |
|---|---|---|
| Device not eligible | Basic, non‑AI UI | No prompts, just fallback |
| Apple Intelligence not enabled | Basic UI + one‑time banner linking to Settings | Offer enablement, don’t block |
| Model not ready | Loading spinner + retry logic | Poll SystemLanguageModel.default.availability until .available |
| All good | Full AI‑powered UI | Use LanguageModelSession as intended |
By detecting the exact unavailability reason and responding appropriately, you keep every user’s experience smooth—whether they have the latest iPhone 17 Pro Max or an iPhone 12. Happy coding!
Handling Apple Intelligence Availability
When you integrate Apple Intelligence (or any foundation model) you’ll encounter three possible unavailability states:
| Unavailability Reason | Meaning | Typical UI Response |
|---|---|---|
deviceNotEligible | The device can’t run the model (e.g., older iPhone) | Show a fallback UI that works everywhere |
appleIntelligenceNotEnabled | The user has disabled Apple Intelligence in Settings | Prompt the user to enable it |
modelNotReady | The model is still loading on the device | Show a loading indicator and retry later |
Below is a concise way to handle these states in SwiftUI.
1️⃣ Model‑layer: Detecting Availability
import SwiftUI
import FoundationModels
final class AIFeatureManager: ObservableObject {
enum State {
case available
case unsupportedDevice
case notEnabled
case modelLoading
}
@Published private(set) var state: State = .unsupportedDevice
init() {
refreshAvailability()
}
func refreshAvailability() {
switch SystemLanguageModel.default.availability {
case .available:
state = .available
case .unavailable(.deviceNotEligible):
state = .unsupportedDevice
case .unavailable(.appleIntelligenceNotEnabled):
state = .notEnabled
case .unavailable(.modelNotReady):
state = .modelLoading
scheduleRetry()
@unknown default:
// Future‑proofing: treat unknown cases as unsupported
state = .unsupportedDevice
}
}
private func scheduleRetry() {
Task {
try? await Task.sleep(for: .seconds(15))
refreshAvailability()
}
}
}
2️⃣ View‑layer: Rendering the Correct UI
struct SmartFeatureView: View {
@StateObject private var aiManager = AIFeatureManager()
var body: some View {
switch aiManager.state {
case .available:
AIEnhancedView()
case .unsupportedDevice:
BasicFallbackView()
case .notEnabled:
EnablePromptView {
aiManager.refreshAvailability()
}
case .modelLoading:
LoadingView(message: "AI features are getting ready…")
}
}
}
Each state gets its own dedicated view, keeping the UI thin and maintainable. When the model becomes available, refreshAvailability() updates state and SwiftUI automatically re‑renders.
What Your Fallback UI Should Actually Do
A fallback isn’t just “hide the AI button.” It should still deliver value without the model. Here are common patterns:
| Feature | Fallback Idea |
|---|---|
| Smart text summarization | Show a character‑count preview or a “show more/less” toggle. |
| Auto‑tagging / content classification | Let the user pick from a curated list of tags manually, or rely on keyword search. |
| AI‑generated suggestions | Provide a set of hand‑written preset options. |
| Contextual chat assistant | Switch to an FAQ‑style interface or a link to help docs. |
Goal: A user on an iPhone 14 should open your app and see a working, useful feature—not a broken screen or a wall of text explaining why their device isn’t good enough.
A Note on @unknown default
Because Apple’s API is still evolving, always include @unknown default in your switch. If a new unavailability reason appears in a future OS version, the compiler will warn you, and you can safely treat it as “unsupported device.”
Testing Without an Eligible Device
Simulating the three unavailability states can be done as follows:
| State | How to Simulate |
|---|---|
deviceNotEligible | Run the app on an older simulator (e.g., iPhone 14). |
appleIntelligenceNotEnabled | On a supported simulator, go to Settings → Apple Intelligence & Siri and toggle it off. |
modelNotReady | Mock the availability in AIFeatureManager for testing. |
Making Availability Mockable
protocol LanguageModelAvailabilityChecker {
var availability: SystemLanguageModel.Availability { get }
}
struct LiveChecker: LanguageModelAvailabilityChecker {
var availability: SystemLanguageModel.Availability {
SystemLanguageModel.default.availability
}
}
struct MockChecker: LanguageModelAvailabilityChecker {
var availability: SystemLanguageModel.Availability
}
Inject LiveChecker in production and MockChecker in unit tests. This lets you verify UI behavior for every availability state without a physical device.
The Bigger Picture
Great apps treat foundation models as progressive enhancements—they improve the experience for capable devices while still functioning perfectly on older hardware.
- Build the baseline (fallback UI) first.
- Layer the intelligence on top once the baseline is solid.
Requirements
- iOS 26+
- Xcode 26+
Apple Intelligence‑compatible devices
- iPhone 15 Pro / Pro Max
- All iPhone 16 and iPhone 17 models
Happy coding! 🎉