13 KiB
AI Implementation Context
This file summarizes project-specific context, architecture, and conventions to speed up future AI work.
Project Summary
BusinessCard is a SwiftUI app for building and sharing digital business cards with QR codes. It includes iOS screens for cards, sharing, customization, contact tracking, and widget previews, plus a watchOS companion to show a default card QR code.
Key Constraints
- iOS 26+, watchOS 12+, Swift 6.2.
- SwiftUI with
@Observableclasses and@MainActor. - Protocol‑oriented architecture is prioritized.
- Clean Architecture with separation of concerns.
- One public type per file, keep files under 300 lines.
- No UIKit unless explicitly requested.
- String Catalogs only (
.xcstrings). - No magic numbers in views; use Bedrock's
Designconstants. - Uses Bedrock package for shared design system and utilities.
Core Data Flow
AppStateowns:CardStore(cards and selection)ContactsStore(contact list + search)ShareLinkService(share URLs)
- SwiftData with CloudKit for persistence and sync.
- WatchConnectivity for iOS-Watch data sharing (NOT App Groups - see below).
- Views read state via environment and render UI only.
CRITICAL: WatchConnectivity vs App Groups
App Groups do NOT work for iPhone ↔ Apple Watch communication.
- App Groups only work between an app and its extensions on the SAME device
- iPhone and Apple Watch are different devices with separate file systems
- Use WatchConnectivity framework instead
The WatchConnectivityService on iOS uses updateApplicationContext to push card data to the watch. The watch receives this data in WatchConnectivityService and updates WatchCardStore.
Dependencies
Bedrock Package
Located at /Frameworks/Bedrock (local package). Provides:
Design.Spacing,Design.CornerRadius,Design.Opacity, etc.QRCodeGeneratorandQRCodeImageViewfor QR codes- Reusable settings components
App-specific extensions are in Design/DesignConstants.swift:
Design.CardSize- card dimensions, avatar, QR sizesDesign.Shadow.offsetNone- zero offset extensionColor.AppBackground,Color.CardPalette,Color.AppAccent,Color.AppText
Important Files
Models
-
Models/BusinessCard.swift— SwiftData model with:- Name fields: prefix, firstName, middleName, lastName, maidenName, suffix, preferredName
- Basic fields: role, company
- Rich fields: pronouns, bio, headline, accreditations (comma-separated tags)
- Dynamic contact fields:
@Relationshipto array ofContactFieldobjects - Photos:
photoData(profile),coverPhotoData(banner background),logoData(company logo) stored with@Attribute(.externalStorage) - Computed:
theme,layoutStyle,vCardPayload,orderedContactFields,fullName,vCardName - Helper methods:
addContactField,removeContactField,reorderContactFields
Name Properties (Single Source of Truth):
fullName— Computed from individual name fields. Includes special formatting: preferred name in quotes, maiden name and pronouns in parentheses. THIS IS THE ONLY SOURCE for display names.vCardName— Plain name for vCard export (no quotes or parentheses formatting).- ⚠️ There is NO stored
displayNameproperty. Always usefullNamefor display.
-
Models/ContactField.swift— SwiftData model for dynamic contact fields:- Properties:
typeId,value,title,orderIndex - Relationship:
card(inverse to BusinessCard) - Computed:
fieldType,displayName,iconImage(),iconColor,buildURL() - Supports multiple fields of same type, drag-to-reorder
- Properties:
-
Models/ContactFieldType.swift— Struct defining 30+ field types:- Categories: contact, social, developer, messaging, payment, creator, scheduling, other
- Properties:
id,displayName,systemImage,isCustomSymbol,iconColor - Properties:
valueLabel,valuePlaceholder,titleSuggestions,keyboardType - Method:
iconImage()— returns correct Image (asset vs SF Symbol) - Method:
urlBuilder— closure to build deep link URLs - Static instances:
.email,.phone,.linkedIn,.twitter,.instagram, etc. - Uses custom assets from
Assets.xcassets/SocialSymbols/
-
Models/Contact.swift— SwiftData model with:- Basic fields: name, role, company
- Annotations: notes, tags (comma-separated), followUpDate, whereYouMet
- Received cards: isReceivedCard, email, phone
- Photo:
photoDatastored with@Attribute(.externalStorage)- editable via PhotosPicker in ContactDetailView and AddContactSheet - Computed:
tagList,hasFollowUp,isFollowUpOverdue - Static:
fromVCard(_:)parser
-
Models/CardTheme.swift— card theme palette -
Models/CardLayoutStyle.swift— stacked/split/photo -
Models/AppTab.swift— tab bar enum
Protocols (POP)
Protocols/BusinessCardProviding.swift— card selection interfaceProtocols/ContactTracking.swift— contact management interfaceProtocols/ShareLinkProviding.swift— share URL generation interface
State
State/AppState.swift— central state containerState/CardStore.swift— card CRUD, selection, watch syncState/ContactsStore.swift— contacts, search, received cards
Services
Services/ShareLinkService.swift— share URL helpersServices/VCardFileService.swift— vCard file generation for sharingServices/WatchConnectivityService.swift— WatchConnectivity sync to watch (usesupdateApplicationContext)
Views
Main screens:
Views/RootTabView.swift— tabbed shell (3 tabs: My Cards, Contacts, Widgets) + floating share buttonViews/CardsHomeView.swift— full-screen swipeable card view with edit buttonViews/ShareCardView.swift— QR + share actions + track share (opened as sheet from floating button)Views/ContactsView.swift— contact list with sectionsViews/WidgetsView.swift— widget preview mockups
Feature views:
Views/BusinessCardView.swift— card display with layoutsViews/CardEditorView.swift— create/edit cards with PhotosPicker for 3 image types (profile, cover, logo)Views/ContactDetailView.swift— full contact view with annotationsViews/QRScannerView.swift— camera-based QR scannerViews/QRCodeView.swift— QR code image generator
Reusable components (in Views/Components/):
AvatarBadgeView.swift— circular avatar with photo or iconIconRowView.swift— icon + text row for detailsLabelBadgeView.swift— small badge labelsActionRowView.swift— generic action row with chevronContactFieldPickerView.swift— grid picker for selecting contact field typesContactFieldsManagerView.swift— orchestrates picker + added fields listAddedContactFieldsView.swift— displays added fields with drag-to-reorderPhotoSourcePicker.swift— generic photo source picker sheet (library, camera, remove)CameraCaptureView.swift— UIImagePickerController wrapper for camera capture
Sheets (in Views/Sheets/):
RecordContactSheet.swift— track share recipientContactFieldEditorSheet.swift— add/edit contact field with type-specific UIAddContactSheet.swift— manually add a new contactPhotoCropperSheet.swift— 2-step photo editor with pinch-to-zoom and square crop
Small utilities:
Views/EmptyStateView.swift— empty state placeholderViews/PrimaryActionButton.swift— styled action button
Assets
Assets.xcassets/SocialSymbols/— custom brand icons as.symbolsetfiles:- Social: linkedin, x-twitter, instagram, facebook, tiktok, threads, bluesky, mastodon, reddit, twitch
- Developer: github.fill
- Messaging: telegram, discord.fill, slack, matrix
- Creator: patreon.fill, ko-fi
Icon Rendering:
- Custom symbols use
Image("symbolName")(asset catalog) - SF Symbols use
Image(systemName: "symbolName") ContactFieldType.isCustomSymbolflag determines which to useContactFieldType.iconImage()returns the correct Image type
Design + Localization
Design/DesignConstants.swift— extends BedrockResources/Localizable.xcstrings— string catalogLocalization/String+Localization.swift— string helpers
watchOS
BusinessCardWatch Watch App/BusinessCardWatchApp.swift— App entry pointBusinessCardWatch Watch App/Views/WatchContentView.swift— Shows default card QR code only (no picker)BusinessCardWatch Watch App/State/WatchCardStore.swift— Receives cards from iPhone via WatchConnectivityBusinessCardWatch Watch App/Services/WatchConnectivityService.swift— ReceivesupdateApplicationContextfrom iPhoneBusinessCardWatch Watch App/Models/WatchCard.swift— Simplified card struct withfullName,role,company,qrCodeData,isDefaultBusinessCardWatch Watch App/Design/WatchDesignConstants.swift— Watch-specific design constantsBusinessCardWatch Watch App/Resources/Localizable.xcstrings
Watch Architecture Notes:
- Watch displays ONLY the default card (no card picker UI)
- Default card is determined by iPhone's
isDefaultflag - QR codes are pre-generated on iPhone (CoreImage not available on watchOS)
- Cards sync via
updateApplicationContext(persists even when apps not running) - Bundle ID must be prefixed with iOS bundle ID (e.g.,
com.mbrucedogs.BusinessCard.watchkitapp)
File Guidelines
Size Limits
- Main views: aim for under 300 lines
- Extract reusable sub-views to
Components/ - Extract sheets/modals to
Sheets/ - Private structs in same file OK if under 50 lines
Current File Sizes
| File | Lines | Status |
|---|---|---|
| ContactFieldType | ~650 | 30+ field definitions, acceptable |
| CardEditorView | ~520 | Complex form + field manager, acceptable |
| BusinessCardView | ~320 | Clickable fields + legacy fallback, acceptable |
| QRScannerView | ~310 | Camera + parsing, acceptable |
| ShareCardView | ~235 | Good |
| ContactDetailView | ~235 | Good |
| ContactsView | ~220 | Good |
| CardsHomeView | ~150 | Full-screen swipeable cards, good |
| AddedContactFieldsView | ~135 | Drag-to-reorder, good |
| ContactFieldPickerView | ~100 | Grid picker, good |
| All others | <110 | Good |
Localization
- All user-facing strings are in
.xcstrings. - Supported locales: en, es‑MX, fr‑CA.
- Use
String.localized("Key")for non-Text strings.
Testing
BusinessCardTests/BusinessCardTests.swiftcovers:- vCard payload formatting
- Card CRUD operations
- Contact search and filtering
- Social links detection
- Contact notes/tags
- Follow-up status
- vCard parsing for received cards
Known Stubs / TODOs
- Apple Wallet and NFC flows are alert-only placeholders.
- Share URLs are sample placeholders.
- Widget previews are not WidgetKit extensions.
- See
ROADMAP.mdfor full feature status.
Lessons Learned / Common Pitfalls
Watch App Not Installing
If isWatchAppInstalled returns false even with both apps running:
- Check "Embed Watch Content" build phase in iOS target's Build Phases
- Ensure "Code Sign On Copy" is CHECKED ← This is the #1 cause
- Verify bundle ID is prefixed correctly
- Clean build folder (⇧⌘K) and rebuild
- On iPhone, open Watch app → verify app appears under "Installed"
Simulator WatchConnectivity Issues
WatchConnectivity is unreliable on simulators:
isWatchAppInstalledoften returnsfalseeven when runningisReachablemay befalseeven with both apps runningupdateApplicationContextmay fail with "counterpart not installed"
Solution: Test real sync functionality on physical devices only. Use #if targetEnvironment(simulator) blocks for UI testing with sample data if needed.
Single Source of Truth for Names
The BusinessCard model uses individual name fields (prefix, firstName, middleName, lastName, etc.) and computes fullName dynamically. There is NO stored displayName property.
- ✅ Use
card.fullNamefor display everywhere - ✅ Use
card.vCardNamefor vCard export - ❌ Never add a stored
displayNameproperty
CoreImage on watchOS
CoreImage is NOT available on watchOS. QR codes must be generated on iOS and sent to the watch as Data.
If You Extend The App
- Add new strings to the String Catalogs.
- Use
Design.*from Bedrock for spacing, opacity, etc. - Add app-specific constants to
DesignConstants.swift. - Keep view logic UI-only; push business logic to state classes.
- Prefer protocols for new capabilities.
- Add unit tests for new model logic.
- Update
ROADMAP.mdwhen adding features. - Keep files under 300 lines — extract components when needed.
- No duplicate code — check for existing components first.
- One public type per file — private helpers OK if small.