SwiftChat is a fully functional AI chat app template for iOS. It connects to any OpenAI-compatible API and supports streaming responses, reasoning/thinking models, web search, image attachments, voice input, and LaTeX rendering out of the box.
Use it as a starting point for your own AI-powered chat app, or as a reference for how to integrate the OpenAI Responses API into a SwiftUI project.
- Streaming responses via the OpenAI Responses API with real-time token display
- Reasoning model support with collapsible thinking boxes (o3, o4-mini, etc.)
- Web search with live search status indicators and source citations
- Image attachments with camera, photo library, and multimodal model support
- Document attachments (PDF, TXT, MD, CSV, HTML) injected as context
- Voice input with audio recording and Whisper transcription
- LaTeX and Markdown rendering with syntax-highlighted code blocks
- Chat history with sidebar navigation and swipe gestures
- Auto-generated titles for conversations using a lightweight model
- Dark mode and Light mode with adaptive theming
- iPad support with side-by-side sidebar layout
- Liquid Glass icon for iOS 26+ with legacy icon fallback
- Xcode 26+
- iOS 18+ deployment target
- An OpenAI API key (or any OpenAI-compatible API)
-
Clone the repository
git clone https://github.com/user/SwiftChat.git cd SwiftChat -
Open in Xcode
open SwiftChat.xcodeproj
-
Run the app on a simulator or device. On first launch, you'll be prompted to enter your API key.
All configuration lives in AppConfig.swift:
class AppConfig: ObservableObject {
var apiKey: String = "YOUR_API_KEY"
/// OpenAI-compatible API host
var apiHost: String = "api.openai.com"
/// Base path for the API
var apiBasePath: String = "/v1"
/// System prompt sent with every conversation
var systemPrompt: String = "You are a helpful AI assistant."
/// Additional rules appended to the system prompt
var rules: String = ""
}To use a different OpenAI-compatible API (e.g. Azure, a local model, or a proxy), change apiHost and apiBasePath.
Default models are configured in AppConfig.setupDefaultModels():
availableModels = [
ModelType(id: "gpt-4.1", displayName: "GPT-4.1", ...),
ModelType(id: "gpt-4.1-mini", displayName: "GPT-4.1 Mini", ...),
ModelType(id: "o4-mini", displayName: "o4-mini", ...),
]Add or remove models by editing this array. Set isMultimodal: true for models that accept image input.
SwiftChat/
├── Config/
│ ├── AppConfig.swift # API configuration, model definitions
│ ├── Constants.swift # App-wide constants
│ ├── SettingsManager.swift # User preferences (persisted)
│ └── Theme.swift # Design system tokens
├── Models/
│ ├── ChatModels.swift # Chat, Message, WebSearchState
│ └── AttachmentModels.swift # Attachment types (image, document)
├── ViewModels/
│ └── ChatViewModel.swift # Core logic: streaming, chat management
├── Views/
│ ├── ChatView.swift # Main container with sidebar
│ ├── ChatListView.swift # Message list (UITableView-backed)
│ ├── MessageView.swift # Individual message bubbles
│ ├── MessageInputView.swift # Input bar with attachments, mic, send
│ ├── ChatSidebar.swift # Chat history sidebar
│ ├── LaTeXMarkdownView.swift # Rich text rendering
│ └── ... # Supporting views
├── Services/
│ ├── AudioRecordingService.swift # Voice recording + transcription
│ ├── StreamingMarkdownChunker.swift # Incremental markdown parsing
│ ├── ThinkingSummaryService.swift # Thinking content summarizer
│ └── ThinkingTextChunker.swift # Thinking text paragraph splitter
├── Utilities/
│ ├── ChatQueryBuilder.swift # Builds Responses API queries
│ └── NetworkMonitor.swift # Connectivity monitoring
└── Extensions/
├── Color.swift # App color palette
└── Color+Hex.swift # Hex color initializer
ChatQueryBuilder converts your chat history into a CreateModelResponseQuery for the Responses API, handling system prompts, multimodal content, document context, and web search tools.
ChatViewModel manages the streaming loop, processing ResponseStreamEvent variants:
.outputText(.delta)for response content.reasoning(.delta)for thinking/reasoning content.webSearchCall(...)for search status updates.outputTextAnnotation(.added)for citations
StreamingMarkdownChunker breaks streaming content into semantic chunks (paragraphs, code blocks, tables, etc.) so only the active chunk re-renders during streaming.
Edit AppConfig.systemPrompt to change the AI's behavior. You can use placeholders:
{MODEL_NAME}- replaced with the current model's display name{LANGUAGE}- replaced with the user's selected language{CURRENT_DATETIME}- replaced with the current date/time{TIMEZONE}- replaced with the user's timezone
Colors are defined in Extensions/Color.swift. The Theme.swift file provides semantic design tokens. Both light and dark mode are fully supported.
Add entries to AppConfig.setupDefaultModels():
ModelType(
id: "your-model-id", // API model identifier
displayName: "Display Name", // Shown in model picker
fullName: "Full Model Name", // Used in system prompt
iconName: "openai-icon", // Asset catalog image name
isMultimodal: true // Set true if model accepts images
)| Package | Purpose |
|---|---|
| OpenAI | OpenAI API client with Responses API + streaming support (Tinfoil fork) |
| Textual | Markdown/rich text rendering for SwiftUI (Tinfoil fork) |
| SwiftMath | Native LaTeX math rendering |
All dependencies are managed via Swift Package Manager and resolve automatically when you open the project.
- iOS 18.0+
- Xcode 16+
- Swift 5.0+
This project is available under the MIT License. See the LICENSE file for details.



