iOS Quick Start Guide
This guide will walk you through setting up SQLite in Swift to load CloudsSync extensions.
1. Create a New Swift Project
- Open Xcode
- Create a new project
- Select Multiplatform → App
2. Download and Add CloudSync Framework
-
Download the latest version of
cloudsync-apple-xcframework
from here -
In Xcode, click on your project name in the source tree (top left with the Xcode logo)
-
In the new tab that opens, navigate to the left column under the Targets section and click on the first target
-
You should now be in the General tab. Scroll down to “Frameworks, Libraries, and Embedded Content”
-
Click the + button → Add Other… → Add Files…
-
Select the downloaded
CloudSync.xcframework
folder -
Switch to the Build Phases tab and verify that
CloudSync.xcframework
appears under Embedded Frameworks
3. Handle Security Permissions (macOS)
When you return to the main ContentView file, you may encounter an Apple security error:
- Click Done when the security dialog appears
- Open System Settings → Privacy & Security
- Scroll to the bottom and find the message “Mac blocked CloudSync”
- Click Allow Anyway
- Close and reopen ContentView in Xcode
- The same error should appear but now with a third button Open Anyway - click it
- If errors persist, try reopening and closing ContentView multiple times or repeat the security steps above
4. Set Up SQLite with Extension Loading
You need a version of SQLite that supports loading extensions. You have two options:
Option A: Download SQLite Amalgamation (Recommended)
- Download the amalgamation from here
- Create a new folder called SQLite in your Swift project in Xcode
- Copy
sqlite3.c
andsqlite3.h
into this folder by dragging them in - Enable all targets and confirm
Option B: Use CocoaPods
5. Configure Objective-C Bridging Header
- When you add the SQLite files, a popup will appear asking “Would you like to configure an Objective-C bridging header?”
- Click Create Bridging Header
- In the newly created bridging header file, import the SQLite headers:
#import "sqlite3.h"
6. Test the Setup
To verify that the extension loads correctly in your Swift project, replace your ContentView.swift content with this test code:
import SwiftUI
struct ContentView: View {
@State private var statusLines: [String] = []
private var statusText: String { statusLines.joined(separator: "\n") }
var body: some View {
VStack(spacing: 12) {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
Divider()
Text("Status")
.font(.headline)
ScrollView {
Text(statusText.isEmpty ? "No status yet." : statusText)
.font(.system(.footnote, design: .monospaced))
.frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled)
.padding(.vertical, 4)
}
.frame(maxHeight: 260)
}
.padding()
.task {
log("Starting...")
var db: OpaquePointer?
// Open an in-memory database just for demonstrating status updates.
// Replace with your own URL/path if needed.
var rc = sqlite3_open(":memory:", &db)
if rc != SQLITE_OK {
let msg = db.flatMap { sqlite3_errmsg($0) }.map { String(cString: $0) } ?? "Unknown error"
log("sqlite3_open failed (\(rc)): \(msg)")
if let db { sqlite3_close(db) }
return
}
log("Database opened.")
// Enable loadable extensions
rc = sqlite3_enable_load_extension(db, 1)
log("sqlite3_enable_load_extension rc=\(rc)")
// Locate the extension in the bundle (adjust as needed)
let vendorBundle = Bundle(identifier: "ai.sqlite.cloudsync")
let candidatePaths: [String?] = [
vendorBundle?.path(forResource: "CloudSync", ofType: "dylib"),
vendorBundle?.path(forResource: "CloudSync", ofType: ""),
Bundle.main.path(forResource: "CloudSync", ofType: "dylib"),
Bundle.main.path(forResource: "CloudSync", ofType: "")
]
let cloudsyncPath = candidatePaths.compactMap { $0 }.first
log("cloudsyncPath: \(cloudsyncPath ?? "Not found")")
var loaded = false
if let path = cloudsyncPath {
var errMsg: UnsafeMutablePointer<Int8>? = nil
rc = sqlite3_load_extension(db, path, nil, &errMsg)
if rc != SQLITE_OK {
let message = errMsg.map { String(cString: $0) } ?? String(cString: sqlite3_errmsg(db))
if let e = errMsg { sqlite3_free(e) }
log("sqlite3_load_extension failed rc=\(rc): \(message)")
} else {
loaded = true
log("sqlite3_load_extension succeeded.")
}
// Optionally disable further extension loading
_ = sqlite3_enable_load_extension(db, 0)
} else {
log("Skipping load: extension file not found in bundle.")
}
// Run SELECT cloudsync_version() and log the result
if loaded {
let sql = "SELECT cloudsync_version()"
log("Running query: \(sql)")
var stmt: OpaquePointer?
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nil)
if rc != SQLITE_OK {
let msg = String(cString: sqlite3_errmsg(db))
log("sqlite3_prepare_v2 failed (\(rc)): \(msg)")
} else {
defer { sqlite3_finalize(stmt) }
rc = sqlite3_step(stmt)
if rc == SQLITE_ROW {
if let cstr = sqlite3_column_text(stmt, 0) {
let version = String(cString: cstr)
log("cloudsync_version(): \(version)")
} else {
log("cloudsync_version(): (null)")
}
} else if rc == SQLITE_DONE {
log("cloudsync_version() returned no rows")
} else {
let msg = String(cString: sqlite3_errmsg(db))
log("sqlite3_step failed (\(rc)): \(msg)")
}
}
} else {
log("Extension not loaded; skipping cloudsync_version() query.")
}
if let db { sqlite3_close(db) }
log("Done.")
}
}
@MainActor
private func log(_ line: String) {
statusLines.append(line)
}
}
#Preview {
ContentView()
}
Expected Results
When you run the test app, you should see status messages in the UI indicating:
- Database connection success
- Extension loading status
- CloudSync version information (if successfully loaded)
This confirms that CloudSync is properly integrated and functional in your Swift project.
Usage Example
Check out the Swift Multiplatform app for a complete implementation of using the SQLite CloudSync extension to sync data across devices.