Build On-Device Search Suggestions With Foundation Models
Foundation Models is one of the more interesting new Apple frameworks because it makes a local language model feel like a Swift API instead of a web service. You create a session, send a prompt, and get a typed response back with Swift concurrency.
For app code, the important word is local. The default system model depends on Apple Intelligence availability, so your feature needs to handle devices where the model is not available, not enabled, or not ready yet.
Let’s build a small search suggestion model for a SwiftUI app.
The Foundation Models framework is available on iOS 26, iPadOS 26, macOS 26, and visionOS 26. It is unavailable on tvOS and watchOS in the current SDK.
Describe the output
The nicest version of this feature does not ask the model for a paragraph and then split strings by commas. Instead, describe the output you want:
import FoundationModels
@Generable
struct SearchSuggestions {
@Guide(description: "Four short suggested search terms", .count(4))
var terms: [String]
}
@Generable tells Foundation Models that this type can be generated by the model. @Guide adds a constraint and a little semantic help for the property.
That gives your UI a real Swift value:
let terms: [String]
No JSON parsing, no regular expressions, and no “please respond with exactly…” string ritual unless you actually need more instruction.
Check availability
The model can be unavailable for normal reasons. A device might not support Apple Intelligence, the user might have it disabled, or the model might not be ready.
import FoundationModels
import Observation
@MainActor
@Observable
final class SearchSuggestionModel {
var query = ""
var suggestions: [String] = []
var errorMessage: String?
private let languageModel = SystemLanguageModel.default
var isAvailable: Bool {
if case .available = languageModel.availability {
return true
}
return false
}
}
I like checking availability instead of only checking a boolean because the enum can tell you why the feature cannot run. That lets you show better UI than a disabled button with no explanation.
Generate suggestions
Now add the generation method:
func generateSuggestions() async {
switch languageModel.availability {
case .available:
break
case .unavailable(let reason):
suggestions = []
errorMessage = unavailableMessage(for: reason)
return
}
let session = LanguageModelSession(
instructions: "Generate concise search suggestions for an iOS developer blog."
)
do {
let response = try await session.respond(
to: "Suggest search terms related to: \(query)",
generating: SearchSuggestions.self
)
suggestions = response.content.terms
errorMessage = nil
} catch {
suggestions = []
errorMessage = error.localizedDescription
}
}
private func unavailableMessage(
for reason: SystemLanguageModel.Availability.UnavailableReason
) -> String {
switch reason {
case .deviceNotEligible:
"This device does not support Apple Intelligence."
case .appleIntelligenceNotEnabled:
"Apple Intelligence is not enabled."
case .modelNotReady:
"The language model is not ready yet."
@unknown default:
"The language model is not available right now."
}
}
The key call is:
try await session.respond(
to: prompt,
generating: SearchSuggestions.self
)
The result’s content is a SearchSuggestions value, so the rest of the app can stay boring in the best way.
Use it from a view
Here is a small view around the model:
import SwiftUI
struct SearchSuggestionView: View {
@State private var model = SearchSuggestionModel()
var body: some View {
Form {
TextField("Topic", text: $model.query)
Button("Generate Suggestions") {
Task {
await model.generateSuggestions()
}
}
.disabled(model.query.isEmpty || !model.isAvailable)
if let errorMessage = model.errorMessage {
Text(errorMessage)
.foregroundStyle(.secondary)
}
ForEach(model.suggestions, id: \.self) { suggestion in
Text(suggestion)
}
}
}
}
This is intentionally small, but it has the pieces a production feature needs:
- It checks model availability.
- It asks for structured output.
- It keeps generated data as typed Swift state.
- It has a fallback path when generation fails.
A good use case boundary
Search suggestions are a nice fit because the model is assisting the user, not deciding anything critical. I would still keep deterministic search ranking in normal app code. Let the model suggest words, labels, summaries, drafts, or tags. Let your app decide what gets stored, ranked, purchased, deleted, or sent.
That line keeps the feature useful without making the model the source of truth.
Further reading:
- WWDC25 Foundation Models session: https://developer.apple.com/videos/play/wwdc2025/286/
- Foundation Models documentation: https://developer.apple.com/documentation/FoundationModels