A SwiftUI iOS app designed for exactly two people (you + your partner) to share and coordinate life together. Think of it as FamilyWall, but optimized for couples.
- Couple Space: Create a private shared space with CKShare invite system
- Meals: Weekly meal planning with recipes, ingredients, and nutrition tracking
- Tasks: Shared task list with recurring chores and assignees
- Budget: Savings goals tracking with contributions and expenses
- Map: Opt-in location sharing with privacy controls
- CloudKit Sync: All data syncs between partners via iCloud
- Public/Private Visibility: Control what your partner can see
- Diet Restriction Warnings: Automatic alerts when meals contain restricted ingredients
- Grocery List Generator: Auto-generate shopping lists from planned meals
- Recurring Tasks: Daily, weekly, monthly, or custom recurrence
- Battery-Friendly Location: Significant location change updates only
- iOS 17.0+
- Xcode 15.0+
- Apple Developer Account (for CloudKit)
- Two iCloud accounts (for testing sharing)
DuoWall/
├── DuoWall.xcodeproj/
├── DuoWall/
│ ├── DuoWallApp.swift # App entry point
│ ├── ContentView.swift # Root view + tab navigation
│ ├── Info.plist # App configuration
│ ├── DuoWall.entitlements # iCloud entitlements
│ │
│ ├── Models/
│ │ ├── Models.swift # Enums and basic types
│ │ └── DataModels.swift # SwiftData @Model classes
│ │
│ ├── ViewModels/
│ │ ├── CoupleSpaceViewModel.swift
│ │ ├── MealsViewModel.swift
│ │ ├── TasksViewModel.swift
│ │ ├── BudgetViewModel.swift
│ │ └── MapViewModel.swift
│ │
│ ├── Views/
│ │ ├── Onboarding/
│ │ ├── Home/
│ │ ├── Meals/
│ │ ├── Tasks/
│ │ ├── Budget/
│ │ ├── Map/
│ │ └── Settings/
│ │
│ ├── Services/
│ │ ├── CloudKitManager.swift # CloudKit sync operations
│ │ └── LocationManager.swift # Location services
│ │
│ └── Utilities/
│ └── Helpers.swift # Date extensions, helpers
│
└── README.md
- Log in to Apple Developer Portal
- Create a new App ID with:
- Bundle ID:
com.yourteam.DuoWall(or your own) - Enable iCloud capability
- Enable CloudKit under iCloud
- Enable Push Notifications (for sync notifications)
- Bundle ID:
- Go to CloudKit Dashboard
- Create a new container:
iCloud.com.yourteam.DuoWall - In Schema, create these Record Types:
| Field | Type |
|---|---|
| id | String |
| createdAt | Date/Time |
| ownerUserRecordName | String |
| partnerUserRecordName | String |
| isConnected | Int64 |
| Field | Type |
|---|---|
| id | String |
| date | Date/Time |
| mealType | String |
| title | String |
| notes | String |
| servings | Int64 |
| calories | Int64 |
| protein | Int64 |
| carbs | Int64 |
| fat | Int64 |
| tags | String (List) |
| ingredients | String (List) |
| steps | String (List) |
| store | String |
| visibility | String |
| ownerUserRecordName | String |
| coupleSpaceID | String |
| createdAt | Date/Time |
| updatedAt | Date/Time |
| Field | Type |
|---|---|
| id | String |
| title | String |
| notes | String |
| dueDate | Date/Time |
| isComplete | Int64 |
| recurrenceRule | String |
| customRecurrenceDays | Int64 |
| assignee | String |
| visibility | String |
| ownerUserRecordName | String |
| coupleSpaceID | String |
| createdAt | Date/Time |
| updatedAt | Date/Time |
| Field | Type |
|---|---|
| id | String |
| name | String |
| quantity | String |
| category | String |
| store | String |
| isChecked | Int64 |
| visibility | String |
| ownerUserRecordName | String |
| coupleSpaceID | String |
| updatedAt | Date/Time |
| Field | Type |
|---|---|
| id | String |
| title | String |
| notes | String |
| targetAmount | Double |
| targetDate | Date/Time |
| visibility | String |
| ownerUserRecordName | String |
| coupleSpaceID | String |
| createdAt | Date/Time |
| updatedAt | Date/Time |
| Field | Type |
|---|---|
| id | String |
| goalID | String |
| transactionType | String |
| amount | Double |
| date | Date/Time |
| note | String |
| category | String |
| visibility | String |
| ownerUserRecordName | String |
| coupleSpaceID | String |
| Field | Type |
|---|---|
| userRecordName | String |
| isSharingEnabled | Int64 |
| accuracyMode | String |
| expiry | String |
| expiresAt | Date/Time |
| lastLatitude | Double |
| lastLongitude | Double |
| lastUpdatedAt | Date/Time |
| coupleSpaceID | String |
-
Add indexes for queryable fields:
coupleSpaceID(Queryable) on all record typesownerUserRecordName(Queryable) on all record typesdate(Queryable, Sortable) on MealEntrydueDate(Queryable, Sortable) on TaskItem
-
Deploy schema to Production when ready
- Open
DuoWall.xcodeprojin Xcode - Select the project in the navigator
- Go to Signing & Capabilities
- Select your Team
- Update Bundle Identifier if needed
- Ensure these capabilities are enabled:
- iCloud (CloudKit checked, container selected)
- Push Notifications
- Background Modes (if adding background location)
In CloudKitManager.swift, update the container identifier:
self.container = CKContainer(identifier: "iCloud.com.yourteam.DuoWall")Also update in DuoWallApp.swift for share handling.
- Connect an iOS device or use Simulator
- Build and run (Cmd+R)
- Sign in to iCloud on the device if not already
- You need TWO physical iOS devices (or one device + simulator)
- Each device must be signed into a DIFFERENT iCloud account
- Both accounts must have iCloud Drive enabled
On Device A (Owner):
- Launch app
- Complete onboarding
- Create Couple Space
- Tap "Invite Partner"
- Share the link via Messages/AirDrop/etc.
On Device B (Partner):
- Receive the share link
- Tap the link - it should open DuoWall
- Accept the share invitation
- You should now see the shared space
- Add a task on Device A
- Check Device B - task should appear within seconds
- Complete task on Device B
- Verify on Device A
- In Xcode, select Product > Archive
- Wait for archive to complete
- Organizer window will open
- In Organizer, select your archive
- Click Distribute App
- Select App Store Connect
- Select Upload
- Follow prompts (Manage signing automatically recommended)
- Go to App Store Connect
- Select your app
- Go to TestFlight tab
- Wait for build processing (5-30 minutes)
- Add Test Information (what to test, email, etc.)
- Set up Internal or External testing group
- Add testers by email
For Internal Testing:
- Add Apple ID email addresses
- Testers get instant access
For External Testing:
- Requires Beta App Review (usually 24-48 hours)
- Can use public link for up to 10,000 testers
- Check CloudKit Dashboard for records
- Verify container identifier matches
- Ensure schema is deployed to appropriate environment
- Check device is signed into iCloud
- Both users must have iCloud enabled
- Check that CKShare was created successfully
- Verify the URL scheme is correctly registered
- Check Info.plist has location usage descriptions
- Verify location permission was granted
- Test on real device (simulator location is simulated)
In DEBUG builds, go to Settings > Debug Menu to:
- Add sample data
- View app state
- Clear local data
- SwiftUI + MVVM: Views are declarative, business logic in ViewModels
- SwiftData: Local persistence with offline support
- CloudKit: Sync via CKRecord operations (not automatic SwiftData sync)
- CKShare: Partner invitation via iOS sharing sheet
- Async/Await: All CloudKit operations use modern concurrency
- Partner profile sync requires both users to be online
- Location sharing requires foreground or recent background activity
- CKShare acceptance requires the app to be installed first
- Conflict resolution is last-write-wins
- Resy/OpenTable integration for Date Night
- Push notifications for task reminders
- Calendar integration
- Photo sharing
- Chat/notes feature
- Widget support
Private project - not for distribution.
Built with SwiftUI, CloudKit, and SwiftData for iOS 17+.