Apple’s Foundation Models framework gives iOS developers a native way to experiment with generative AI features powered by the on-device language model behind Apple Intelligence.
In this guide I'll walk through a small SwiftUI experiment: an ambient sound mixer that turns a natural language prompt into a list of sounds with different volume levels that create an ambient soundscape.
Before starting
To try this, you need:
- Xcode 27 with the Foundation Models framework available.
- A device that supports Apple Intelligence.
- Apple Intelligence enabled on the device.
For this we will build:
- A structured Swift type that describes the model output.
- An availability check before asking the model for anything.
- A model session with developer instructions and a user prompt.
- A SwiftUI button that triggers generation and plays the selected sounds.
Step 1: Model the response with @Generable
The most interesting part of the framework is guided generation. Instead of asking a model to return JSON and then hoping it formats the output correctly, you describe the Swift type you want the model to generate.
For this sound mixer, the result can be simple: a list of sound components. Each component has a local file name and a volume.
import FoundationModels
@Generable
struct AmbientMix: Equatable {
@Guide(description: "Sounds that should be layered together for the requested ambience.")
@Guide(.count(1...4))
var components: [AmbientSoundComponent]
}
@Generable
struct AmbientSoundComponent: Equatable {
@Guide(description: "The exact local sound file name without the file extension.")
var fileName: String
@Guide(description: "Playback volume between 0.1 and 1.0.")
var volume: Float
}
The @Generable macro tells the framework that this type can be generated by the model. The @Guide annotations give the model extra context and constraints.
In this case, the model should return between 1 and 4 sounds. That matters because an ambient mix with 20 sounds would probably be noisy and hard to control.
This is the part that feels different from the usual cloud LLM flow. Normally, I would write something like:
Return a valid JSON object with this shape...
Then I would still need to parse the response, handle malformed JSON, retry, validate missing fields, and protect the app from bad output.
With guided generation, the framework can generate a Swift value that matches the type I described.
Step 2: Check model availability
Before using the model, the app needs to check whether it is actually available.
Foundation Models depends on Apple Intelligence support and model readiness. The user may be on an unsupported device, Apple Intelligence may not be enabled, or the model may not be ready yet.
A simple availability check can look like this:
import FoundationModels
func canUseFoundationModels() -> Bool {
let model = SystemLanguageModel.default
switch model.availability {
case .available:
return true
case .unavailable(let reason):
print("Foundation Models unavailable: \(reason)")
return false
}
}
For a real app, I would avoid only printing the reason. I would use this result to decide what the UI should do.
For example:
- Hide the AI feature if it is not supported.
- Show a message explaining that Apple Intelligence is required.
- Fall back to a manual sound picker.
- Fall back to a server-based model if the product supports that.
The important part is: do not assume the model is always available.
Step 3: Create the language model session
Now we can create a LanguageModelSession.
The session receives developer-controlled instructions. These instructions should tell the model how to behave and what constraints to follow.
For this app, I want the model to act like a small soundscape assistant. It should choose only from my local sound catalog and avoid inventing file names.
import FoundationModels
func generateAmbientMix(
prompt: String,
availableSounds: [String]
) async throws -> AmbientMix {
let instructions = """
You create ambient sound mixes for a relaxation app.
Choose only from the available local sound files.
Do not invent file names.
Prefer simple mixes that match the user prompt.
Available sounds:
\(availableSounds.joined(separator: ", "))
"""
let session = LanguageModelSession(
model: SystemLanguageModel.default,
instructions: instructions
)
let response = try await session.respond(
to: prompt,
generating: AmbientMix.self
)
return response.content
}
The important line is this one:
let response = try await session.respond(
to: prompt,
generating: AmbientMix.self
)
The model is not just returning text. It is generating an AmbientMix.
That means the rest of the app can work with normal Swift values:
let components = response.content.components
No JSONDecoder, no regex, and no manual string parsing for the model response.
Prompting note
I would keep the instructions mostly static and pass the user's text as the prompt.
The instructions are controlled by the app. The prompt is controlled by the user.
That separation makes the feature easier to reason about.
Step 4: Validate the generated result
Guided generation helps a lot, but I would still validate the result before playing anything.
For this sound mixer, there are two simple checks I would not skip:
- Make sure the generated file name exists in the local catalog.
- Clamp the volume to the range the app supports.
func validatedComponents(
from mix: AmbientMix,
availableSounds: [String]
) -> [AmbientSoundComponent] {
let soundCatalog = Set(availableSounds)
return mix.components.compactMap { component in
guard soundCatalog.contains(component.fileName) else {
return nil
}
let safeVolume = min(max(component.volume, 0.1), 1.0)
return AmbientSoundComponent(
fileName: component.fileName,
volume: safeVolume
)
}
}
Even when the output shape is correct, the app should still protect its own domain rules.
The model can suggest, but the app should decide what is allowed.
Step 5: Use it from SwiftUI
From SwiftUI, we can trigger generation inside a Task.
The UI should show a loading state, avoid multiple requests at the same time, and handle errors clearly.
Here is a simplified example:
import SwiftUI
struct AmbientMixerView: View {
@State private var userPrompt = ""
@State private var isGenerating = false
@State private var errorMessage: String?
private let availableSounds = [
"rain",
"fire",
"wind",
"cafe"
]
var body: some View {
VStack(spacing: 16) {
TextField(
"Describe the ambience you want",
text: $userPrompt,
axis: .vertical
)
.textFieldStyle(.roundedBorder)
Button {
generateMix()
} label: {
if isGenerating {
ProgressView()
} else {
Text("Generate Ambience")
}
}
.disabled(userPrompt.isEmpty || isGenerating)
if let errorMessage {
Text(errorMessage)
.foregroundStyle(.red)
}
}
.padding()
}
private func generateMix() {
isGenerating = true
errorMessage = nil
Task {
defer { isGenerating = false }
do {
guard canUseFoundationModels() else {
errorMessage = "Apple Intelligence is not available on this device."
return
}
let mix = try await generateAmbientMix(
prompt: userPrompt,
availableSounds: availableSounds
)
let safeComponents = validatedComponents(
from: mix,
availableSounds: availableSounds
)
playAmbientMix(safeComponents)
} catch {
errorMessage = "Could not generate a mix right now."
print(error)
}
}
}
private func playAmbientMix(_ components: [AmbientSoundComponent]) { ... }
}
That was the new native way to add AI to you projects.
My takeaway
Foundation Models is not a replacement for every AI feature.
For complex reasoning, very large context, heavy multimodal tasks, or features that need to work on every device, a server-side model will probably make more sense.
But for focused, privacy-friendly app features with clear inputs and structured outputs, the framework is really cool.
The sound mixer example is intentionally small, but it shows the core flow:
- Define a structured output with
@Generable. - Check model availability.
- Create a
LanguageModelSession. - Ask for a generated Swift value.
- Validate the result.
- Connect it to your UI.
That is a nice developer experience, and I think this is where Foundation Models can be useful in real iOS apps.
Sources:
Apple Developer Documentation — Foundation Models: https://developer.apple.com/documentation/FoundationModels
Apple WWDC25 — Meet the Foundation Models framework: https://developer.apple.com/videos/play/wwdc2025/286/
Apple Developer Documentation — Generable: https://developer.apple.com/documentation/foundationmodels/generable


Top comments (0)