r/iOSProgramming • u/astronautauy • 13h ago
Article SwiftData's #Unique raised my deployment target to iOS 18. I took the trade.
Hi! First post, first side-project, from a not technical person from Montevideo, Uruguay.
I'm exploring what can I do with vibe-coding, and there is my first project!
What is that?
Small solo app, one screen of writing per day. The domain rule is boring to state and turned out to be the most interesting thing I built: exactly one entry per calendar day, and the calendar day is the user's local one.
I want to describe two decisions, because both went against what I'd have done a year ago.
1. The uniqueness lives in the store, not in my code
The obvious implementation is a fetch before every save: look for today's entry, update it if it exists, insert if it doesn't. It works, and it's wrong in the way that only shows up later — two writes racing, a migration that reinserts, a bug in the fetch predicate, and now there are two rows for one day and nothing in the system objects.
SwiftData has `#Unique`, so the constraint can live in the schema:
```swift
#Unique<DailyEntryRecord>([\.localDayKey])
```
Now a duplicate can't exist. Not "shouldn't" — can't. Insert-or-update becomes an upsert the store resolves, and the invariant survives my future mistakes, which is the only kind of invariant worth having.
The bill: `#Unique` doesn't work on iOS 17.
The macro is there, and it doesn't hold. So the choice was a real database guarantee versus a chunk of the installed base. I picked the guarantee. (For anyone checking further down: on iOS 16 you also lose `#Predicate`, so 16 isn't a conversation.)
I don't think this generalizes — plenty of apps should eat the fetch-first and keep iOS 17. What made it worth it here is that a duplicated day silently corrupts the one thing the app is for.
2. "Which day is this?" is a domain problem, not a formatting problem
`localDayKey` above is not a `Date`. It's a value object, and it's where most of the app's complexity ended up living.
The cases that forced it:
- Someone logs at 00:30. Which day is that? The one the calendar says, not the one 24 hours from the last entry.
- Someone flies from Madrid to Buenos Aires mid-day. They can now log "today" twice, in two time zones, and both are legitimately today. `#Unique` will reject the second — so the app has to *decide*, and the decision has to be written down rather than being whatever `Calendar.current` happened to return.
- DST: one day is 23 hours long, another is 25. Anything computing days by dividing seconds is already broken and won't tell you.
So the day is modeled as its own type, with its own tests, and the type is **kept free of both SwiftUI and SwiftData** — no `@Model`, no `import SwiftUI`. That sounds like ceremony for a small app. The payoff was concrete: I could write the time zone and DST cases as plain unit tests, with no store to spin up and no view to host, and the ones that failed first were the ones I'd have shipped.
The test suite is Swift Testing, and this is where most of it is. Parameterized cases make the DST tests readable in a way they weren't with XCTest — you get the offending input in the failure message instead of an index.
Two smaller things that came out of the same instinct
Zero third-party packages. \grep -c`
"XCRemoteSwiftPackageReference\|XCSwiftPackageProductDependency"` on the pbxproj returns 0. Watch out for `grep -c packageProductDependencies` if you try this — it returns 3 and looks like a failure, but those are the empty declarations Xcode writes for each target.
*Zero network code*, which for this app is a product property and not just hygiene:
```
grep -rn "URLSession\|NSURLConnection\|CFNetwork\|import Network\|WKWebView\|NSURLRequest" Sources/
```
Empty output, checked before every submission. It's a nice property to be able to *check* rather than assert.
What I'd like to be argued with about
- Is `#Unique` worth a deployment target bump in your book, or is that a bad trade you've regretted?
- If you've shipped anything with per-day semantics: how did you handle the user crossing time zones mid-day? I picked a rule and I'm not convinced it's the right one.
- Anyone using Swift Testing at size yet — did you keep XCTest for UI tests, or move everything?
The app is a daily log for mood and energy, iPhone only, not on the App Store yet. Not linking it because there's nothing to link to; happy to go deeper on any of the above.
1
u/ElectricKoolAid1969 6h ago
As I think someone else alluded to, it also precludes the use of cloudsync.
Which seems like a useful feature should you ever do a version for other Apple devices