The core initializes asynchronously at launch, so for a moment the transfer lists look empty and the app feels stalled. Show a full-window overlay (centered spinner + "Starting…") as the top layer of the root stack while coreState.isInitialized is false; it fades out once the core is ready.
VniDrop — native Apple app (iOS / iPadOS / macOS)
A native SwiftUI app for Apple platforms, sharing the existing Rust transfer core
(crates/vnidrop) through UniFFI-generated Swift bindings. The Rust crate is not
modified; the Kotlin/Compose app layer is ported to Swift and mirrors the Compose
UI screen-for-screen. Android, Windows, and Linux continue to use shared/ + Compose.
Layout
apple/
scripts/build-core.sh # builds the Rust core + generates Swift bindings + xcframework
VnidropCore/ # local SwiftPM package: xcframework + generated Vnidrop.swift
VniDrop/ # SwiftUI app sources
App/ # entry point, object graph, root view, environment
Core/ # repository, models, preferences, notifications, progress
Features/Send|Receive|Approvals|Settings/
UI/Theme|Components|Navigation|Feedback|Shell/
Platform/ # pickers, QR, NFC, share/export, per-OS file services
Resources/ # Localizable.xcstrings, Info.plist, entitlements, assets
Tests/ # XCTest (ported progress-derivation assertions)
Package.swift # builds VniDrop/ as a library for CLI build/test
project.yml # XcodeGen spec for the iOS/macOS app target
Build & run
Prerequisites: Xcode, Rust with the Apple targets
(aarch64-apple-ios, aarch64-apple-ios-sim, x86_64-apple-ios,
aarch64-apple-darwin), and xcodegen (brew install xcodegen).
# From the repository root:
make apple-core # Rust core, Swift bindings, and XCFramework
make apple-project # generate apple/VniDrop.xcodeproj
make open-apple-project # generate and open the project in Xcode
make build-apple-macos # unsigned macOS build
make open-apple # build and launch the macOS app
make build-apple-ios # unsigned iOS simulator build
make check-apple # iOS simulator tests
Use APPLE_PROFILE=release to request a release Rust core, or set
APPLE_DESTINATION to override the automatically selected iOS simulator.
Code signing is disabled for the app and test targets; local and CI builds do
not require an Apple Development team or provisioning profile. Make builds can
opt in with APPLE_CODE_SIGNING=YES. For signed builds from Xcode, create the
ignored apple/Local.xcconfig and override the signing settings there, including
the development team.
Command-line typecheck & tests
Package.swift builds the same sources as a library (minus the @main entry),
so the shared logic can be checked and unit-tested without Xcode:
cd apple
swift build # macOS
swift test # runs Tests/ (ported progress-derivation assertions)
# iOS typecheck:
swift build --triple arm64-apple-ios16.0-simulator --sdk "$(xcrun --sdk iphonesimulator --show-sdk-path)"
Generated / ignored artifacts
build-core.sh produces build outputs that are gitignored (see apple/.gitignore):
VnidropCore/vnidrop.xcframework/, VnidropCore/Sources/VnidropCore/Vnidrop.swift,
and .build-core/. A clean checkout must run build-core.sh before generating or
opening the Xcode project. VniDrop.xcodeproj itself is generated by XcodeGen from
project.yml and does not need to be committed.
Build profile note
The default is debug. The workspace [profile.release] uses thin LTO, which the
current macOS toolchain miscompiles into corrupt host proc-macro dylibs
("mis-aligned LINKEDIT string pool"). build-core.sh sets
CARGO_PROFILE_DEV_STRIP=none (matching the existing Gobley Xcode run-script) so
debug builds succeed. For a release core, disable LTO for proc-macros/build
scripts (e.g. add a [profile.release.build-override] lto = false locally) — the
Rust crate itself is never changed.
System frameworks
The Rust core (iroh network stack) links SystemConfiguration, Security, and
libresolv. These are declared in both Package.swift (for CLI build/test) and
project.yml (for the app target).
Parity & scope
Screens mirror the Compose UI in shared/. Two deliberate simplifications:
- Empty-state Lottie animations are rendered as SF Symbols (no
lottie-iosdependency); swap inlottie-iosif exact-parity animation is required. - The full diagnostics/telemetry stack (
diagnostics/*) is stubbed behindBugReportService/DiagnosticsBuildConfigand lands in a later phase; the UI hides the diagnostics toggle when not compiled in.