Implementation reference · iOS 26 and iOS 27
Localight
Localight is a SwiftUI showcase for Apple’s Foundation Models framework, not a production-ready app. It compares how a native chat flow is implemented with the iOS 26 and iOS 27 SDKs.
Problem
Comparing two SDK generations in one project
Apple changed the Foundation Models API between the iOS 26 and iOS 27 SDKs. Code that uses the newer API cannot be compiled with the older SDK, while an app built with the newer SDK may still run on iOS 26. Because Localight is a showcase for both generations, it needs to separate SDK availability from runtime availability instead of presenting only the latest API surface.
Implementation scope
Foundation Models chat flow
After SystemLanguageModel reports that the model is available, the version-specific view model creates a LanguageModelSession with user-editable instructions. Prompts use either respond or streamResponse, depending on the selected mode. Applying new instructions or a new temperature replaces the session and clears its in-memory conversation. On iOS 27, the same flow also accepts one image, records context and per-message token usage, and maps typed generation failures to alerts.
Interface
Chat and session controls
The iOS 27 chat shows a single image attachment with its prompt and response, plus optional per-message token counts. Its settings screen exposes response streaming, model instructions, current context usage, and temperature. Both screens observe the same view model, so the controls and metrics refer to the active in-memory session.


The essential loop
Stream a local response
The iOS 27 view model prepares the prompt, publishes partial responses while the on-device model is generating them, and stores the completed answer in the chat.
func streamResponse() async {
let image = await preparePrompt()
let stream = responseStream(with: image)
defer {
streamingResponse = ""
isResponding = false
}
do {
for try await chunk in stream {
streamingResponse = chunk.content
}
let response = try await stream.collect()
messages.append(Message_27(text: response.content, sender: .model))
let modelMessageIndex = messages.index(before: messages.endIndex)
updateTokenUsage(for: modelMessageIndex, using: response.usage)
} catch {
presentGenerationError(error)
}
}
Implementation notes
Separate implementations for both SDKs
The iOS_26 and iOS_27 folders contain dedicated chat, settings, component, and model types. SDK-specific conditions omit the iOS 27 implementation from iOS 26 builds; iOS 27 builds compile both, and LocalightApp selects the runtime variant without raising the deployment target above iOS 26.
The system model provides a context window of 4,096 tokens per session. Localight displays current usage on iOS 27, including the instructions, and presents a dedicated error when the limit is exceeded.