Building an iOS App with Rust Using UniFFI
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:
- Rust: Install it from the official Rust website.
- Xcode: Available on the Mac App Store.
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:
[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:
uniffi::setup_scaffolding!();
#[uniffi::export]
fn say_hi() -> String {
"Hello from Rust!".to_string()
}
We need uniffi-bindgen
as a CLI tool.
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!
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()
}
}
Putting everything together
Create a script named build-ios.sh
. This script automates the process of building your Rust library for various iOS targets.
#!/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