โ† All articles

On-Device AI in SwiftUI with Apple's Foundation Models

Run on-device AI in a SwiftUI app with Apple's Foundation Models: LanguageModelSession, guided generation with @Generable, streaming, and iOS 27 updates.

On-Device AI in SwiftUI with Apple's Foundation Models

Apple's Foundation Models framework gives you the on-device language model behind Apple Intelligence through a plain Swift API. It runs on the device, so it is free, private, works offline, and needs no API keys or accounts. For the everyday AI tasks in an app, summarizing, extracting structured data, classifying, rewriting, and answering with tools, it is often all you need, and it takes only a few lines of SwiftUI.

This guide covers the framework end to end: checking availability, running a prompt, getting typed output instead of parsing strings, streaming, giving the model tools, and what iOS 27 added at WWDC26. The examples are current SwiftUI and FoundationModels.

On this page

Step 1: Check the model is available

The on-device model exists only on Apple Intelligence capable devices, and only when the user has it enabled and the model has finished downloading. Check before you show any AI feature, and hide or disable it gracefully when it is not there.

import FoundationModels
 
switch SystemLanguageModel.default.availability {
case .available:
    // Ready to use.
    break
case .unavailable(let reason):
    // reason tells you why: the device is not eligible, Apple
    // Intelligence is off, or the model is still downloading.
    break
}

Treat unavailability as a normal state, not an error. A big chunk of your users may be on a device that does not have the model, and your app should still work for them.

Step 2: A session and a prompt

The entry point is LanguageModelSession. Create one, send a prompt, read the content. A session also keeps context, so follow-up prompts in the same session remember the conversation.

import FoundationModels
 
let session = LanguageModelSession()
let response = try await session.respond(to: "Summarize this in one sentence: \(article)")
print(response.content) // a String

In SwiftUI, hold the session as state so it survives view updates, and drive it from a task.

struct TipView: View {
    @State private var session = LanguageModelSession()
    @State private var tip = ""
 
    var body: some View {
        Text(tip.isEmpty ? "Thinking..." : tip)
            .task {
                let reply = try? await session.respond(to: "Give me one concise SwiftUI tip.")
                tip = reply?.content ?? ""
            }
    }
}

You can also pass instructions when you create the session (a system prompt) to set tone and rules once, rather than repeating them in every request.

Step 3: Typed output with @Generable

This is the feature that makes the framework worth reaching for. Annotate a Swift type with @Generable and ask the model to produce it. Instead of a string you have to hope is valid JSON, you get a real, populated, type-checked value. The framework constrains the model at the token level, so the output is always a valid instance of your type.

import FoundationModels
 
@Generable
struct Recipe {
    @Guide(description: "A short, appetizing title")
    var title: String
 
    @Guide(description: "Between 3 and 6 ingredients")
    var ingredients: [String]
 
    var minutes: Int
}
 
let session = LanguageModelSession()
let response = try await session.respond(
    to: "A quick weeknight pasta.",
    generating: Recipe.self
)
 
let recipe = response.content // a fully populated Recipe, no parsing

The @Guide annotations steer each field, and the type itself is the schema. There is no "please return JSON" in the prompt and no decoding step that can throw. This is the difference between shipping a feature and fighting a model that keeps returning almost-valid text.

Step 4: Stream the response

For anything longer than a sentence, stream it so the UI fills in as the model generates rather than freezing until it finishes. streamResponse yields the partial result as it grows.

let stream = session.streamResponse(to: "Explain optionals in three short paragraphs.")
for try await partial in stream {
    text = partial.content // update the view on each snapshot
}

Streaming works with guided generation too. As the model fills in a @Generable type, you receive partial versions of it, so you can render fields the moment they are ready instead of waiting for the whole object.

Step 5: Give the model tools

Tools let the model call your code when it needs information it does not have, like live data or something from your app's database. You define a tool, hand it to the session, and the model decides when to invoke it, all on device.

