-
Notifications
You must be signed in to change notification settings - Fork 12
Description
πΈ Integrating HeyCyan Glasses AI Gallery in Your Swift App
This guide explains how to add HeyCyan Glasses support with AI image gallery functionality to any Swift/SwiftUI application.
Prerequisites
- Xcode 14.0+
- iOS 15.0+ deployment target
- HeyCyan Glasses SDK (
QCSDK.framework) - Physical iOS device (Bluetooth doesn't work in simulator)
Step 1: Add the QCSDK Framework
- Download or obtain the
QCSDK.framework - Drag it into your Xcode project
- In your target's settings:
- Go to General β Frameworks, Libraries, and Embedded Content
- Add
QCSDK.frameworkand set to Embed & Sign
Step 2: Configure Info.plist
Add these required Bluetooth permissions to your Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs Bluetooth to connect to HeyCyan Glasses</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs Bluetooth to communicate with HeyCyan Glasses</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to save AI-generated images</string>Step 3: Create the Bridging Header (if using Swift)
Create a bridging header file YourApp-Bridging-Header.h:
#import <QCSDK/QCSDK.h>
#import <QCSDK/QCSDKManager.h>
#import <QCSDK/QCSDKCmdCreator.h>Configure it in Build Settings β Swift Compiler - General β Objective-C Bridging Header
Step 4: Add the AI Gallery View
Create AIGalleryView.swift:
import SwiftUI
import Photos
struct AIImage: Identifiable {
let id = UUID()
let image: UIImage
let timestamp: Date
}
class AIImageStore: ObservableObject {
static let shared = AIImageStore()
@Published var images: [AIImage] = []
private let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first\!
private let imagesFolder: URL
private init() {
imagesFolder = documentsDirectory.appendingPathComponent("AIImages")
try? FileManager.default.createDirectory(at: imagesFolder, withIntermediateDirectories: true)
loadImages()
// Set up global listener for AI images
NotificationCenter.default.addObserver(
forName: .aiImageReceived,
object: nil,
queue: .main
) { [weak self] notification in
if let imageData = notification.object as? Data,
let image = UIImage(data: imageData) {
self?.addImage(image)
}
}
}
func addImage(_ image: UIImage) {
let aiImage = AIImage(image: image, timestamp: Date())
images.insert(aiImage, at: 0)
saveImageToDisk(aiImage)
}
private func saveImageToDisk(_ aiImage: AIImage) {
let filename = "\(aiImage.id.uuidString).jpg"
let url = imagesFolder.appendingPathComponent(filename)
if let data = aiImage.image.jpegData(compressionQuality: 0.8) {
try? data.write(to: url)
}
}
private func loadImages() {
guard let files = try? FileManager.default.contentsOfDirectory(at: imagesFolder,
includingPropertiesForKeys: [.creationDateKey]) else { return }
for file in files {
if let data = try? Data(contentsOf: file),
let image = UIImage(data: data),
let attributes = try? FileManager.default.attributesOfItem(atPath: file.path),
let creationDate = attributes[.creationDate] as? Date {
let aiImage = AIImage(image: image, timestamp: creationDate)
images.append(aiImage)
}
}
images.sort { $0.timestamp > $1.timestamp }
}
}
struct AIGalleryView: View {
@StateObject private var imageStore = AIImageStore.shared
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationView {
ScrollView {
if imageStore.images.isEmpty {
VStack(spacing: 20) {
Image(systemName: "photo.on.rectangle.angled")
.font(.system(size: 60))
.foregroundColor(.gray)
Text("No AI Images Yet")
.font(.headline)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, minHeight: 400)
} else {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(imageStore.images) { aiImage in
Image(uiImage: aiImage.image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 120)
.clipped()
.cornerRadius(8)
}
}
.padding()
}
}
.navigationTitle("AI Gallery")
}
}
}
extension Notification.Name {
static let aiImageReceived = Notification.Name("aiImageReceived")
}Step 5: Create the Bluetooth Manager
Create BluetoothManager.swift:
import Foundation
import CoreBluetooth
import UIKit
class BluetoothManager: NSObject, ObservableObject {
static let shared = BluetoothManager()
@Published var isConnected = false
@Published var deviceInfo = DeviceInfo()
private var sdkManager: QCSDKManager?
private override init() {
super.init()
setupManager()
}
private func setupManager() {
sdkManager = QCSDKManager.shareInstance()
sdkManager?.delegate = self
}
func takeAIImage() {
// Set device to AI Photo mode to capture image
QCSDKCmdCreator.setDeviceMode(.aiPhoto, success: {
print("AI Photo mode activated")
}, fail: { mode in
print("Failed to set AI Photo mode")
})
}
}
// MARK: - QCSDKManagerDelegate
extension BluetoothManager: QCSDKManagerDelegate {
func didReceiveAIChatImageData(_ imageData: Data) {
print("π¨ AI image received: \(imageData.count) bytes")
// Verify it's a valid image
if let image = UIImage(data: imageData) {
print("β
Valid image: \(image.size.width)x\(image.size.height)")
// Post notification for gallery
DispatchQueue.main.async {
NotificationCenter.default.post(name: .aiImageReceived, object: imageData)
}
}
}
}
struct DeviceInfo {
var batteryLevel: Int = 0
var isCharging: Bool = false
var aiImageData: Data?
}Step 6: Integrate in Your App
In your main app or view controller:
import SwiftUI
struct ContentView: View {
@StateObject private var bluetoothManager = BluetoothManager.shared
@State private var showingGallery = false
var body: some View {
NavigationView {
VStack(spacing: 20) {
// Connection status
HStack {
Circle()
.fill(bluetoothManager.isConnected ? Color.green : Color.red)
.frame(width: 10, height: 10)
Text(bluetoothManager.isConnected ? "Connected" : "Disconnected")
}
// Take AI Photo button
Button(action: {
bluetoothManager.takeAIImage()
}) {
Label("Take AI Photo", systemImage: "camera.fill")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.disabled(\!bluetoothManager.isConnected)
// Gallery button
Button(action: {
showingGallery = true
}) {
Label("View Gallery", systemImage: "photo.stack")
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.sheet(isPresented: $showingGallery) {
AIGalleryView()
}
}
}
}Step 7: Handle Device Connection
Add connection management to BluetoothManager:
extension BluetoothManager {
func connect(to device: CBPeripheral) {
// Use QCCentralManager to connect
QCCentralManager.shared().connect(device)
}
func disconnect() {
QCCentralManager.shared().disconnect()
}
func startScanning() {
QCCentralManager.shared().scan()
}
}How It Works
-
Image Capture Flow:
- User taps "Take AI Photo" button
- App sends command to glasses via
setDeviceMode(.aiPhoto) - Glasses capture and process image
- Image data received via
didReceiveAIChatImageDatadelegate - Notification posted with image data
- AIImageStore receives notification and adds to gallery
- Image saved to disk for persistence
-
Gallery Features:
- Grid layout with 3 columns
- Images sorted by timestamp (newest first)
- Persistent storage across app launches
- Automatic UI updates when new images arrive
Important Notes
- Physical Device Required: Bluetooth functionality requires a physical iOS device
- Background Modes: If you need background Bluetooth, add appropriate background modes in Capabilities
- Error Handling: Add proper error handling for production apps
- Memory Management: Consider implementing image cache limits for apps with heavy usage
Troubleshooting
Images not appearing in gallery
- Check that the delegate is properly set:
sdkManager?.delegate = self - Verify the notification name matches:
aiImageReceived - Check console logs for "AI image received" messages
Bluetooth connection issues
- Ensure all Info.plist permissions are added
- Check that the device is properly paired in iOS Settings
- Verify the QCSDK.framework is properly embedded
Build errors
- Verify the bridging header path in Build Settings
- Ensure the framework is added to "Embedded Binaries"
- Check that minimum iOS version is 15.0+
Complete Example Project
A complete working example is available at: https://github.com/ebowwa/HeyCyanGlassesSDK
Support
For SDK-specific issues, refer to the HeyCyan SDK documentation.
For implementation help, please comment on this issue or create a new one.
License
This integration guide is provided as-is. Please refer to HeyCyan's SDK license for usage terms.