Skip to content

Commit 3cc90d7

Browse files
committed
Initial implementation of Swift Multiplatform App Example
1 parent bbfbdc2 commit 3cc90d7

File tree

17 files changed

+278023
-0
lines changed

17 files changed

+278023
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## User settings
6+
xcuserdata/
7+
8+
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9+
*.xcscmblueprint
10+
*.xccheckout
11+
12+
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13+
build/
14+
DerivedData/
15+
*.moved-aside
16+
*.pbxuser
17+
!default.pbxuser
18+
*.mode1v3
19+
!default.mode1v3
20+
*.mode2v3
21+
!default.mode2v3
22+
*.perspectivev3
23+
!default.perspectivev3
24+
25+
## Obj-C/Swift specific
26+
*.hmap
27+
28+
## App packaging
29+
*.ipa
30+
*.dSYM.zip
31+
*.dSYM
32+
33+
## Playgrounds
34+
timeline.xctimeline
35+
playground.xcworkspace
36+
37+
# Swift Package Manager
38+
#
39+
# Add this line if you want to avoid checking in source DropBox:
40+
# Packages/
41+
# Package.pins
42+
# Package.resolved
43+
# *.xcodeproj
44+
#
45+
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46+
# hence it is not needed unless you have added a package configuration file to your project
47+
.swiftpm/
48+
49+
.build/
50+
51+
# CocoaPods
52+
#
53+
# We recommend against adding the Pods directory to your .gitignore. However
54+
# you should judge for yourself, the pros and cons are mentioned at:
55+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56+
#
57+
# Pods/
58+
#
59+
# Add this line if you want to avoid checking in source CocoaPods lock file:
60+
# Podfile.lock
61+
62+
# Carthage
63+
#
64+
# Add this line if you want to avoid checking in source Carthage dependencies:
65+
# Carthage/Checkouts
66+
67+
Carthage/Build/
68+
69+
# Accio dependency management
70+
Dependencies/
71+
.accio/
72+
73+
# fastlane
74+
#
75+
# It is recommended to not store the screenshots in the git repo.
76+
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
77+
# For more information about the recommended setup visit:
78+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
79+
80+
fastlane/report.xml
81+
fastlane/Preview.html
82+
fastlane/screenshots/**/*.png
83+
fastlane/test_output
84+
85+
# Code Injection
86+
#
87+
# After new code Injection tools there's a generated folder /iOSInjectionProject
88+
# https://github.com/johnno1962/injectionforxcode
89+
90+
iOSInjectionProject/
91+
92+
# General
93+
.DS_Store
94+
.AppleDouble
95+
.LSOverride
96+
97+
# Icon must end with two \r
98+
Icon
99+
100+
# Thumbnails
101+
._*
102+
103+
# Files that might appear in the root of a volume
104+
.DocumentRevisions-V100
105+
.fseventsd
106+
.Spotlight-V100
107+
.TemporaryItems
108+
.Trashes
109+
.VolumeIcon.icns
110+
.com.apple.timemachine.donotpresent
111+
112+
# Directories potentially created on remote AFP share
113+
.AppleDB
114+
.AppleDesktop
115+
Network Trash Folder
116+
Temporary Items
117+
.apdisk
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Load Extension Guide for Swift
2+
3+
This guide will walk you through setting up SQLite in Swift to load SQLiteAI extensions.
4+
5+
## 1. Create a New Swift Project
6+
7+
1. Open Xcode
8+
2. Create a new project
9+
3. Select **Multiplatform****App**
10+
11+
## 2. Download and Add CloudSync Framework
12+
13+
1. Download the latest version of `cloudsync-apple-xcframework` from:
14+
https://github.com/sqliteai/sqlite-sync/releases
15+
16+
2. In Xcode, click on your project name in the source tree (top left with the Xcode logo)
17+
18+
3. In the new tab that opens, navigate to the left column under the **Targets** section and click on the first target
19+
20+
4. You should now be in the **General** tab. Scroll down to **"Frameworks, Libraries, and Embedded Content"**
21+
22+
5. Click the **+** button → **Add Other...****Add Files...**
23+
24+
6. Select the downloaded `CloudSync.xcframework` folder
25+
26+
7. Switch to the **Build Phases** tab and verify that `CloudSync.xcframework` appears under **Embedded Frameworks**
27+
28+
## 3. Handle Security Permissions (macOS)
29+
30+
When you return to the main ContentView file, you may encounter an Apple security error:
31+
32+
1. Click **Done** when the security dialog appears
33+
2. Open **System Settings****Privacy & Security**
34+
3. Scroll to the bottom and find the message "Mac blocked CloudSync"
35+
4. Click **Allow Anyway**
36+
5. Close and reopen ContentView in Xcode
37+
6. The same error should appear but now with a third button **Open Anyway** - click it
38+
7. If errors persist, try reopening and closing ContentView multiple times or repeat the security steps above
39+
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+
```
195+
196+
## Expected Results
197+
198+
When you run the test app, you should see status messages in the UI indicating:
199+
- Database connection success
200+
- Extension loading status
201+
- CloudSync version information (if successfully loaded)
202+
203+
This confirms that CloudSync is properly integrated and functional in your Swift project.

0 commit comments

Comments
 (0)