MadLibs

A SwiftUI word game app for iOS 17+ with two distinct play modes: Classic (fill-in-the-blank) and Creative (word-bank-driven story generation). Ships with 30 story templates across 3 categories, a custom template editor, word bank management, biometric + PIN authentication, and 6 user-customizable themes with ambient animations. ~5,881 LOC Swift. Zero third-party dependencies.

Overview

MadLibs is the word game genre brought to mobile as a pure-Swift, offline-first iOS app. Players choose between Classic Mode — where they fill in blanks one at a time before revealing the finished story — and Creative Mode, where a user-built word bank drives dynamic story generation without any AI or network calls.

The app graduated from v1.0 (core gameplay) through v1.4 (theme system, template editor, haptics, biometric auth), landing in maintenance mode at v1.4. v1.5 (Classic/Creative UX refinement) is planned but not started.

GitHub: irishrocker1125/MadLibs

Tech Stack

TechnologyVersionPurpose
Swift5.xPrimary language
SwiftUIiOS 17+Declarative UI (@Observable, NavigationStack)
XcodeLatestBuild system / IDE
LocalAuthenticationBuilt-inFace ID / Touch ID
Security (Keychain)Built-inSecure PIN storage via KeychainService
UserDefaultsBuilt-inPersistence — stories, word banks, templates, settings
XCTestBuilt-inUnit tests (MadLibsTests target, ~50-80% coverage)
FoundationBuilt-inCore types, JSON encoding/decoding

No third-party packages. Pure Apple frameworks.

Architecture

Feature-based module organization with shared Components/ and Core/ layers.

MadLibs/MadLibs/
  MadLibsApp.swift              # App entry point
  ContentView.swift             # Root view
  RootView.swift                # Navigation root
  Models/
    WordType.swift              # 11 word types with examples
    StoryTemplate.swift         # Template + StorySegment DSL
    WordBank.swift              # Word collection with smart selection
  Core/
    Theme/
      AppTheme.swift            # Customizable color palette (6 themes)
      ThemeManager.swift        # @Observable theme state
    Authentication/
      AuthenticationManager.swift   # Biometric + PIN auth
      KeychainService.swift         # Keychain credential storage
  Components/
    ScaleButtonStyle.swift          # 0.97-scale press feedback
    Buttons/MesmerizingButtonStyle  # Animated gradient button
    Cards/MesmerizingCard           # Ambient card container
    Inputs/MesmerizingTextField     # Styled text input
    Effects/AmbientBackground       # Animated gradient background
  Data/
    SampleStories.swift         # 30 templates (10 everyday/adventure/fantasy)
  Extensions/
    Color+Hex.swift             # Hex-to-SwiftUI Color
    Animation+Presets.swift     # Standardized animation curves
MadLibs/MadLibsTests/          # XCTest unit tests

State management: @Observable macro (iOS 17) on service classes (AuthenticationManager, ThemeManager). No Combine, no ObservableObject.

Navigation: NavigationStack + typed Route enum for type-safe routing.

Template DSL: StorySegment enum (.text / .blank) represents templates as alternating text and fill-in positions.

Key Features

  • Classic Mode — Fill blanks sequentially, reveal completed story
  • Creative Mode — Word bank drives story generation; auto-generates when only one template matches
  • Custom Template Editor — Create and store user-defined templates; blank insertion uses “insert after focused segment” workaround (SwiftUI TextField doesn’t expose cursor position)
  • Word Bank — Single cumulative collection, auto-saved on mutation; 11 WordType categories (noun, verb, adjective, etc.)
  • 30 Built-in Templates — 10 everyday, 10 adventure, 10 fantasy; merged with custom templates via allTemplates computed property
  • Theme System — 6 themes with hue selection; CodableColor wrapper bridges SwiftUI.Color to Codable for UserDefaults persistence
  • Biometric + PIN Auth — Face ID / Touch ID via LocalAuthentication with PIN fallback stored in Keychain
  • Haptics — Medium impact for primary actions, light for secondary

Key Decisions

DecisionRationaleOutcome
Two separate play modesDistinct user mental models: fill-in vs. generativeGood
SwiftUI only, iOS 17.0 target@Observable, NavigationStack — clean modern architectureGood
State-driven view switchingSimpler than deep navigation for contained gameplay flowGood
[WordType: [String]] word bankMultiple words per type enables creative generationGood
generateStory static on WordBankKeeps generation logic near word dataGood
UserDefaults for persistenceSufficient for data sizes; no CoreData setup neededGood
Single word bank per userSimpler mental model than multiple named banksGood
Auto-save on word mutationNo explicit save button; prevents data lossGood
allTemplates combined sourceMerges built-in + custom in single computed propertyGood
Medium detent sheet for blank editingInline editing feel without full-screen modalGood
CodableColor wrapperBridges SwiftUI.Color to Codable for theme persistenceGood
Biometric + PIN dual authFace ID/Touch ID with PIN fallback for full coverageGood

Patterns Established

PatternDescriptionReusable
ScaleButtonStyle0.97-scale press feedback, used in 4+ viewsYes
MesmerizingButtonStyleAnimated gradient button with ambient glowYes
MesmerizingCardCard with ambient gradient backgroundYes
AmbientBackgroundAnimated gradient atmospheric layerYes
AppTheme + ThemeManagerUser-customizable theme with hue selectionYes
@Environment injectionServices injected via .environment() — clean DIYes
NavigationStack + Route enumType-safe routing with Hashable enum casesYes
StorySegment DSLAlternating .text / .blank template representationYes
Observable persistence service@Observable CRUD with UserDefaults auto-syncYes

Technical Debt

ItemPriorityNotes
Classic Mode input flowMediumKeyboard behavior and field interactions not yet polished
Creative Mode word collection UXMediumTemplate matching clarity needs improvement
Template Editor blank positioningMediumInsert position not always intuitive
No iCloud syncLowData is device-local only
No sharing featuresLowExplicitly out of scope

Lessons Learned

  • @Observable (iOS 17) eliminates ObservableObject + @Published boilerplate with less ceremony
  • SwiftUI TextField doesn’t expose cursor position — design insert UX around segment focus instead
  • Template generation without AI: 30 templates across 3 categories provides solid variety; start with ~10 per category
  • Auto-generate when only 1 template matches — skip selection screens when the result is predetermined
  • Biometric lockout requires PIN fallback — always provide a secondary auth method
  • gray-matter-style: CodableColor shows the pattern for bridging non-Codable platform types to UserDefaults