All projects

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.

Localight chat showing an image prompt, a generated response, and token counts
Image input, generated response, and token counts
Localight settings for streaming, token usage, instructions, context, and temperature
Streaming, instructions, context usage, and temperature

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.

swift
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.