A word definition and learning iOS app demonstrating ScreenStateKit - a comprehensive Swift state management toolkit with clean architecture and offline-first capability.
This project serves as a real-world demo for ScreenStateKit, showcasing the Three Pillars pattern (State + ViewModel + View) in a production-like app.
| Workflow | Status |
|---|---|
| Release to Testflight | |
| Test Runner iOS | |
| Test Runner macOS |
- Browse random words with pull-to-refresh
- Load more pagination
- Language filter (English, Spanish, French, etc.)
- Save words to library
- Offline fallback to cached words
- View saved words
- Delete words from library
- View word details
- Countdown timer challenge
- Pick the correct meaning from multiple choices
- Words sourced from user's library
- Score tracking
This project follows Clean Architecture principles with separate frameworks for each layer.
┌─────────────────────────────────────────────────────────────────────────────┐
│ MAIN APP │
│ (Definery target) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Composition Root │ │
│ │ - Wires all dependencies │ │
│ │ - Creates RemoteWithLocalFallback loader │ │
│ │ - Injects into ViewModels │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ UI Layer │ │
│ │ - SwiftUI Views (HomeView, LibraryView, QuizView) │ │
│ │ - ViewStates (ScreenState subclasses) │ │
│ │ - ViewModels (ScreenActionStore actors) │ │
│ │ - Uses ScreenStateKit patterns │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐
│ WordFeature │ │ WordAPI │ │ WordCache │
│ (Framework) │ │ (Framework) │ │ (Framework) │
├──────────────────┤ ├──────────────────┤ ├──────────────────────────┤
│ • Word (Model) │◄───│ • RemoteLoader │ │ • LocalWordLoader │
│ • Meaning │ │ • WordMapper │ │ • WordStorageProtocol │
│ • WordLoader │◄───│ • WordsEndpoint │ │ • LocalWord (DTO) │
│ (Protocol) │ │ │ │ • LocalMeaning (DTO) │
│ │ │ Depends on: │ │ │
│ │ │ → WordFeature │ │ Depends on: │
│ │ │ │ │ → WordFeature │
└──────────────────┘ └──────────────────┘ └──────────────────────────┘
│
▼
┌──────────────────────────┐
│ WordCacheInfrastructure │
│ (Framework) │
├──────────────────────────┤
│ • SwiftDataWordStore │
│ • InMemoryWordStore │
│ │
│ Depends on: │
│ → WordCache │
└──────────────────────────┘
User triggers refresh/load
│
▼
┌─────────────────────┐
│ RemoteWordLoader │ ──────► Fetch from API
└─────────────────────┘
│
├── [Success] ──► Cache to LocalWordLoader ──► Return [Word]
│
└── [Failure] ──► Fallback to LocalWordLoader.load() ──► Return cached [Word]
| Purpose | API | Endpoint |
|---|---|---|
| Random Words | random-word-api | GET /word?number=20&lang=en |
| Definitions | dictionaryapi.dev | GET /api/v2/entries/{lang}/{word} |
Definery/
├── README.md
├── Definery.xcodeproj/
│
├── WordFeature/ # Domain Layer (Framework)
│ ├── Word.swift # Domain model
│ ├── Meaning.swift # Value object for word meanings
│ ├── WordLoaderProtocol.swift # Protocol for loading words
│ └── WordCacheProtocol.swift # Protocol for caching words
│
├── WordFeatureTests/ # Domain tests
│ └── WordTests.swift
│
├── WordAPI/ # API Layer (Framework)
│ ├── HTTPClientProtocol.swift # HTTP client abstraction
│ ├── URLSessionHTTPClient.swift # URLSession implementation
│ ├── RemoteWordLoader.swift # Implements WordLoaderProtocol
│ ├── WordsEndpoint.swift # URL builder for APIs
│ ├── WordMapper.swift # Maps API JSON → Word
│ └── RemoteWord.swift # API DTO (Decodable)
│
├── WordAPITests/ # API tests
│ ├── RemoteWordLoaderTests.swift
│ ├── WordMapperTests.swift
│ └── WordsEndpointTests.swift
│
├── WordCache/ # Cache Layer (Framework)
│ ├── LocalWordLoader.swift # Cache use case (implements WordCacheProtocol)
│ ├── WordStorageProtocol.swift # Protocol for store implementations
│ └── Models/
│ ├── LocalWord.swift # Cache DTO for Word
│ └── LocalMeaning.swift # Cache DTO for Meaning
│
├── WordCacheTests/ # Cache tests
│ ├── CacheWordUseCaseTests.swift # Save tests
│ ├── LoadWordFromCacheUseCaseTests.swift # Load tests
│ └── Helpers/
│ ├── WordStorageSpy.swift # Test double
│ ├── TestHelpers.swift # Test utilities
│ └── Optional+Evaluate.swift # Result evaluation
│
├── WordCacheInfrastructure/ # Infrastructure Layer (Framework)
│ ├── SwiftDataWordStore.swift # SwiftData implementation
│ └── InMemoryWordStore.swift # In-memory implementation (testing)
│
├── WordCacheInfrastructureTests/ # Infrastructure tests
│ └── SwiftDataWordStoreTests.swift
│
└── Definery/ # Main App Target
├── DefineryApp.swift # App entry point
├── Composer/
│ └── WordLoaderComposer.swift # Wires remote + local with fallback
└── Features/
├── Home/
│ ├── HomeViewState.swift # ScreenState subclass
│ ├── HomeViewModel.swift # ScreenActionStore actor
│ └── HomeView.swift # SwiftUI view
├── Library/
│ ├── LibraryViewState.swift
│ ├── LibraryViewModel.swift
│ └── LibraryView.swift
├── Quiz/
│ ├── QuizViewState.swift
│ ├── QuizViewModel.swift # Uses Clock for countdown timer
│ └── QuizView.swift
└── WordDetail/
└── WordDetailView.swift
WordFeature (no dependencies)
▲
│
├──────────────┬──────────────────┐
│ │ │
WordAPI WordCache Main App
│ │ │
│ ▼ │
│ WordCacheInfrastructure │
│ │ │
└──────────────┴──────────────────┘
│
▼
ScreenStateKit
public struct Word: Equatable, Hashable {
public let id: UUID
public let text: String
public let language: String
public let phonetic: String?
public let meanings: [Meaning]
}public struct Meaning: Equatable, Hashable {
public let partOfSpeech: String
public let definition: String
public let example: String?
}public protocol WordLoaderProtocol {
func load() async throws -> [Word]
}public protocol WordCacheProtocol: Sendable {
func save(_ words: [Word]) async throws
}Implemented by: LocalWordLoader in WordCache framework
public protocol WordStorageProtocol: Sendable {
func deleteCachedWords() async throws
func insertCache(words: [LocalWord]) async throws
func retrieveWords() async throws -> [LocalWord]
}public struct LocalWord: Equatable {
public let id: UUID
public let text: String
public let language: String
public let phonetic: String?
public let meanings: [LocalMeaning]
}Purpose: Cache DTOs (LocalWord, LocalMeaning) create a protocol boundary between the cache layer and infrastructure layer, allowing SwiftData models to be independent of domain models.
Each feature follows the Three Pillars pattern from ScreenStateKit:
@Observable @MainActor
final class HomeViewState: ScreenState {
private(set) var words: [Word] = []
private(set) var selectedLanguage: Language = .english
private(set) var canLoadMore: Bool = true
}actor HomeViewModel: ScreenActionStore {
enum Action: ActionLockable, LoadingTrackable, Sendable {
case refresh
case loadMore
case saveWord(Word)
}
func binding(state: HomeViewState) { ... }
nonisolated func receive(action: Action) { ... }
}struct HomeView: View {
@State private var viewState: HomeViewState
@State private var viewModel: HomeViewModel
var body: some View {
// ...
.onShowLoading($viewState.isLoading)
.onShowError($viewState.displayError)
.task {
await viewModel.binding(state: viewState)
viewModel.receive(action: .refresh)
}
}
}- Create WordFeature framework
- Create WordLoaderProtocol
- Define Word model with properties
- Define Meaning model
- Add Equatable/Hashable/Sendable conformance
- Create WordAPI framework
- Create HTTPClient protocol and URLSessionHTTPClient
- Create RemoteWordLoader
- Create WordsEndpoint (URL builder)
- Create WordMapper (JSON → Word)
- Write RemoteWordLoaderTests
- Write WordMapperTests
- Write WordsEndpointTests
- Create WordCache framework
- Create LocalWordLoader (save/load use case)
- Create WordStorageProtocol
- Write CacheWordUseCaseTests (6 tests)
- Write LoadWordFromCacheUseCaseTests (7 tests)
- Create WordCacheInfrastructure framework
- Create SwiftDataWordStore (with in-memory option for tests/previews)
- Create ManagedWord and ManagedMeaning SwiftData models
- Write SwiftDataWordStoreTests (10 tests)
- Create WordLoaderComposer
- Implement remote-with-fallback pattern
- Wire dependencies in app
- Create HomeViewState + HomeViewModel + HomeView
- Add language filter
- Add pull-to-refresh
- Add load more
- Create LibraryViewState + LibraryViewModel + LibraryView
- Add delete functionality
- Navigate to word detail
- Create QuizViewState + QuizViewModel + QuizView
- Implement countdown timer with Clock protocol
- Multiple choice UI
- Score tracking
| Framework | Test Focus |
|---|---|
| WordFeature | Model equality, protocol contracts |
| WordAPI | Mapper tests, endpoint URL building, loader behavior |
| WordCache | Cache/load behavior, DTO mapping |
| WordCacheInfrastructure | SwiftData persistence, in-memory store |
Using swift-clocks to control time in tests:
// Production: uses ContinuousClock
// Tests: uses TestClock for deterministic timing
@Test
func countdown_decrements_every_second() async {
let clock = TestClock()
let viewModel = QuizViewModel(clock: clock)
await viewModel.startCountdown(from: 10)
await clock.advance(by: .seconds(3))
#expect(viewModel.remainingTime == 7)
}Using swift-snapshot-testing for UI verification:
@Test
func homeView_withWords_matchesSnapshot() {
let view = HomeView(
viewState: .preview(words: Word.samples),
viewModel: .preview
)
assertSnapshot(of: view, as: .image(layout: .device(config: .iPhone13)))
}- iOS 17+
- Swift 6
- SwiftUI
- SwiftData (persistence)
- ScreenStateKit (state management)
- swift-clocks (testable time)
- swift-snapshot-testing (UI snapshot tests)
- Swift Testing (unit tests)
Add these packages to your project:
// Package.swift or via Xcode SPM
dependencies: [
.package(url: "https://github.com/anthony1810/ScreenStateKit.git", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-clocks.git", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.15.0"),
]- Clone the repository
- Open
Definery.xcodeproj - Add package dependencies (ScreenStateKit, swift-clocks, swift-snapshot-testing)
- Build and run
- ScreenStateKit - State management toolkit
- Free Dictionary API - Word definitions
- Random Word API - Random words
- swift-clocks - Testable Swift concurrency clocks
- swift-snapshot-testing - Snapshot testing library