Tutorial: Swift iOS App
This tutorial walks through building a Rust library, generating Swift bindings with WeaveFFI, and integrating everything into an Xcode iOS project.
Prerequisites
- Rust toolchain (stable channel)
- Xcode 15+ with iOS SDK
- WeaveFFI CLI installed (
cargo install weaveffi-cli) - iOS Rust targets:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
1) Define your API
Create a file called greeter.yml:
version: "0.1.0"
modules:
- name: greeter
structs:
- name: Greeting
fields:
- { name: message, type: string }
- { name: lang, type: string }
functions:
- name: hello
params:
- { name: name, type: string }
return: string
- name: greeting
params:
- { name: name, type: string }
- { name: lang, type: string }
return: Greeting
2) Generate bindings
weaveffi generate greeter.yml -o generated --scaffold
This produces:
generated/
├── c/
│ └── weaveffi.h
├── swift/
│ ├── Package.swift
│ └── Sources/
│ ├── CWeaveFFI/
│ │ └── module.modulemap
│ └── WeaveFFI/
│ └── WeaveFFI.swift
└── scaffold.rs
3) Create the Rust library
Create a new Cargo project for the native library:
cargo init --lib mygreeter
mygreeter/Cargo.toml:
[package]
name = "mygreeter"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib", "cdylib"]
[dependencies]
weaveffi-abi = { version = "0.1" }
Use staticlib for iOS — Xcode links static libraries into the app
bundle. cdylib is included for desktop testing.
mygreeter/src/lib.rs:
#![allow(unused)]
#![allow(unsafe_code)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
fn main() {
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use weaveffi_abi::{self as abi, weaveffi_error};
#[no_mangle]
pub extern "C" fn weaveffi_greeter_hello(
name_ptr: *const c_char,
_name_len: usize,
out_err: *mut weaveffi_error,
) -> *const c_char {
abi::error_set_ok(out_err);
let name = unsafe { CStr::from_ptr(name_ptr) }.to_str().unwrap_or("world");
let msg = format!("Hello, {name}!");
CString::new(msg).unwrap().into_raw() as *const c_char
}
#[no_mangle]
pub extern "C" fn weaveffi_free_string(ptr: *const c_char) {
abi::free_string(ptr);
}
#[no_mangle]
pub extern "C" fn weaveffi_free_bytes(ptr: *mut u8, len: usize) {
abi::free_bytes(ptr, len);
}
#[no_mangle]
pub extern "C" fn weaveffi_error_clear(err: *mut weaveffi_error) {
abi::error_clear(err);
}
}
Fill in the remaining functions (weaveffi_greeter_greeting,
weaveffi_greeter_Greeting_destroy, getters, etc.) using the generated
scaffold.rs as a guide.
4) Build for iOS
Build the static library for each iOS target:
# Physical devices (arm64)
cargo build -p mygreeter --target aarch64-apple-ios --release
# Simulator (arm64 Apple Silicon)
cargo build -p mygreeter --target aarch64-apple-ios-sim --release
# Simulator (x86_64 Intel Mac)
cargo build -p mygreeter --target x86_64-apple-ios --release
Create a universal simulator library with lipo:
mkdir -p target/universal-ios-sim/release
lipo -create \
target/aarch64-apple-ios-sim/release/libmygreeter.a \
target/x86_64-apple-ios/release/libmygreeter.a \
-output target/universal-ios-sim/release/libmygreeter.a
Optionally, create an XCFramework that bundles both device and simulator slices:
xcodebuild -create-xcframework \
-library target/aarch64-apple-ios/release/libmygreeter.a \
-headers generated/c/ \
-library target/universal-ios-sim/release/libmygreeter.a \
-headers generated/c/ \
-output MyGreeter.xcframework
5) Set up the Xcode project
-
Create a new iOS App in Xcode (SwiftUI or UIKit).
-
Add the static library. Drag
MyGreeter.xcframework(or the.afile for a single architecture) into your project navigator. Ensure it appears under Build Phases > Link Binary With Libraries. -
Add the generated Swift package. In Xcode, go to File > Add Package Dependencies > Add Local… and select
generated/swift/. This adds theCWeaveFFI(C module map) andWeaveFFI(Swift wrapper) targets. -
Set the Header Search Path. Under Build Settings > Header Search Paths, add the path to
generated/c/(e.g.$(SRCROOT)/../generated/c). This lets the module map findweaveffi.h. -
Set the Library Search Path. Under Build Settings > Library Search Paths, add the path to the Rust static library (e.g.
$(SRCROOT)/../target/aarch64-apple-ios/releasefor device builds). -
Add a bridging dependency. In your app target’s Build Phases > Dependencies, ensure
WeaveFFIis listed.
6) Call from Swift
import WeaveFFI
struct ContentView: View {
@State private var greeting = ""
var body: some View {
VStack {
Text(greeting)
Button("Greet") {
do {
greeting = try Greeter.hello("Swift")
} catch {
greeting = "Error: \(error)"
}
}
}
.padding()
}
}
The generated WeaveFFI module exposes:
Greeter.hello(_:)— returns aStringGreeter.greeting(_:_:)— returns aGreetingobject with.messageand.langpropertiesGreeting— a class wrapping the opaque Rust pointer, with automatic cleanup ondeinit
7) Build and run
Select an iOS Simulator target in Xcode and press Cmd+R. The app should display “Hello, Swift!” when you tap the button.
For a physical device, ensure you built for aarch64-apple-ios and that
the correct library search path is set.
Troubleshooting
| Problem | Solution |
|---|---|
Undefined symbols for architecture arm64 | Check that the static library is linked and the library search path is correct. |
Module 'CWeaveFFI' not found | Ensure the header search path points to generated/c/. |
No such module 'WeaveFFI' | Add the generated/swift/ local package to your Xcode project. |
| Simulator crash on Intel Mac | Build with x86_64-apple-ios and create a universal binary with lipo. |
Next steps
- See the Swift generator reference for type mapping details.
- Read the Memory Ownership guide to understand struct lifecycle management.
- Explore the Calculator tutorial for a simpler end-to-end walkthrough.