Help us measure site usage?

With your permission, we use self-hosted analytics to understand page usage. It is optional and does not affect the Service. Learn more.

SwiftadvancedmobileAI generated

Secure Sync Client

Build an iOS notes app that works fully offline, encrypts every note on the device before it leaves, and syncs with a server when the connection comes back. If the same note was changed in two places, the app detects the conflict and lets the user choose which version to keep. Notes are stored locally with SwiftData, encrypted with CryptoKit (AES-GCM) using a key kept in the Keychain, and synced with URLSession using version numbers to detect conflicts. The server only ever sees encrypted data. You won't need a real backend: you'll define the sync API yourself and build a fake server with URLProtocol, which also makes the sync logic fully testable. You need a Mac with Xcode.

Estimate
~17.5h
Steps
8
Completed by
0
Proposed by
codeseed.app

SwiftUI · SwiftData · URLSession

Project roadmap

  1. 01

    Model notes with SwiftData

    ~2h

    Create a SwiftUI app with a SwiftData @Model Note: id (UUID), title, body, updatedAt, serverVersion (an integer, 0 before the first sync) and syncState (synced, pendingUpload or conflict, stored as a string). Build a list screen and an editor. Every local edit sets syncState to pendingUpload and updates updatedAt. Done when: notes can be created and edited offline and are still there after restarting the app.

  2. 02

    Encrypt notes with CryptoKit

    ~2.5h

    On first launch, generate a 256-bit SymmetricKey and store it in the Keychain as a generic password, accessible after first unlock. Write encrypt(note), which serializes the title and body to JSON, seals them with AES.GCM and returns the combined data as base64, and a matching decrypt. Done when: encrypt followed by decrypt returns the original note, and the key survives an app restart.

  3. 03

    Define the sync API and a fake server

    ~2.5h

    Define Codable request and response types for two endpoints: GET /notes?since=<version> returns changed notes, and PUT /notes/{id} uploads an encrypted note together with the serverVersion it was based on. The server replies 409 Conflict if its copy is newer. Implement a fake server as a URLProtocol subclass that keeps notes in memory and follows these rules. Done when: the fake server gives correct responses for downloads, successful uploads and conflicting uploads.

  4. 04

    Upload pending changes

    ~2.5h

    Write a SyncService using URLSession with async/await. It uploads every pendingUpload note, encrypted, with PUT. On 200 it stores the new serverVersion and marks the note synced. On 409 it saves the server's copy in a separate ConflictCopy model linked to the note and marks the note conflict. Network errors leave the note pending so it's retried later. Done when: against the fake server, edits upload and conflicts are flagged instead of overwritten.

  5. 05

    Download remote changes

    ~2h

    Fetch the notes changed since the highest known serverVersion, decrypt them and merge. A note you haven't changed locally is simply updated, a new note is inserted, and a note that is also pendingUpload locally becomes a conflict. A note that fails to decrypt is logged and skipped; it must never crash the sync. Done when: changes made on the fake server appear in the app after a sync.

  6. 06

    Sync when the connection returns

    ~2h

    Use NWPathMonitor to detect when the network becomes available and start a sync, and add pull-to-refresh on the list for manual syncs. Make sure only one sync runs at a time, for example with an actor. Show a small status label: Synced, Syncing…, Offline, or N conflicts. Done when: going offline, editing a note and coming back online uploads the change automatically.

  7. 07

    Resolve conflicts in the UI

    ~2h

    Show a badge on notes in the conflict state, and a screen that displays both versions side by side with Keep mine and Keep server buttons. Keep mine uploads the local version based on the server's latest version, and Keep server replaces the local copy. Either choice returns the note to synced and removes its ConflictCopy. Done when: a conflict created through the fake server can be resolved both ways.

  8. 08

    Test the sync logic

    ~2h

    Write XCTest tests against the fake URLProtocol server for: a normal upload, a 409 conflict, merging a download, a network error that leaves the note pending, and a note that can't be decrypted. Use an in-memory SwiftData container so tests never touch real data. Done when: all tests pass in Xcode.

Ready to build this?

Get a GitHub repo and start building. Your AI reviewer checks each step as you go.

~17.5h · 8 steps

Tech stack

SwiftUISwiftDataURLSession