Multimodal AI in SwiftUI: Image Input, Dynamic Profiles, and Model Switching
This architecture targets the Xcode 27 and iOS 27 beta cycle. The goal is not to place a complete agent inside a SwiftUI view. Establish four boundaries instead: the UI selects an image, a preprocessor creates a controlled attachment, a model policy selects an available backend, and a session service owns generation and cancellation. Beta API churn then stays inside one adapter.
UI state should describe UI facts
A view model can expose idle, preparing, generating, result, and failure, plus the selected image and a cancellable Task. The view does not own a language-model session and never handles provider credentials. When the user replaces the image or leaves the screen, cancel the old task. Commit a response only if its task id still matches, so a late result cannot overwrite a newer selection.
After loading, normalize orientation, constrain pathological pixel dimensions, and remove metadata the task does not need. Preserve task-relevant detail before compression. Apple accepts several image representations and arbitrary aspect ratios, but larger images can consume more tokens and latency. That is a reason to evaluate real photographs, not to impose one arbitrary crop on every use case.
Keep preprocessing deterministic enough to test. Record dimensions and transformation version, not the user’s full image, in ordinary diagnostics.
Make model selection a policy
Define product intents such as privateQuickLook, deepReasoning, and approvedProvider. A policy checks OS availability, model availability, vision capability, network state, and organization settings before returning a LanguageModel. Do not hard-code a device-name list, and do not silently upload a sensitive photo when the on-device model is unavailable.
If fallback changes the data route, explain the change and let the user choose. On-device inference, Private Cloud Compute, and an external provider carry different authentication, billing, and privacy implications. Record the selected model in a trace while returning only a stable domain result to the UI.
Capability checks must happen again when a task starts. Availability can change because of downloads, policy, account state, or connectivity after the screen was presented.
Send an image with a bounded objective
The service combines an image attachment with explicit text: identify only visible objects, mark uncertainty as unknown, and do not infer identity or location without evidence. For data rendered by the UI, request a versioned generable domain type. Validate it again after generation: bound array length, enforce text limits, and route sensitive categories to review.
Image-capable tool arguments do not authorize unrestricted tools. OCR, barcode, or custom recognition outputs remain untrusted. URLs pass through an allowlist. Contacts, photo-library writes, and network uploads require application and platform permission. The model cannot bypass a privacy prompt.
Avoid displaying a partially decoded structured result as committed state. Streaming text may drive a progress surface, but the domain object appears only after complete parsing and validation.
Inject dynamic profiles rather than global state
A profile can compose instructions, tools, and model configuration for a use case such as recipe suggestions or receipt extraction. Define it as an immutable, versioned application value. The composition root injects the profile and session service into the view model. SwiftUI previews and unit tests receive a fake service; production receives the Foundation Models adapter.
The fake can return a fixed result after a controlled delay and simulate cancellation, model unavailability, corrupt images, guardrail responses, and tool failure. Tests assert state transitions and reject late results without requiring a real model or paid provider call.
Profiles must not grant authority. A user-selected “advanced” mode may change model or reasoning configuration, but it cannot add a write tool that the signed-in role does not possess.
Design the experience for uncertainty
Show a concrete stage and cancel button during work; do not invent a percentage. Announce status changes through accessibility APIs. Distinguish unsupported hardware, temporarily unavailable model, unreadable image, and content that cannot be processed. Mark the result as AI-generated and provide editing or reporting paths.
Build an evaluation set with portrait and landscape photos, screenshots, small text, low light, several objects, sensitive content, and irrelevant images. Score factuality, omissions, unsupported inference, latency, memory, cancellation, and data route. Use one domain rubric across models while allowing each provider adapter to use its native configuration.
The hard part of multimodal SwiftUI is not one attachment call. It is asynchronous state, privacy routing, and capability variation. Separating the view, preprocessing, model policy, and session service captures the benefit of the WWDC26 abstraction without spreading beta details throughout the application.