Mirrors the Android/desktop fixes where iOS has the same defects, and fixes the ones that are specific to SwiftUI. Still not compiled - no Swift toolchain here, same status as the rest of the iOS port in this branch. Swallowed alert. The one-time link picker reported "the core ignored keepActiveUser" by setting its own `alert` @State, consumed by .alert(item:) on the very view that is presenting the create-profile sheet. UIKit refuses an alert on a controller that already has a presentation, so it was never shown: the active profile silently switched, the connection stayed where it was, and the user was left looking at an unchanged form. Uses the global showAlert, which targets the top view controller, as the compose picker already does. Stale picker list. The compose picker fills its own `users` in onAppear and never again, so the profile just created was missing from the list whenever the reassignment failed - tapping "Add profile" again then hit "Duplicate display name!" - and the frame height, which is computed from that count, was one row short. The local from listUsers also shadowed the property, which is what hid this. Resync failure reported as a creation failure. changeActiveUserAsync_ throws, and on the "core ignored the flag" path that propagated into the form's catch, which says "Error creating profile!" although the profile had been created. On the compose picker that path also left chatId pointing at a prepared chat that had stayed with the previous profile, i.e. at a chat absent from the list, which renders blank. Sheet owner destroyed mid-dismissal. The compose picker's sheet was attached inside profilePicker(), which is swapped out for currentSelection() the moment listExpanded flips - which changeProfile does while the sheet is still dismissing, and which an incoming event can do at any time via profileChangeProhibited. Moved to the Group in viewBody(), which survives both; stacking sheets is supported from iOS 14.5 and the app targets 15. Dismissal during creation. The submit runs in an unstructured Task that SwiftUI does not cancel, so a swipe-to-dismiss still created the profile and switched to it while every state write, including the one clearing the in-flight flag, landed on a view that was gone. Both sheets now block interactive dismissal while creating. Latched picker. If apiChangeConnectionUser returns nothing rather than throwing, profileSwitchStatus was never reset, leaving the picker dimmed behind a spinner with hit testing off. Pre-existing, but the new row routes through the same handler. Re-tappable row. switchingProfileByTimeout only latches half a second after the switch starts, and creatingProfile is cleared as soon as the switch is handed over, so the row was live in between.
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