Phototagger is a SwiftUI iPhone app for people whose photo library has outgrown Apple Photos. It adds custom tagging, fast retrieval, and cleanup workflows on top of PhotoKit by maintaining a local metadata index in SQLite via GRDB.
It is intentionally opinionated about modern iOS development: Swift 6.2, SwiftUI, @Observable, async/await, PhotoKit, and a database-backed architecture that keeps the UI responsive even with large libraries.
- Inbox-first triage flow for untagged photos
- Custom tags stored locally and mapped to
PHAsset.localIdentifier - Browse/search experience powered by indexed metadata
- Duplicate detection using Vision feature prints and DCT-based perceptual hashing
- Cleanup workflows for screenshots and large videos
- Capture flow that lets users shoot and tag in one motion
- Privacy-forward architecture with local-first storage
The app is organized around a clear split between product surfaces and infrastructure:
Phototagger/
├── Models/ GRDB-backed records and lightweight domain types
├── Database/ SQLite schema, migrations, and high-performance queries
├── Services/ App state, indexing, PhotoKit integration, diagnostics
└── Views/ Feature-driven SwiftUI screens and reusable components
Core pieces:
AppStateis the@Observablemain-actor coordinator for shared app state.PhotoLibraryServicewraps PhotoKit and authorization concerns.IndexingServicekeeps the local index in sync with the user’s library.AppDatabasehandles persistence, migrations, and query-heavy workflows.
- Swift 6.2
- SwiftUI
- Swift Observation (
@Observable) - GRDB / SQLite
- PhotoKit
- AVFoundation
- Vision
- Optional diagnostics integrations:
- Firebase Crashlytics
- TelemetryDeck
This repository does not include live diagnostics credentials.
- Crash reporting is disabled unless you add your own
GoogleService-Info.plist. - Analytics is disabled unless you provide
TELEMETRYDECK_APP_IDin the app’s Info settings. - A safe starter file is included at
GoogleService-Info.plist.example.
To enable diagnostics locally:
- Copy
GoogleService-Info.plist.exampletoGoogleService-Info.plistand replace it with your real Firebase config if you want Crashlytics. - In Xcode target build settings, provide a value for
TELEMETRYDECK_APP_IDif you want TelemetryDeck.
The current Xcode project and scheme are still named PhotoShelf, while the product and app branding are Phototagger.
Build in Xcode:
- Open
PhotoShelf.xcodeproj - Select the
PhotoShelfscheme - Run on an iPhone simulator or device running iOS 18.6+
Command line build:
xcodebuild -project PhotoShelf.xcodeproj -scheme PhotoShelf -destination 'generic/platform=iOS Simulator' buildServices/AppState.swiftfor state orchestration and app-level coordinationServices/IndexingService.swiftfor PhotoKit indexing and perceptual hashing flowDatabase/AppDatabase.swiftfor schema and query-heavy persistence workViews/Inbox/for the focused inbox sprint workflowViews/Cleanup/for cleanup tooling and duplicate detection UXJournal.mdfor the engineering story, tradeoffs, and debugging history