Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Swift iOS App

Goal

Build a small Rust greeter library, generate Swift bindings with WeaveFFI, and call them from a SwiftUI iOS app running in the simulator.

Prerequisites

  • Rust toolchain (stable channel).

  • Xcode 15 or later with the iOS SDK installed.

  • WeaveFFI CLI (cargo install weaveffi-cli).

  • iOS Rust targets:

    rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
    

Step-by-step

1. Author the IDL

Save as greeter.yml:

version: "0.3.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

You should see, among other targets:

generated/
├── c/
│   └── weaveffi.h
├── swift/
│   ├── Package.swift
│   └── Sources/
│       ├── CWeaveFFI/
│       │   └── module.modulemap
│       └── WeaveFFI/
│           └── WeaveFFI.swift
└── scaffold.rs

3. Implement the Rust 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" }

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
}

// Emit the WeaveFFI C ABI runtime symbols (free_string, free_bytes,
// error_clear, cancel_token_*) — one line per cdylib.
abi::export_runtime!();
}

Use scaffold.rs as the template for the rest of the API (weaveffi_greeter_greeting, the Greeting lifecycle, getters, …).

4. Build for iOS targets

cargo build -p mygreeter --target aarch64-apple-ios --release
cargo build -p mygreeter --target aarch64-apple-ios-sim --release
cargo build -p mygreeter --target x86_64-apple-ios --release

Combine the simulator architectures with lipo and bundle everything in an XCFramework so Xcode can pick the right slice automatically:

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

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. Wire it into Xcode

  1. Create a new iOS App in Xcode (SwiftUI or UIKit).
  2. Drag MyGreeter.xcframework into the project navigator. Confirm it appears under Build Phases > Link Binary With Libraries.
  3. File > Add Package Dependencies > Add Local… and pick generated/swift/. The package contributes the CWeaveFFI and WeaveFFI targets.
  4. Build Settings > Header Search Paths: add the path to generated/c/ (e.g. $(SRCROOT)/../generated/c).
  5. Build Settings > Library Search Paths: add the path to the matching Rust static library ($(SRCROOT)/../target/aarch64-apple-ios/release for device builds).
  6. Build Phases > Dependencies: ensure WeaveFFI is listed.

6. Call from Swift

import SwiftUI
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 String.
  • Greeter.greeting(_:_:) — returns a Greeting instance with .message and .lang properties; deinit calls the Rust destructor automatically.
  • Greeting — the wrapper class around the opaque Rust pointer.

Verification

  • Select an iOS Simulator target and press Cmd+R.

  • Tap Greet in the running app; the label changes to Hello, Swift!.

  • Re-run on a physical device after building for aarch64-apple-ios to confirm the device path also works.

  • Common error mappings:

    SymptomLikely cause
    Undefined symbols for architecture arm64Static library not linked or the search path is wrong.
    Module 'CWeaveFFI' not foundHeader search path does not point at generated/c/.
    No such module 'WeaveFFI'Local Swift package not added under Add Package Dependencies > Add Local….
    Crash when running on Intel simulatorBuild for x86_64-apple-ios and combine with lipo.

Cleanup

rm -rf generated/ MyGreeter.xcframework
cargo clean -p mygreeter

Remove the MyGreeter.xcframework reference from the Xcode project and undo the Header Search Paths / Library Search Paths edits.

Next steps