◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/AI Models
◉ AI Models/2026-04-30Advanced

Building an AI-Powered Photo Organizer App with Rork — A Complete Implementation Guide Using Vision, CLIP, and pgvector

A complete implementation guide for building an AI-powered photo organizer app with Rork. Extract faces, objects, and text with the Vision framework, generate semantic embeddings with CLIP, and run similarity search at scale using Supabase pgvector — designed as a production pipeline from day one.

Rork584AI31Photo OrganizerCLIPVisionpgvector3Supabase36PhotoKitVector Search

✦ Premium Article

import { Callout } from '@/components/ui/callout';

After five years on the same iPhone, photo libraries routinely cross 30,000 images. I've personally lost ten minutes scrolling for "that sunset shot" more times than I want to admit, and I've come to believe that photo organizing is one of those product categories that needs to be rebuilt from scratch with AI as the foundation. Looking at the App Store today, most top-grossing photo organizers were rebuilt around AI features after 2024 — which means there's still meaningful room for new entrants if you can ship a credible AI-first experience.

This article walks through the implementation pipeline I use to build a serious AI photo organizer in Rork — not a "call Vision and call it a day" demo, but a production pattern that holds up at 50,000-image scale. I'm including everything I tripped over so you can skip the wasted hours.

The Three-Layer Architecture That Actually Scales

A common mistake in photo organizer apps is analyzing every image at launch, eating both memory and battery in the process. My first prototype showed users a "Processing..." spinner for 30 minutes after the first launch — predictably, retention was zero. To get to production quality, you have to split analysis across three async layers.

The skeleton looks like this:

  • Layer 1 (on-device, immediate): Enumerate PHAsset objects from PhotoKit and run lightweight Vision passes (object detection, OCR, face detection). Generate 512-dim CLIP embeddings via CoreML.
  • Layer 2 (on-device, background): Schedule a BGProcessingTask to chew through the rest of the library while the device is idle. Cache results and embeddings in SQLite.
  • Layer 3 (server): The vector index that doesn't fit in device storage lives in Supabase pgvector, accelerated with HNSW.

The single most important architectural decision is to share the same embedding space between device and server. Locally generated CLIP embeddings must be compatible with whatever pgvector stores. Decide this upfront — retrofitting it later means rebuilding the whole pipeline.

Why CLIP at All — Splitting the Job with Vision

The Vision framework excels at categorical judgments ("this is a dog", "this is a car", "this is text") but cannot reason about semantic similarity like "a sunset photo at the beach with a dog in it." CLIP — particularly Apple's CoreML-optimized MobileCLIP variants — projects images and text into the same vector space, which is what enables natural-language queries.

In my apps I use both. Vision attaches tags; CLIP attaches semantic embeddings. At query time the hybrid wins on precision. I tried Vision-only and CLIP-only versions before settling on the hybrid; the precision gap is real.

Step 1: Accessing the Photo Library Through PhotoKit

The first hurdle is how you ask for photo library access. Since iOS 14, PHPickerViewController is the recommended path for picker-style flows, but a photo organizer that wants to analyze the whole library needs readWriteAccess rather than the addOnlyAccess mode.

Here's the permission helper I use, packaged so it can be called from Rork's native module layer (or via Expo Modules API).

import Photos
 
enum PhotoLibraryAuthError: Error {
  case denied
  case restricted
  case limited
}
 
func requestPhotoLibraryAccess() async throws -> [PHAsset] {
  let status = await PHPhotoLibrary.requestAuthorization(for: .readWrite)
  switch status {
  case .authorized:
    return fetchAllAssets()
  case .limited:
    // Only user-selected images are returned. Surface a "switch to Full Access" guide in your UI.
    throw PhotoLibraryAuthError.limited
  case .denied:
    throw PhotoLibraryAuthError.denied
  case .restricted:
    throw PhotoLibraryAuthError.restricted
  case .notDetermined:
    throw PhotoLibraryAuthError.denied
  @unknown default:
    throw PhotoLibraryAuthError.denied
  }
}
 
private func fetchAllAssets() -> [PHAsset] {
  let options = PHFetchOptions()
  options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
  options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
  let result = PHAsset.fetchAssets(with: options)
  var assets: [PHAsset] = []
  result.enumerateObjects { asset, _, _ in assets.append(asset) }
  return assets
}

What you should see: a permission dialog on first launch, then — if granted — all image PHAssets sorted newest-first. Users who hit the limited branch need a clear, in-app explanation that the AI features require library-wide scanning, with a one-tap deeplink to Settings. I ship a single screen that says "We need full access because similarity search has to scan your whole library to find matches" and stop there.

A Hidden Trap When Pulling the Actual Image Bytes

When you call PHImageManager.requestImage, getting the options wrong means iCloud will redownload your users' originals every single time — and burn through their cellular plan in the process. I learned this the hard way after launch. Always use these options:

let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = false  // Use the local cache only
options.deliveryMode = .opportunistic
options.resizeMode = .fast
options.isSynchronous = false

With isNetworkAccessAllowed = false, "optimized" (cloud-stored) images get skipped. Queue those into a separate "offline pending" list and pick them up in batch on Wi-Fi via the background job in Step 5.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You'll be able to find that one sunset photo out of 50,000 in under 0.3 seconds by integrating a CLIP embedding-based similarity pipeline into your Rork app.
✦You'll learn how to ship a production background-job system that pulls tens of thousands of images from PhotoKit and indexes them off-hours.
✦You'll be able to apply the hybrid search pattern combining Vision's object detection, OCR, and face vectors with CLIP's semantic embeddings to your own apps.
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◉ AI Models2026-04-22
Building an AI Reading Assistant App with Rork — Bookshelf Scanning, Progress Tracking, and Chapter Summaries
A complete guide to building a reading app on Rork that scans your bookshelf with a photo, tracks progress per chapter, and generates personal AI summaries and reflective questions with Claude — including monetization design.
◉ AI Models2026-10-14
Take the Prompt Out of the App: An Edge Function Gateway With the Guard on the Output Side
A system prompt shipped inside your app is readable from the day it ships. This walkthrough moves the prompt into a Supabase Edge Function, lets the client name only a task, and puts the guard on an output allowlist, with tested code.
◉ AI Models2026-09-28
Keep Fixing in the Same Thread, or Restore and Rephrase? What I Learned Fixing One Rork Bug Both Ways
When a Rork fix request fails for the third time, do you keep going or use Restore and ask differently? I fixed the same bug both ways and compared round trips, leftover code, and where I now draw the line.