Building an iOS App with Rust Using UniFFI

Building an iOS App with Rust Using UniFFI

Alexandre Hanot

Developing iOS applications typically involves languages like Swift or Objective-C. However, with the advent of Rust, a systems programming language known for its safety and performance, and UniFFI, an interface generator for Rust, it's now possible to create iOS apps in Rust. In this blog post, we'll guide you through the process of building a simple iOS app using Rust and UniFFI.

Prerequisites

Before we begin, ensure you have the following installed:

Step 1: Setting Up Your Rust Library

Start by creating a new Rust library.

cargo new app --lib

Add uniffi to your Cargo.toml file:

Cargo.toml
[lib]
crate_type = ["cdylib", "staticlib"]
name = "mobile"
 
[dependencies]
uniffi = { version = "0.25.3", features = [ "cli" ] }

Change your lib.rs file. Here's a basic example:

src/lib.rs
uniffi::setup_scaffolding!();
 
#[uniffi::export]
fn say_hi() -> String {
    "Hello from Rust!".to_string()
}

We need uniffi-bindgen as a CLI tool.

src/bin/uniffi-bindgen.rs
fn main() {
    uniffi::uniffi_bindgen_main()
}

Build the library:

cargo build

In target/debug, you should find the libmobile.dylib file.

Generate the bindings:

cargo run --bin uniffi-bindgen generate --library ./target/debug/libmobile.dylib --language swift --out-dir ./bindings

Step 2: Building the iOS binaries

Add these targets to Rust:

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

Build the library for Swift:

cargo build --release --target=aarch64-apple-ios-sim
cargo build --release --target=aarch64-apple-ios

You should have two binaries target/aarch64-apple-ios-sim/release/libmobile.a and target/aarch64-apple-ios/release/libmobile.a.

Step 3: Creating the XCFramework

The XCFramework will allow us to import the library with zero effort in Xcode.

First, we need to rename the file bindings/mobileFFI.modulemap to bindings/module.modulemap.

⚠️

It is important to rename the file to module.modulemap because Xcode will not be able to find the module otherwise.

Then, we can create the XCFramework:

xcodebuild -create-xcframework \
        -library ./target/aarch64-apple-ios-sim/release/libmobile.a -headers ./bindings \
        -library ./target/aarch64-apple-ios/release/libmobile.a -headers ./bindings \
        -output "ios/Mobile.xcframework"

Set 4: Import the library in Xcode

Create a new iOS app in Xcode.

Import both the XCFramework Mobile.xcframework and the Swift file bindings bindings/Mobile.swift files into your project (drag and drop should work).

You should be good to go!

HelloWorld/ContentView.swift
import SwiftUI
 
struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text(sayHi()) // <-- Here we call the Rust function
                .padding()
        }
        .padding()
    }
}

iOS App

Putting everything together

Create a script named build-ios.sh. This script automates the process of building your Rust library for various iOS targets.

build-ios.sh
#!/bin/bash
 
# Build the dylib
cargo build
 
# Generate bindings
cargo run --bin uniffi-bindgen generate --library ./target/debug/libmobile.dylib --language swift --out-dir ./bindings
 
# Add the iOS targets and build
for TARGET in \
        aarch64-apple-darwin \
        aarch64-apple-ios \
        aarch64-apple-ios-sim \
        x86_64-apple-darwin \
        x86_64-apple-ios
do
    rustup target add $TARGET
    cargo build --release --target=$TARGET
done
 
# Rename *.modulemap to module.modulemap
mv ./bindings/mobileFFI.modulemap ./bindings/module.modulemap
 
# Move the Swift file to the project
rm ./ios/TodoList/Mobile.swift
mv ./bindings/mobile.swift ./ios/TodoList/Mobile.swift
 
# Recreate XCFramework
rm -rf "ios/Mobile.xcframework"
xcodebuild -create-xcframework \
        -library ./target/aarch64-apple-ios-sim/release/libmobile.a -headers ./bindings \
        -library ./target/aarch64-apple-ios/release/libmobile.a -headers ./bindings \
        -output "ios/Mobile.xcframework"
 
# Cleanup
rm -rf bindings

See also