Skip to content

How to Integrate HeyCyan Glasses AI Gallery in Any Swift AppΒ #6

@ebowwa

Description

@ebowwa

πŸ“Έ 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

  1. Download or obtain the QCSDK.framework
  2. Drag it into your Xcode project
  3. In your target's settings:
    • Go to General β†’ Frameworks, Libraries, and Embedded Content
    • Add QCSDK.framework and 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

  1. 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 didReceiveAIChatImageData delegate
    • Notification posted with image data
    • AIImageStore receives notification and adds to gallery
    • Image saved to disk for persistence
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions