1- # Load Extension Guide for Swift
1+ # Swift Multiplatform App Example
22
3- This guide will walk you through setting up SQLite in Swift to load SQLiteAI extensions .
3+ A simple Swift App QuickStart with the SQLite CloudSync extension loading. Now you can build cross-platform apps that sync data seamlessly across devices .
44
5- ## 1. Create a New Swift Project
5+ ## 🚀 Quick Start
66
7- 1 . Open Xcode
8- 2 . Create a new project
9- 3 . Select ** Multiplatform** → ** App**
7+ 1 . Open Xcode project
108
11- ## 2. Download and Add CloudSync Framework
12-
13- 1 . Download the latest version of ` cloudsync-apple-xcframework ` from:
9+ 2 . Download the latest version of ` cloudsync-apple-xcframework ` from:
1410 https://github.com/sqliteai/sqlite-sync/releases
1511
16- 2 . In Xcode, click on your project name in the source tree (top left with the Xcode logo)
12+ 3 . In Xcode, click on your project name in the source tree (top left with the Xcode logo)
1713
18- 3 . In the new tab that opens, navigate to the left column under the ** Targets** section and click on the first target
14+ 4 . In the new tab that opens, navigate to the left column under the ** Targets** section and click on the first target
1915
20- 4 . You should now be in the ** General** tab. Scroll down to ** "Frameworks, Libraries, and Embedded Content"**
16+ 5 . You should now be in the ** General** tab. Scroll down to ** "Frameworks, Libraries, and Embedded Content"**
2117
22- 5 . Click the ** +** button → ** Add Other...** → ** Add Files...**
18+ 6 . Click the ** +** button → ** Add Other...** → ** Add Files...**
2319
24- 6 . Select the downloaded ` CloudSync.xcframework ` folder
20+ 7 . Select the downloaded ` CloudSync.xcframework ` folder
2521
26- 7 . Switch to the ** Build Phases** tab and verify that ` CloudSync.xcframework ` appears under ** Embedded Frameworks**
22+ 8 . Switch to the ** Build Phases** tab and verify that ` CloudSync.xcframework ` appears under ** Embedded Frameworks**
2723
28- ## 3. Handle Security Permissions (macOS)
24+ ## Handle Security Permissions (macOS)
2925
3026When you return to the main ContentView file, you may encounter an Apple security error:
3127
@@ -37,161 +33,9 @@ When you return to the main ContentView file, you may encounter an Apple securit
37336 . The same error should appear but now with a third button ** Open Anyway** - click it
38347 . If errors persist, try reopening and closing ContentView multiple times or repeat the security steps above
3935
40- ## 4. Set Up SQLite with Extension Loading
41-
42- You need a version of SQLite that supports loading extensions. You have two options:
43-
44- ### Option A: Download SQLite Amalgamation (Recommended)
45- 1 . Download the amalgamation from: https://sqlite.org/download.html
46- 2 . Create a new folder called ** SQLite** in your Swift project in Xcode
47- 3 . Copy ` sqlite3.c ` and ` sqlite3.h ` into this folder by dragging them in
48- 4 . Enable all targets and confirm
49-
50- ### Option B: Use CocoaPods
51-
52- ## 5. Configure Objective-C Bridging Header
53-
54- 1 . When you add the SQLite files, a popup will appear asking ** "Would you like to configure an Objective-C bridging header?"**
55- 2 . Click ** Create Bridging Header**
56- 3 . In the newly created bridging header file, import the SQLite headers:
57- ``` objc
58- #import " sqlite3.h"
59- ```
60-
61- ## 6. Test the Setup
62-
63- To verify that the extension loads correctly in your Swift project, replace your ContentView.swift content with this test code:
64-
65- ``` swift
66- //
67- // ContentView.swift
68- // swift-multiplatform-app
69- //
70- // Created by Gioele Cantoni on 18/08/25.
71- //
72-
73- import SwiftUI
74-
75- struct ContentView : View {
76- @State private var statusLines: [String ] = []
77- private var statusText: String { statusLines.joined (separator : " \n " ) }
78-
79- var body: some View {
80- VStack (spacing : 12 ) {
81- Image (systemName : " globe" )
82- .imageScale (.large )
83- .foregroundStyle (.tint )
84- Text (" Hello, world!" )
85-
86- Divider ()
87-
88- Text (" Status" )
89- .font (.headline )
90-
91- ScrollView {
92- Text (statusText.isEmpty ? " No status yet." : statusText)
93- .font (.system (.footnote , design : .monospaced ))
94- .frame (maxWidth : .infinity , alignment : .leading )
95- .textSelection (.enabled )
96- .padding (.vertical , 4 )
97- }
98- .frame (maxHeight : 260 )
99- }
100- .padding ()
101- .task {
102- log (" Starting..." )
103- var db: OpaquePointer ?
104-
105- // Open an in-memory database just for demonstrating status updates.
106- // Replace with your own URL/path if needed.
107- var rc = sqlite3_open (" :memory:" , & db)
108- if rc != SQLITE_OK {
109- let msg = db.flatMap { sqlite3_errmsg ($0 ) }.map { String (cString : $0 ) } ?? " Unknown error"
110- log (" sqlite3_open failed (\( rc ) ): \( msg ) " )
111- if let db { sqlite3_close (db) }
112- return
113- }
114- log (" Database opened." )
115-
116- // Enable loadable extensions
117- rc = sqlite3_enable_load_extension (db, 1 )
118- log (" sqlite3_enable_load_extension rc=\( rc ) " )
119-
120- // Locate the extension in the bundle (adjust as needed)
121- let vendorBundle = Bundle (identifier : " ai.sqlite.cloudsync" )
122- let candidatePaths: [String ? ] = [
123- vendorBundle? .path (forResource : " CloudSync" , ofType : " dylib" ),
124- vendorBundle? .path (forResource : " CloudSync" , ofType : " " ),
125- Bundle.main .path (forResource : " CloudSync" , ofType : " dylib" ),
126- Bundle.main .path (forResource : " CloudSync" , ofType : " " )
127- ]
128- let cloudsyncPath = candidatePaths.compactMap { $0 }.first
129- log (" cloudsyncPath: \( cloudsyncPath ?? " Not found" ) " )
130-
131- var loaded = false
132- if let path = cloudsyncPath {
133- var errMsg: UnsafeMutablePointer <Int8 >? = nil
134- rc = sqlite3_load_extension (db, path, nil , & errMsg)
135- if rc != SQLITE_OK {
136- let message = errMsg.map { String (cString : $0 ) } ?? String (cString : sqlite3_errmsg (db))
137- if let e = errMsg { sqlite3_free (e) }
138- log (" sqlite3_load_extension failed rc=\( rc ) : \( message ) " )
139- } else {
140- loaded = true
141- log (" sqlite3_load_extension succeeded." )
142- }
143-
144- // Optionally disable further extension loading
145- _ = sqlite3_enable_load_extension (db, 0 )
146- } else {
147- log (" Skipping load: extension file not found in bundle." )
148- }
149-
150- // Run SELECT cloudsync_version() and log the result
151- if loaded {
152- let sql = " SELECT cloudsync_version()"
153- log (" Running query: \( sql ) " )
154- var stmt: OpaquePointer ?
155- rc = sqlite3_prepare_v2 (db, sql, -1 , & stmt, nil )
156- if rc != SQLITE_OK {
157- let msg = String (cString : sqlite3_errmsg (db))
158- log (" sqlite3_prepare_v2 failed (\( rc ) ): \( msg ) " )
159- } else {
160- defer { sqlite3_finalize (stmt) }
161- rc = sqlite3_step (stmt)
162- if rc == SQLITE_ROW {
163- if let cstr = sqlite3_column_text (stmt, 0 ) {
164- let version = String (cString : cstr)
165- log (" cloudsync_version(): \( version ) " )
166- } else {
167- log (" cloudsync_version(): (null)" )
168- }
169- } else if rc == SQLITE_DONE {
170- log (" cloudsync_version() returned no rows" )
171- } else {
172- let msg = String (cString : sqlite3_errmsg (db))
173- log (" sqlite3_step failed (\( rc ) ): \( msg ) " )
174- }
175- }
176- } else {
177- log (" Extension not loaded; skipping cloudsync_version() query." )
178- }
179-
180- if let db { sqlite3_close (db) }
181- log (" Done." )
182- }
183- }
184-
185- @MainActor
186- private func log (_ line : String ) {
187- statusLines.append (line)
188- }
189- }
190-
191- #Preview {
192- ContentView ()
193- }
194- ```
36+ ## Test the Setup
37+
38+ To verify that the extension loads correctly in your Swift project, run it for iOS or macOS.
19539
19640## Expected Results
19741
0 commit comments