Sample CodeiOS 16.0+, iPadOS 16.0+, Mac Catalyst 16.0+Reviewed 2026-07-21View on Apple Developer

WWDC22 Challenge: Learn Switch Control through gaming

At a glance

Item Summary
Purpose Teach Switch Control through a memory-matching game whose cards act through accessibility actions.
Architecture A code-light SwiftUI app where GameBoardView owns an observable Game; Game mutates value-type Deck and Card models.
Main pattern View-model ownership with explicit delayed match/mismatch transitions and accessibility announcements.
Boundary One app target; no extension, persistence, network layer, or custom protocol.

Project structure

SwitchControlChallenge/
├── Models/                 # Game, Deck, Card, Symbol
├── Views/
│   ├── GameBoardView.swift
│   ├── CardView.swift
│   └── Intro/
└── SwitchControlChallengeApp.swift

The folder split cleanly separates game mutation from declarative rendering.

Overall architecture

Reference code

SwitchControlChallenge/Views/GameBoardView.swift:23 — interaction is routed through accessibility, not tap handling (abridged).

ForEach(viewModel.cards[range]) { card in
    CardView(card: card)
        .onTapGesture {
            // Only playable via Switch Control!
        }
        .accessibilityAction {
            viewModel.select(card)
        }
}

Ownership and state

Ownership evidence

SwitchControlChallenge/Models/Game.swift:11Game is the single mutation authority (abridged).

class Game: ObservableObject {
    @Published private(set) var deck: Deck
    @Published var score = 0
    @Published var didWinGame = false
    private var currentlySelectedCardIndex: Int?

    func select(_ card: Card) {
        deck.cards[requestedCardIndex].isFaceUp = true
        // resolve match or mismatch
    }
}

GameBoardView owns the Game lifecycle through @StateObject; CardView receives snapshots and sends actions back to the owner.

Class and protocol design

Game adopts the framework ObservableObject protocol. Deck and Card are mutable value types; Card adopts Identifiable for SwiftUI diffing. There is no app-defined protocol because the small view hierarchy depends directly on one game behavior surface (select and reset).

Access control

Symbol Access Rationale
deck setter private(set) Views can render cards but only Game can replace the deck (SwitchControlChallenge/Models/Game.swift:13).
Selected index and derived counts private They are game-rule implementation details.
score, didWinGame internal mutable The view observes them; the sample does not enforce read-only access.
Models/views internal default Single-target teaching app, with no public surface.

No public or fileprivate boundary is needed. The strongest guard is on the deck’s setter because replacement must remain coordinated.

Logic ownership and placement

Logic Owner Placement reason
Match selection, score, delayed flips Game All rules mutate the same published state (SwitchControlChallenge/Models/Game.swift:29).
Pair construction and shuffle Deck Value-level collection operations stay with the collection (SwitchControlChallenge/Models/Deck.swift:14).
Accessibility label Card It derives only from card state (SwitchControlChallenge/Models/Card.swift:20).
Grid and presentation state GameBoardView Sheets/popovers and layout are screen concerns.

Design patterns

Pattern Evidence Purpose
MVVM-like split GameBoardView owns Game Rules stay out of SwiftUI body code.
Observable state Published deck/score/win state Drives automatic rendering.
Value model Deck and cards are structs Simple local mutation and identity snapshots.
Accessibility command .accessibilityAction invokes select Makes Switch Control the intended input path.

Naming conventions

  • Domain nouns are short and singular (Game, Deck, Card, Symbol).
  • Boolean state uses is... / did... (isFaceUp, isMatched, didWinGame).
  • View types mirror their domain (GameBoardView, CardView).

Architecture takeaways

  • Let the StateObject owner control the lifetime of an observable game model.
  • Expose value snapshots to views and route mutations back through commands.
  • Put derived accessibility labels on the model when they depend only on model state.
  • The one-second tasks are game feedback timing, not a general scheduler abstraction.

Source map

Source Role
SwitchControlChallenge/SwitchControlChallengeApp.swift:10 App entry point
SwitchControlChallenge/Views/GameBoardView.swift:14 Game owner
SwitchControlChallenge/Views/CardView.swift:28 Card accessibility semantics
SwitchControlChallenge/Models/Game.swift:42 Delayed match transition
SwitchControlChallenge/Models/Deck.swift:28 Shuffle and index mutation
SwitchControlChallenge/Models/Card.swift:10 Identifiable card value