Architecture

From One Quiz App Template to 8 Country-Specific Exam Prep Apps

April 2026 · 9 min read

Building one quiz app is straightforward. Building eight, each targeting a different country's official exam with different question formats, passing thresholds, and regulatory bodies — that is an architecture problem. This is how a single SwiftUI template became the foundation for exam prep apps spanning the UK, Germany, Italy, France, and Switzerland.

The First App: CCSE Study Guide

It started with the CCSE Study Guide — a UK citizenship exam prep app. The Life in the UK test has a specific format: 24 multiple-choice questions, 45-minute time limit, 75% pass mark. The question bank is finite and well-documented.

The initial architecture was simple and correct for one app: a question model, a quiz engine that tracks answers and time, a results screen, and a progress tracker that remembers which questions you got wrong. Standard SwiftUI with @Observable state management. Nothing clever.

Then I looked at the German market.

The Pattern: Every Country Has Official Exam Prep

Germany has the Einbürgerungstest (citizenship) and Fischerprüfung (fishing licence). Italy has Patente B (driving licence) and Patente Nautica (nautical licence). France has the Examen Civique (citizenship). The UK has CSCS (construction safety). Switzerland has its own citizenship exam variants.

Every one of these exams shares the same fundamental structure: a fixed question bank, multiple-choice format, a passing threshold, and a time limit. The differences are in the content, the number of questions, the pass mark, and the language. That is a configuration problem, not a code problem.

The Template Architecture

The quiz template extracts everything that varies between apps into a configuration layer. The core engine stays identical across all eight apps. Here is what the configuration looks like:

struct QuizConfig {
    let examName: String
    let questionsPerSession: Int
    let timeLimit: TimeInterval       // seconds, or .infinity
    let passingPercentage: Double     // 0.75 for CCSE, 0.94 for CSCS
    let questionBank: [Question]
    let categories: [Category]
    let shuffleQuestions: Bool
    let showExplanations: Bool
}

Each app provides its own QuizConfig at launch. The template handles everything else: question presentation, answer tracking, timer display, results calculation, progress persistence, and review-wrong-answers mode.

The Apps

8
Apps from one template
5
Countries covered
38
Languages supported

Here is what shipped from the template:

  • CCSE Study Guide — UK Life in the UK citizenship exam. 24 questions, 75% pass mark.
  • CSCS Quiz — UK construction safety card. 50 questions, 94% pass mark. The highest pass threshold in the portfolio.
  • Einbürgerungstest — German citizenship. 310 official BAMF questions plus 16 state-specific variants.
  • Fischerprüfung — German fishing licence. 6 topic areas, 60 questions per exam.
  • Quiz Patente — Italian driving licence (Patente B). Over 7,000 official questions.
  • Patente Nautica — Italian nautical licence. Coastal and offshore variants.
  • Examen Civique — French citizenship exam. Oral interview preparation with 5 topic areas.
  • AutoCoach Drive — Driving theory prep for additional European markets.

What Varies Between Apps

The question bank is the obvious difference, but it is not the only one. Each exam has quirks that the template needed to accommodate:

State-specific content. Germany's Einbürgerungstest includes questions specific to each of the 16 Bundesländer. The app needs a state selector that filters the question bank accordingly. The template handles this through an optional regionFilter property — most apps set it to nil.

Category-based scoring. Some exams report scores by topic area, not just overall. The CSCS test covers 8 distinct areas (working at height, manual handling, fire safety, etc.), and candidates need to know which areas are weak. The template computes per-category accuracy out of the box, but only displays the breakdown when the config enables it.

Explanation depth. For the Italian Patente B, every question has a detailed explanation referencing the relevant article of the Codice della Strada (Highway Code). The Fischerprüfung explanations reference German fishing regulations. The template renders these identically — the content just differs.

Pass mark display. A 75% pass mark needs different visual treatment than a 94% pass mark. At 94%, nearly every wrong answer means failure — the results screen emphasizes this with a different color threshold. The template uses the passingPercentage config to dynamically set the pass/fail boundary on the progress indicator.

The Content Pipeline

The hardest part of shipping eight quiz apps is not the code. It is the content.

Each app needs an accurate, complete question bank sourced from official materials. The German BAMF publishes its 310 questions openly. Italy's Motorizzazione Civile publishes the full Patente B question bank. The CSCS questions come from the official CITB materials.

Questions are stored as JSON files bundled with each app:

{
    "id": "einb-47",
    "category": "politics",
    "region": null,
    "question": "Welches Recht gehört zu den Grundrechten?",
    "options": [
        "Waffenbesitz",
        "Faustrecht",
        "Meinungsfreiheit",
        "Selbstjustiz"
    ],
    "correct": 2,
    "explanation": "Art. 5 GG garantiert die Meinungsfreiheit."
}