struct WeatherTool: Tool {
    let name = "getWeather"
    let description = "Get the current temperature for a city."
 
    @Generable
    struct Arguments {
        @Guide(description: "The city name")
        var city: String
    }
 
    func call(arguments: Arguments) async throws -> ToolOutput {
        let temp = await fetchTemperature(for: arguments.city)
        return ToolOutput("\(temp) degrees in \(arguments.city)")
    }
}
 
let session = LanguageModelSession(tools: [WeatherTool()])
let response = try await session.respond(to: "What should I wear in Paris today?")

The model reads the tool's description, decides Paris weather is relevant, calls getWeather, and folds the result into a natural answer. Your tool runs your Swift, so it can hit your data, your network, or the device, and nothing leaves the phone unless your tool sends it.

What iOS 27 added

WWDC26 turned Foundation Models from an on-device-only API into a single interface over several models. The headline changes:

  • One API, many models. The on-device model, Apple's Private Cloud Compute, and third-party clouds like Claude and Gemini now sit behind the same call site through a Language Model protocol. You can start on device and escalate a hard prompt to a bigger model without rewriting your feature.
  • Vision. The on-device model gained image input. You can attach a UIImage or other image types to a prompt and ask about visual content, on device.
  • Private Cloud Compute. PrivateCloudComputeLanguageModel reaches Apple's server models with a larger context window and reasoning levels, still with no accounts, keys, or auth, and it reached watchOS this cycle.
  • Better plumbing. Token counting and context-size inspection, refined guardrails that trigger fewer false positives, and dynamic profiles that swap the model, tools, or instructions mid-session.

For the wider WWDC26 toolchain around this, see the Xcode 27 guide and what's new in Swift.

When to use it, and when not

Reach for the on-device model when the task is bounded and privacy or offline support matters: summarizing an article, pulling structured fields out of messy text, classifying, rewriting in a tone, or answering with a tool that reads your app's data. It is fast, free, and never leaves the device.

Do not reach for it as a general-purpose chatbot with broad world knowledge. The on-device model is small and tuned for focused tasks, not for deep reasoning over obscure facts. When you need that, the iOS 27 update makes it a one-line change to route the same prompt to Private Cloud Compute or a third-party cloud, so the pattern is to start on device and escalate only where you must.

FAQ

Which devices support Foundation Models?

Apple Intelligence capable devices, with the feature enabled and the model downloaded. Always check SystemLanguageModel.default.availability and design for the unavailable case, because many users will be on older hardware.

Does it cost anything or need API keys?

No. The on-device model and Private Cloud Compute are free and need no keys or accounts. Only third-party cloud models, reached through the same API, use their provider's keys.

Do I have to parse JSON?

No. @Generable gives you a typed Swift value directly, and constrained decoding guarantees it is valid. That is the main reason to use the framework over calling a raw model.

How large is the on-device model?

Small by frontier standards, and tuned for on-device tasks like summarizing, extracting, classifying, and tool use. For heavy reasoning, escalate to Private Cloud Compute or a cloud model through the same interface.

Can it work with images?

Yes, as of the iOS 27 update. You can attach images to a prompt and the on-device model can reason about them.

Does it work offline?

The on-device model does. Private Cloud Compute and third-party cloud models need a network connection.

On-device AI used to mean shipping and running your own model. Foundation Models replaces that with a session, a prompt, and a @Generable type, and the iOS 27 update means the same code reaches Apple's cloud and third-party models when the on-device one is not enough. For most app features, a few lines is the whole integration.


Spaceport generates a production-ready SwiftUI Xcode project with an AI assistant already wired up: one async call that runs on Apple's on-device Foundation Models where available and falls back to OpenAI or Anthropic in the cloud, alongside RevenueCat subscriptions, Sign in with Apple and Google, notifications, onboarding, and App Store Connect pricing across 25 markets. You start from a working AI feature instead of a blank session. From an indie iOS dev, for indie iOS devs.

Read more at spaceport.build

Community appsJoin Discord