CASE STUDY 04 / 05

CuongThai iOS

iPhone & iPad app

A native SwiftUI app for iPhone and iPad on the same REST API as the web: study by major, Apple Pencil notebooks, a book library, realtime messaging and calls.

No public link yet: test builds are distributed through TestFlight by invitation.

Status
In TestFlight testing — not yet on the App Store
Timeline
iOS repository since 19 Aug 2026 (first commit)
Platforms
iOS 17+ · iPhone and iPad
Role
One lead engineer: feature design, testing on simulator and real devices, TestFlight releases. Much of the Swift is written with an AI coding assistant (Claude Code), as the project README states.
01

Context & problem

Studying on a phone or iPad needs things a browser does poorly: pen handwriting, notifications, home-screen widgets, sharing from other apps, offline use.

The app has to pass App Store review: a private sign-in option, in-app account deletion, moderation of user content, no downloading of third-party media, correctly declared permissions and data use.

02

Who it serves

  • Students on their phones

    Study by major, follow courses, message, get notifications.

  • Learners on iPad

    Handwritten notebooks with Apple Pencil, a two-column layout.

03

Solution & key features

  • Study by major

    Choose a major and specialisation, filter subjects, view the subject map, download lessons for offline study on iPad.

  • Handwritten notebooks

    PencilKit notebooks; the AI can draw shapes and text onto the page as real pen strokes.

  • Realtime

    Messaging over Socket.IO, calls over WebRTC, push notifications.

  • Beyond the main app

    A home-screen widget (WidgetKit) and a share extension to send videos in from other apps.

04

Architecture

  1. Targets in the project

    • CuongThaiAppiPhone + iPad
    • TienWidgetWidgetKit
    • ChiaSeVideoShare extension
    • CuongThaiAdminiPad admin app
    Share the Shared/ folder
  2. Shared layer

    • SwiftUI viewsMVVM
    • APIClientasync/await, token refresh
    • KeychainCredentials
    • SwiftDataLocal data
    HTTPS · Socket.IO · WebRTC
  3. Server

    • cuongthai.com APIREST /api/v1, JWT
    • StoreKit verificationApple JWS signatures
The Xcode project is generated from project.yml with XcodeGen; every target shares the Shared/ layer and the same API as the web.
05

Hard technical decisions & trade-offs

01The server is the source of truth for access and paid plans

Problem
Trusting a client-reported Pro status invites users to patch the app to unlock it.
Decision
The app always asks the server; every Pro gate is enforced on the backend and the app only displays what the server returns. A plan bought on the web therefore works in the app immediately.
Trade-off
Some screens need the network to know access rights.

02Verifying App Store transactions up to Apple’s root certificate

Problem
StoreKit transactions are JWS carrying their own certificate chain. Verifying with the certificate inside the payload is a trap: an attacker self-signs a “bought 12 months” transaction and the code still says valid.
Decision
The backend cryptographically verifies leaf ← intermediate ← root, requires the root to match the pinned SHA-256 fingerprint of Apple Root CA – G3 (never taken from the payload), checks validity periods, and only then the signature.
Trade-off
When Apple rotates its root, the pinned fingerprint must be updated.

03App Store compliance built in before submission

Problem
The first version had placeholder screens, uploads that posted a fake URL, tokens in UserDefaults, a music module pulling audio from YouTube and a shop surface — all grounds for rejection.
Decision
Added Sign in with Apple, in-app account deletion, report/hide/block and a rules-consent screen; removed the music module and shop; built out the placeholder screens and real image upload; moved tokens to Keychain with a one-time migration for older installs.
Trade-off
The app has fewer features than the web where the rules do not allow them.

04An Xcode project generated from configuration

Problem
Xcode project files are hard to read and conflict easily under concurrent changes.
Decision
Targets, packages and settings are described in project.yml and the project is generated with XcodeGen.
Trade-off
New Swift files require re-running generation; forgetting shows up as “symbol not found”.
06

Real incidents & lessons

The app is not widely released, so these are bugs caught during development and testing, not incidents with end users.

Swallowed decoding errors: an empty profile instead of an error

What happened
Some screens showed empty data although the API returned everything.
Root cause
The JSON decoder used the default date strategy, so declaring a Date failed the whole decode; and `let` properties with a default value are silently skipped by Codable.
Fix & safeguard
API dates are declared as strings and parsed explicitly; API model fields are `var` and optional. Written down as project rules.

AI-drawn strokes present in the drawing but invisible on a real iPad

28/09/2026

What happened
Shapes the AI drew into a notebook did not appear on a real iPad.
Root cause
Stroke points of 1.2pt with pen ink — PencilKit on a real iPad renders nothing; point size is not pen width.
Fix & safeguard
Point size now follows the user’s sample stroke and never drops below 2.4pt. Lesson: test on real hardware.

Two sheets on one screen: one button silently did nothing

What happened
A button meant to open a sheet did nothing, with no error.
Root cause
SwiftUI only runs the last `.sheet(isPresented:)` when two are attached to the same view.
Fix & safeguard
Each screen uses a single `.sheet(item:)` driven by an enum.
07

Measured numbers

No user, revenue or client numbers here — only what can be counted from source code and the database. Each row says how it was counted so you can check it. Anything that cannot be counted is not shown.

iOS codebase (separate repository)

Counted by command on the codebase on 04/10/2026

Committed Swift filesgit ls-files "*.swift" | wc -l
343
Committed lines of Swiftgit ls-files -z "*.swift" | xargs -0 cat | wc -l
132,833
Commitsgit rev-list --count HEAD
263
iOS app targetsiOS target blocks in project.yml
4
Backend Apple-verification test filessrc/services/appleIAP/*.test.ts
3
08

Technology

App
Swift · SwiftUI · SwiftData · Keychain · XcodeGen
Apple frameworks
PencilKit · WidgetKit · StoreKit · Sign in with Apple · AVFoundation · Vision · PDFKit
Packages
Kingfisher · Socket.IO-Client-Swift · GoogleSignIn · WebRTC