Each app's question bank is a standalone JSON file. No shared database, no network dependency. The app works completely offline, which matters for users studying on commutes without reliable connectivity.

Localization: Harder Than It Looks

A German citizenship app needs to work in German, obviously. But it also needs English — because many naturalization candidates are not yet fluent in German. The same applies to every app: the UI chrome (buttons, labels, progress indicators) must be localized separately from the question content.

We use Xcode String Catalogs (.xcstrings) for the UI and support up to 38 languages across the portfolio. The question content stays in its original language — the Einbürgerungstest questions are in German because the exam is in German.

The tricky part is that SwiftUI's String(localized:) reads from Bundle.main, ignoring the SwiftUI environment locale. For apps that let users switch language at runtime (studying a French exam while your phone is set to English), we use a Bundle override pattern that intercepts localizedString(forKey:value:table:) and redirects to the correct .lproj bundle. This is documented in detail in Apple's localization guides, but in practice it requires explicit .id(refreshID) on the root view to force SwiftUI to re-render after a language switch.

Paid Variants: One Codebase, Two Products

Several of the quiz apps have paid one-time-purchase counterparts. CSCS Quiz has SitePass Card. Einbürgerungstest has CitizenPass ID. Examen Civique has CiviQuiz.

These paid variants use the same codebase but compile with a PAID_VERSION flag:

#if PAID_VERSION
// All content unlocked, no paywall, no subscription
static let accessLevel: AccessLevel = .full
#else
// Free tier: 50 questions, then paywall
static let accessLevel: AccessLevel = .limited(count: 50)
#endif

This is set in the XcodeGen project.yml under each target's SWIFT_ACTIVE_COMPILATION_CONDITIONS. The paid app never imports RevenueCat. The free app uses RevenueCat for subscription management with a 50-question free tier. Same quiz engine, same questions, same UI — the monetization layer is the only difference.

Why offer both? Because some users do not want subscriptions. They want to pay once and own the app. Offering a one-time purchase alternative at a higher price point captures this segment without cannibalizing the subscription revenue from users who prefer the lower commitment of a monthly plan.

What I Learned

Start with two apps, not one. Building a template from a single app produces a template that only works for that app. Building it alongside the second app forces real abstraction. CCSE and CSCS were built in parallel, and that pressure is what made the template genuinely reusable.

Content accuracy is the product. The code is a commodity. The question bank accuracy is what users review. A single wrong answer in a 310-question bank generates a one-star review and a support email. Verification of official question sources is non-negotiable and cannot be automated.

Offline-first is not optional for exam prep. Users study on trains, in waiting rooms, during lunch breaks. Any network dependency makes the app useless exactly when people want to use it. JSON bundles add app size but eliminate the failure mode entirely.

Region-specific content needs its own data model. Trying to handle the Einbürgerungstest's state-specific questions as a filter on a flat list worked initially but became unwieldy. A proper region field on the question model, with an optional region selector in the UI, scaled cleanly to any exam with regional variants.

The pass mark defines the UX. An app where 75% is passing needs different psychological framing than one where 94% is passing. At 94%, every practice session should feel rigorous. At 75%, the emphasis can be on building confidence. The results screen copy, the progress colors, and the suggested study actions all key off this single number.

Key Takeaways

  • Quiz apps are configuration problems, not code problems. Extract what varies (questions, thresholds, categories) into data. Keep the engine identical.
  • Build the template alongside the second app — never retroactively from the first.
  • Content accuracy drives App Store ratings more than UI polish or feature count.
  • Offline JSON bundles beat networked question banks for exam prep use cases.
  • Paid one-time-purchase variants with PAID_VERSION compile flags capture a real market segment with zero incremental code.
  • Localize UI chrome broadly, keep question content in the exam's language. Users expect the exam questions in the language they will face.

Download: Quiz App Template Checklist

The configuration checklist I use when spinning up a new exam prep app from the template. Includes question bank format, config schema, and launch steps.

Related Posts

Building 27 Apps with One Shared Swift Package

The shared infrastructure that powers all 27 apps, including these quiz apps.

How to Pass the CSCS Card Test First Time

The exam guide for one of the apps built on this template.

Evgeny Goncharov - Founder of TechConcepts, ex-Yandex, ex-EY, Darden MBA

Evgeny Goncharov

Founder, TechConcepts

I build automation tools and custom software for businesses. Previously at Yandex (Search) and EY (Advisory). Darden MBA. Based in Madrid.

About me LinkedIn GitHub
← All blog posts

Need help with iOS or macOS app architecture?

15 minutes. No pitch. Just honest advice on whether I can help.

Book a Call
Book Free Audit