description: "How I built a privacy-first iOS app for BJD doll enthusiasts using SwiftUI, CoreData, and local notifications."
published: false
tags: swiftui, ios, indiedev, appdev
cover_image: /assets/bjd-cover.jpg
As an iOS developer and BJD (Ball-Jointed Doll) collector, I found myself managing my growing doll collection with spreadsheets, random notes, and calendar reminders. It was messy.
So I built BJD Doll Archive — a dedicated app that understands the specific needs of BJD collectors.
The Problem
BJD collecting involves a lot of tracking:
- Doll details: brand, sculpt, model, face-up artist, purchase date
- Care schedules: MSC coating refreshes, stain checks, light protection
- Vendor experiences: which face-up artists deliver quality work, which shops are reliable
- Photos: documenting customization progress
Generic inventory apps don't understand BJD-specific concepts like "face-up refresh cycle" or "resin yellowing prevention."
The Solution: SwiftUI + Local-First Architecture
SwiftUI for Rapid UI Development
SwiftUI made it straightforward to build the kind of detail-oriented forms BJD collectors need:
struct DollProfile: View {
@State private var doll: Doll
var body: some View {
Form {
Section("Basic Info") {
TextField("Name", text: $doll.name)
Picker("Brand", selection: $doll.brand) {
ForEach(Brand.allCases) { Text($0.rawValue) }
}
}
Section("Face-up") {
TextField("Artist", text: $doll.faceUpArtist)
DatePicker("Last Refresh", selection: $doll.lastFaceUp)
}
}
}
}
Privacy-First: No Backend
BJD collections can be valuable and personal. I made a deliberate choice to keep all data on-device:
- CoreData for local persistence
- No user accounts
- No cloud sync
- No analytics tracking
This means the app works offline and your collection data never leaves your phone.
Local Notifications for Care Reminders
The care reminder system uses UNUserNotificationCenter with custom repeat intervals:
func scheduleCareReminder(for doll: Doll, type: CareType) {
let content = UNMutableNotificationContent()
content.title = "Care Reminder"
content.body = "Time to \(type.action) for \(doll.name)"
let trigger = UNCalendarNotificationTrigger(
dateMatching: type.nextDateComponents(from: doll),
repeats: true
)
let request = UNNotificationRequest(
identifier: "\(doll.id)-\(type)",
content: content,
trigger: trigger
)
UNUserNotificationCenter.current().add(request)
}
Lessons Learned
- Niche apps have loyal users — The BJD community is passionate and underserved by mainstream apps
- Privacy matters more than you think — Collectors appreciate that their data stays local
- Simple > Feature-rich — Resistance to feature creep kept the app focused
- One-time purchase > Subscription — For niche tools, users prefer paying once
What's Next
- Japanese and Korean localization (BJD is huge in these markets)
- iPad support for larger photo viewing
- Export/import for backup
BJD Doll Archive is available on the App Store.
If you're interested in indie iOS development or BJD collecting, let's connect!
Top comments (0)