The iOS Accessibility Handbook
Here we will define the core logic of our app: going through chapters and levels.
Our Models will be a Chapter component and a Level component.
As we want to define various levels with different behaviours, we will be using classes and subclassing for Levels.
class Chapter {
public let index: Int public let title: String? public let levels: [Level]
init(index: Int, title: String? = nil, levels: [Level]) { self.index = index
self.title = title
self.levels = levels
} }
class Level {
typealias Answer = String
public let answers: [Answer] public let validAnswers: [Answer]
init(answers: [Answer], validAnswers: [Answer]) { self.answers = answers
self.validAnswers = validAnswers
} }
We will implement our game mode types by subclassing the Level class:
We will first define our levels in a LocalLevelFactory component.
As always, we will use It behind the LevelFactory protocol to allow replacing it with levels defined on some backend later if needed.
Do not mind the used levels here, we will only use them to show the game principle.
As an example, I will use a set of 4 Chapters with 5 levels inside.
Here you can take some time to customise it with any chapters and levels you can imagine!
class ClosedQuestionLevel: Level {
// here we will add additional level specific behaviour
}
class LocalLevelFactory: LevelFactory {
static func build() -> [Chapter] { return LocalLevelFactory.chapters
}
static let chapters = [ Chapter(
index: 1,
title: “Ice Cave”,
description: L10n.chapter1Description,
keywords: [“door”, “trap”, “bridge”, “bats”, “lever”], levels: [
// Closed Questions
ClosedQuestionLevel(question: L10n.cq1, answers: [L10n.cq1a1, L10n.cq1a2, L10n.cq1a3].shuffled(), validAnswers: [L10n.cq1a1]),
ClosedQuestionLevel(question: L10n.cq2, answers: [L10n.cq2a1, L10n.cq2a2, L10n.cq2a3].shuffled(), validAnswers: [L10n.cq2a3]),
ClosedQuestionLevel(question: L10n.cq3, answers: [L10n.cq3a1, L10n.cq3a2, L10n.cq3a3].shuffled(), validAnswers: [L10n.cq3a1]),
ClosedQuestionLevel(question: L10n.cq4, answers: [L10n.cq4a1, L10n.cq4a2, L10n.cq4a3].shuffled(), validAnswers: [L10n.cq4a2]) ]
),
Chapter( index: 2,
title: “The Lost City”,
description: L10n.chapter2Description, keywords: [“bear”, “wolves”, “spikes”, “water”], levels: [
// Rotor Levels RotorLevel(
clues: [L10n.chapter2Cq1a1, L10n.chapter2Cq1a2, L10n.chapter2Cq1a3, L10n.chapter2Cq1a4], hints: [L10n.chapter2Cq1h1],
answers: [“4402”, String(randomDigits(4)), String(randomDigits(4))].shuffled(),
validAnswers: [“4402”]
), RotorLevel(
clues: [L10n.chapter2Cq2a1, L10n.chapter2Cq2a2, L10n.chapter2Cq2a3],
answers: [L10n.chapter2Cq2aa1, L10n.chapter2Cq2aa2, L10n.chapter2Cq2aa3, L10n.chapter2Cq2aa4].shuffled(), validAnswers: [L10n.chapter2Cq2aa1]
) ]
),
Chapter( index: 3,
title: “Hidden Valley”,
description: L10n.chapter3Description,
keywords: [“climbing”, “waterfall”, “temple”, “door mechanism”], levels: [
Page 10 of 22
// Open Questions OpenQuestionLevel(
question: L10n.chapter3Q1,
validAnswers: [L10n.chapter3A1] ),
OpenQuestionLevel(
question: L10n.chapter3Q2, validAnswers: [L10n.chapter3A2]
), OpenQuestionLevel(
question: L10n.chapter3Q3,
validAnswers: [L10n.chapter3A3] ),
OpenQuestionLevel(
question: L10n.chapter3Q4, validAnswers: [L10n.chapter3A4]
) ]
),
Chapter( index: 4,
title: “The Hidden Tomb”,
description: L10n.chapter4Description,
keywords: [“puzzle”, “blocks”, “walls”, “jewels”, “tiles”], levels: [
// Search Levels SearchLevel(
levelDescription: L10n.chapter4L1desc, levelActions: [
(description: L10n.chapter4Cq1desc, actions: [L10n.chapter4Cq1a1, L10n.chapter4Cq1a2], results: [L10n.chapter4Cq1r1, L10n.chapter4Cq1r2]),
(description: L10n.chapter4Cq2desc, actions: [L10n.chapter4Cq1a1, L10n.chapter4Cq1a2], results: [L10n.chapter4Cq1r2, L10n.chapter4Cq1r1]),
(description: L10n.chapter4Cq3desc, [L10n.chapter4Cq1a1, L10n.chapter4Cq1a2], results: [L10n.chapter4Cq1r1, L10n.chapter4Cq1r2]) ],
validActions: [L10n.chapter4Cq1a2, L10n.chapter4Cq1a1, L10n.chapter4Cq1a2] )!,
SearchLevel(
levelDescription: L10n.chapter4L2desc, levelActions: [
(description: L10n.chapter4Cq2desc1, actions: [L10n.chapter4Cq2a1, L10n.chapter4Cq2a2, L10n.chapter4Cq2a3, L10n.chapter4Cq2a4], results: [L10n.chapter4Cq2r1, L10n.chapter4Cq2r2, L10n.chapter4Cq2r1, L10n.chapter4Cq2r1, L10n.chapter4Cq2r1]),
(description: L10n.chapter4Cq2desc2, actions: [L10n.chapter4Cq2a1, L10n.chapter4Cq2a2, L10n.chapter4Cq2a3, L10n.chapter4Cq2a4], results: [L10n.chapter4Cq2r1, L10n.chapter4Cq2r1, L10n.chapter4Cq2r2, L10n.chapter4Cq2r1]),
(description: L10n.chapter4Cq2desc3, actions: [L10n.chapter4Cq2a1, L10n.chapter4Cq2a2, L10n.chapter4Cq2a3, L10n.chapter4Cq2a4], results: [L10n.chapter4Cq2r1, L10n.chapter4Cq2r2, L10n.chapter4Cq2r1, L10n.chapter4Cq2r1])
],
validActions: [L10n.chapter4Cq2a2, L10n.chapter4Cq2a3, L10n.chapter4Cq2a2] )!
] )
] }
The L10n class is used to translate the dialogs of the game in different languages (english and french currently supported for now) – see https://github.com/ SwiftGen/SwiftGen for more info.
We will describe each Level subclasses later in this handbook.
After we have defined the game models, that is Chapters and Levels, we can define the GameFlow, based on current chapter and current level indexes, we will delegate the routing between screens to another component.
Thus lets define the Game Flow protocol:
And the router delegate protocol:
protocol GameFlowProtocol {
func getCurrentChapterIndex() -> Int func getCurrentLevelIndex() -> Int
func start()
func restart()
func validate(_ answers: [Level.Answer]) func validateChapter()
}
protocol Router {
func routeToChapter(_ chapter: Chapter, isNewChapter: Bool) func routeToLevel(_ level: Level)
func routeToResults()
}
The iOS Accessibility Handbook
The GameFlow class will hold a reference to the router:
And be constructed with the different chapters, and the current progress (so it is
agnostic for any other dependency):
The rest of the component source code is pure logic of delegating the screen routing based on player’s progress so we will not describe it in details.