admaDIC App Development & IT Solutions

Swift Foundation Models: Chat

by Annett Schwarze | 2025-09-26

Apple's Foundation Models Framework provides simple access to language models. The sample below shows, how to use a `LanguageModelSession` to send a message and react to a stream of responses.

The feature has been tested on a MacBook with Silicon processor, macOS 26 Tahoe and Xcode 26.

The communication with the LanguageModelSession is very simple:

        

import SwiftUI
import Foundation
import FoundationModels

@available(iOS 26.0, *)
struct SampleChatView: View {
    class Message: ObservableObject, Identifiable {
        let id: UUID = UUID()
        let timestamp: Date = Date()
        @Published var text: String
        let isUser: Bool
        @Published var isUpdating: Bool = false

        init(text: String, isUser: Bool) {
            self.text = text
            self.isUser = isUser
        }
    }

    @MainActor
    class Model: ObservableObject {
        @Published var messageText: String = ""
        @Published var messages: [Message] = []
        @Published var isBusy: Bool = false
        @Published var updateTick: Bool = false

        private var languageModelSession: LanguageModelSession?

        init() {
            languageModelSession = LanguageModelSession()
        }

        func send(message: Message) async {
            let responseMessage = Message(text: "", isUser: false)
            messages.append(responseMessage)
            updateTick.toggle()
            do {
                guard let session = languageModelSession else { return }
                responseMessage.isUpdating = true
                let stream = session.streamResponse(to: message.text)

                isBusy = true
                messageText = ""

                for try await response in stream {
                    responseMessage.text = response
                    updateTick.toggle()
                }
                responseMessage.isUpdating = false
                isBusy = false
            } catch {
                print("Error sending message: \(String(describing: error))")
            }
        }
    }

    struct MessageView: View {
        @ObservedObject var message: Message
        var body: some View {
            HStack {
                if message.isUser {
                    Spacer()
                }
                Text(message.text.isEmpty ? " ...    " : message.text)
                    .padding()
                    .background {
                        RoundedRectangle(cornerRadius: 16)
                            .fill(message.isUser ? Color(white: 0.2) : Color(white: 0.4))
                    }
                    .overlay(alignment: .bottomTrailing) {
                        message.isUpdating ? ProgressView()
                            .padding(8)
                        : nil
                    }
                if !message.isUser {
                    Spacer()
                }
            }
            .foregroundStyle(Color.white)
        }
    }

    @StateObject private var model: Model = Model()

    var body: some View {
        VStack(alignment: .leading, spacing: 15) {
            Text("AI Assistant")
                .font(.title)
                .foregroundStyle(Color.orange)
                .padding(.horizontal, 12)

            ScrollViewReader { svr in
                List {
                    ForEach(model.messages) { msg in
                        MessageView(message: msg)
                            .id(msg.id)
                    }
                    .listRowSeparator(.hidden)
                    .listRowBackground(Color.clear)
                }
                .background(Color(white: 0.1))
                .listStyle(.plain)
                .onChange(of: model.updateTick) { _ in
                    if let m = model.messages.last {
                        withAnimation {
                            svr.scrollTo(m.id, anchor: .bottom)
                        }
                    }
                }
            }

            TextField("", text: $model.messageText, prompt: Text("Your message ...").foregroundStyle(Color.orange), axis: .vertical)
                .padding(12)
                .background(Color(.systemGray6))
                .foregroundColor(Color(white: 0.7))
                .clipShape(RoundedRectangle(cornerRadius: 16))
                .overlay(
                    RoundedRectangle(cornerRadius: 16)
                        .stroke(Color.orange, lineWidth: 1)
                )
                .padding(.horizontal, 12)

            HStack {
                Spacer()
                Button(action: {
                    send()
                }, label: {
                    Text("Send")
                        .foregroundStyle(model.isBusy ? Color.gray : Color.orange)
                        .padding(10)
                })
                .buttonStyle(.bordered)
                .disabled(model.isBusy)
            }
            .padding(.horizontal, 12)
        }
        .background(Color(white: 0.15))
        .preferredColorScheme(.dark)
    }

    private func send() {
        let userMessage = Message(text: model.messageText, isUser: true)
        model.messages.append(userMessage)
        Task {
            await model.send(message: userMessage)
        }
    }
}

@available(iOS 26.0, *)
#Preview {
    SampleChatView()
}
    
Swift Foundation Models

 

www.admadic.de | webmaster@admadic.de | Legal Notice and Trademarks | Privacy
© 2005-2007 - admaDIC | All Rights Reserved
All other trademarks and/or registered trademarks are the property of their respective owners
Last Change: Fri Sep 26 07:46:27 2025 GMT