Three changes that fixed pre-existing bugs the feature neither introduces nor makes materially more reachable. Each is defensible on its own and none belongs here. The one-time link picker's trailing close() popped whatever was on top rather than itself, so backing out during the reassignment dismissed the New chat screen too. The window it needs is the connection change plus the user switch - which the ordinary row tap already has, unchanged, on master. Creating a profile first is local and fast and adds almost nothing to it. Reverted, which takes ModalViewId.ACTIVE_PROFILE_PICKER and ShareListView out of the diff entirely. Same on iOS for allowsHitTesting: blocking the whole picker from t=0 instead of t=0.5s is a change to the existing switch flow, and the two else branches releasing profileSwitchStatus only matter when there is no connection - a state this feature gates itself out of. The add-profile row keeps its own disabled state, which is what the feature actually needs. changingProfile in the compose picker was the same thing again: the feature's own window is already covered by ChatModel.creatingProfileForInvitation, which is held across the reassignment, so this flag only guarded plain row taps. Dropping it also removes the try/finally that existed solely to reset it.
SimpleX Chat iOS app
This file provides guidance when working with code in this repository.
iOS App Overview
The iOS app is a SwiftUI application that interfaces with the Haskell core library via FFI. It shares the SimpleXChat framework with two extensions: Notification Service Extension (NSE) for push notifications and Share Extension (SE) for sharing content from other apps.
Build & Development
Open SimpleX.xcodeproj in Xcode. The project has five targets:
- SimpleX (iOS) - Main app (Bundle ID:
chat.simplex.app) - SimpleXChat - Framework containing FFI bridge and shared types
- SimpleX NSE - Notification Service Extension
- SimpleX SE - Share Extension
- Tests iOS - UI tests
Build and run via Xcode (Product > Build/Run). Tests run via Product > Test or:
xcodebuild test -scheme "SimpleX (iOS)" -destination 'platform=iOS Simulator,name=iPhone 15'
Deployment target: iOS 15.0+, Swift 5.0.
Architecture
Haskell Core Integration
The app calls the Haskell core library through C FFI defined in SimpleXChat/SimpleX.h:
chat_migrate_init_key()- Initialize/migrate databasechat_send_cmd_retry()- Send command to chat controllerchat_recv_msg_wait()- Receive messages from controller
Swift wrappers in SimpleXChat/API.swift:
chatMigrateInit()- Initialize chat controllersendSimpleXCmd<R>()- Send typed commands and parse responsesrecvSimpleXMsg<R>()- Receive typed messages
Haskell runtime initialization (SimpleXChat/hs_init.c) uses different memory configurations:
- Main app: 64MB heap
- NSE: 512KB heap (minimal footprint for background processing)
- SE: 1MB heap
Pre-compiled Haskell libraries are in Libraries/{ios,mac,sim}/.
State Management
- ChatModel (
Shared/Model/ChatModel.swift) - Main singletonObservableObjectfor app-wide state (chat list, active chat, users) - ItemsModel - Manages chat items within a selected chat (similar to Kotlin's ChatsContext)
- AppTheme - Theme management and customization
App Structure
Entry point: Shared/SimpleXApp.swift
Key directories in Shared/:
Model/- Data models and API layer (ChatModel.swift,SimpleXAPI.swift)Views/- SwiftUI views organized by feature:ChatList/- Chat list and user pickerChat/- Message display and compositionCall/- VoIP call UIUserSettings/- App settingsLocalAuth/- Passcode and biometric authenticationDatabase/- Database initialization and migration
Shared Data Between Targets
All three targets share data via App Group (group.chat.simplex.app):
SimpleXChat/AppGroup.swift- GroupDefaults wrapper for typed shared preferences- Keychain for sensitive data:
kcDatabasePassword,kcAppPassword,kcSelfDestructPassword
Key Types
Types are defined in SimpleXChat/:
ChatTypes.swift- User, Chat, Message, Group typesAPITypes.swift- API request/response types
Commands follow ChatCmdProtocol (has cmdString property), sent as JSON through FFI.
Localization
31 languages supported. Localization files in SimpleX Localizations/.
Workflow:
Product > Export Localizations- Export XLIFF filesProduct > Import Localizations- Import updated translations
SimpleX Assets
The app includes optional assets behind the SIMPLEX_ASSETS Swift compilation flag. Without setup, the app builds normally without them.
Setup
Create Local.xcconfig (gitignored) in the apps/ios/ directory:
SIMPLEX_ASSETS_DIR = /path/to/assets
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) SIMPLEX_ASSETS
The copy script (scripts/ios/copy-assets.sh) runs as a build phase on each build but exits immediately if SIMPLEX_ASSETS is not set.
Updating assets
When source images change, regenerate resized images (requires ImageMagick):
cd path/to/assets && ./resize.sh
Background Capabilities
Configured in Info.plist:
- Background modes: audio, fetch, remote-notification, voip
- URL scheme:
simplex://for deep linking - BGTaskScheduler:
chat.simplex.app.receive