Keentune

iOS Development curriculum

24 chapters
·
184 concepts
·
free
Everything the adaptive question bank can teach and test in iOS Development, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the iOS Development guide
A free 16-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. The app, its scenes and the lifecycle
@main on an App type whose body returns Scenes; there is no main.swift to edit
a WindowGroup can be instantiated more than once, so its @State is per window, not app-wide
@Environment(\.scenePhase) reports active, inactive and background; inactive is the last reliable moment to save
a backgrounded app is suspended within seconds and gets no CPU until the user returns to it
@UIApplicationDelegateAdaptor is how a SwiftUI app still receives push tokens and other UIKit delegate callbacks
the OS relaunches a killed app expecting the previous screen back, which forces UI state to be representable as data
B. The SwiftUI view: a description, not an object
a View is a struct SwiftUI creates and throws away; you never retain or mutate a view object
body may run any number of times, so a network call, a random value or a state write inside it is a bug
the View protocol is @MainActor, so body and every update it triggers are main-thread work
a modifier returns a new view wrapping the old one, which is why chains compose and nothing is edited in place
.padding().background(.red) colors the padding; reversing the two does not
a value set with .environment applies to the subtree below it, never to siblings or ancestors
a second view layered over or behind the first, and sized by it rather than the reverse.
which edges receive the space, and why the edge-set shorthand is not decoration.
Group applies a modifier to several views at once without introducing any layout of its own.
C. State and data flow
every value has exactly one owner; everyone else gets a binding or a read, never a second copy
@State keeps its value in framework-managed storage, so it survives the view struct being rebuilt
$value projects a Binding the child can write; handing over a plain copy silently discards the edit
@Observable supersedes ObservableObject plus @Published for reference-type models
a view re-renders only for the properties its body actually read, not for every change on the object
the view that creates an @Observable model holds it in @State; one passed in needs no wrapper at all
@ObservedObject var vm = VM() rebuilds the model on every parent update; @StateObject creates it once
the injection is resolved at runtime, so a forgotten .environmentObject crashes rather than failing to compile
@Bindable produces $ bindings from an @Observable the view does not own
running a side effect when one specific value changes, rather than on every rebuild.
keyboard focus is state you can read and write, so it can be driven programmatically.
D. View identity, lifetime and the async hooks
with no explicit id, a view's identity is its position in the view tree
changing .id() is a removal plus an insertion, so the old view's @State is discarded
state does not survive a move between the two arms of an if/else, because they are different views
a duplicated or index-based id mismatches rows, corrupting both state and animation
it can fire repeatedly, and inside a lazy container it fires as the row scrolls into view
.task starts async work when the view appears and cancels it when the view goes away
E. The layout system
the parent proposes a size, the child chooses its own, the parent then places it
.frame does not resize the child; it inserts a parent that proposes a size and aligns whatever comes back
ideal, zero and infinite proposals get different answers from the same view, which is what sizes it to fit
an HStack satisfies fixed-size children, then divides the remainder among the flexible ones
a Spacer claims all remaining space along the stack's axis, which is why one Spacer pushes everything
it accepts the entire proposal and top-leading-aligns its content, surprising people who expected it to hug
.fixedSize() asks the child for its ideal size and refuses to squeeze it further
layout is inset by the safe area unless a view explicitly ignores it, and the keyboard is a safe-area change too
which container stacks along which axis, and what overlapping instead of stacking means.
a stack's alignment parameter positions children on the CROSS axis, not the stacking one.
uniform gaps between children, which is a different control from padding around the group.
shifting a guide is how views that are not siblings line up with each other.
F. Lists, scrolling and render cost
rows are created near the viewport, unlike a VStack that builds every child immediately
a lazy container defers child creation; a plain stack is correct only for small, fixed content
onDelete is the built-in edit affordance; swipeActions is the general one, on either edge
a ScrollView offers unlimited space on its axis, so children must define their own size
date formatting, decoding or image work inside a row body runs again on every scroll recompute
grouping rows under a header, which changes structure and accessibility, not only appearance.
G. Navigation and presentation
a link pushes a VALUE, and navigationDestination maps that value's type to a view
binding a path collection makes the stack programmatic, restorable and deep-linkable
registering the destination on the pushed view instead of within the stack silently does nothing
a sheet is drag-dismissible; a full-screen cover must supply its own way out
a presented view closes with @Environment(\.dismiss) rather than a "close" binding passed down
onOpenURL has to translate a URL into path values, which only works if navigation state is data
peer top-level sections the user switches between, as opposed to a push/pop stack.
H. Animation and gestures
SwiftUI interpolates between two states; there is no imperative "animate this view" call
it animates every change caused by the state mutation inside its closure, including in other views
.animation(_:value:) animates only when that specific value changes, which is how you stop unrelated motion
.transition applies when a view enters or leaves, so the container change itself must be animated
one namespace plus one id makes two separate views read as a single element moving
nested gestures resolve through simultaneously, exclusively or highPriorityGesture, not by nesting order alone
I. UIKit: view controllers and the responder chain
it fires when the view hierarchy loads, not on each appearance, so per-visit work does not belong there
one runs before the transition and one after, which decides where animation and analytics go
frames are finally valid here, and it can run many times per appearance
touching an outlet in init crashes, because the view has not been loaded yet
addChild, insert the view, then didMove(toParent:), or lifecycle events stop forwarding
an unhandled event travels view → superview → view controller → window → application
a control with a nil target sends its action up the responder chain to whoever implements it
an override that skips super leaves the superclass's own work undone.
one object hands decisions and callbacks to another it does not own.
modal presentation covers the screen; a push adds to an existing stack.
the container that owns a stack of screens and the push/pop transitions between them.
the container that owns peer screens with no ordering between them.
the real trade-offs — visual editing and merge conflicts against explicit, reviewable code.
J. Auto Layout
every constraint is attribute1 = multiplier × attribute2 + constant, with a relation and a priority
too few constraints leaves the frame undefined; conflicting required ones break at runtime
the engine breaks the lowest-priority constraint first and logs which one it dropped
hugging resists growing past the content, compression resistance resists shrinking below it
a label or button sizes itself from its content, so it needs fewer constraints than a bare view
leaving it true on a programmatic view manufactures constraints that conflict with yours
constrain to the guide rather than the superview edge, or content lands under the notch and home indicator
K. Table and collection views
a dequeued cell still holds the previous row's data until every field is reset
the hook for clearing state, cancelling in-flight work and dropping the old image
a late response paints a recycled cell unless the request is cancelled or the index re-checked
snapshots of unique identifiers replace manual index-path bookkeeping and animate the difference for you
automatic row height only works when the content view is constrained edge to edge vertically
arbitrary layouts against a single scrolling column of full-width rows.
L. SwiftUI and UIKit interop
makeUIView builds the view once; updateUIView pushes each new SwiftUI state into it
the Coordinator is where the delegate, data source and target-action live, because a struct cannot be one
UIHostingController puts a SwiftUI view into a UIKit hierarchy as an ordinary child controller
UIKit changes reach SwiftUI only through a binding or a coordinator callback, never by mutating the struct
mutating SwiftUI state from inside updateUIView re-enters the update cycle and can spin
M. Concurrency and the main actor
touching UIKit or SwiftUI state off the main actor is undefined behavior, not merely slow
@MainActor on a type or function moves that work onto the main actor at compile time, replacing a dispatch call
other work runs during the await, so anything read before it may be stale after it
cancelling a Task only sets a flag; work that never checks it or calls a cancelling API keeps going
a Task {} started inside a view inherits main-actor isolation, so it does NOT move work off the main thread
synchronous file, JSON or image work during a view update drops frames the profiler will show as a hitch
an actor serialises access to its own mutable state, which is what removes the race.
unsynchronised read-modify-write from two tasks corrupts memory rather than merely reordering.
a built-in view that owns the fetch, the cancellation and the placeholder states.
N. Networking with URLSession
a 404 or a 500 is a successful transfer; only transport failures throw, so you must inspect the response
a DecodingError names the failing key, and a type or optionality mismatch is the usual cause
URLComponents percent-encodes query values that string concatenation quietly corrupts
a response can be served from the URL cache unless the request's policy forbids it, which hides server changes
a background configuration hands the transfer to the system, which relaunches the app to deliver the result
cleartext HTTP is refused unless an Info.plist exception names that domain
what the API is actually responsible for, and what it deliberately leaves to you.
a decoding strategy converts key conventions without hand-written CodingKeys.
O. Persistence: SwiftData, Core Data, defaults, Keychain and files
it is an unencrypted plist inside the app container, so an auth token stored there is readable
a Keychain item's accessibility class decides whether it can be read while the device is locked, and whether it migrates
@Model makes a class persistable and observable at once, so a fetched object drives the view directly
the ModelContainer owns the store; the ModelContext is the scratchpad where changes accumulate
@Query re-runs and re-renders when the store changes, which is why it belongs in a view and not a model
a managed object belongs to its context's queue and must be passed by object ID, not by reference
additive schema changes migrate automatically, while a rename or a split needs an explicit mapping
Documents is backed up, Caches can be purged under pressure, and tmp can vanish between launches
how a type maps to and from an external representation, and where that mapping can fail.
how the current framework relates to the one it succeeds.
preferences against a queryable store with relationships — size and query needs decide.
two contexts saving conflicting edits need a declared winner, not a crash.
P. Images, memory and profiling
memory cost is pixels × bytes per pixel, so a small JPEG can still cost tens of megabytes decoded
decode to the display size instead of loading full resolution and scaling it down in a view
an escaping closure stored on an object and capturing self strongly keeps that object alive forever
roughly 16 ms at 60 Hz and 8 ms at 120 Hz; anything longer is a visible hitch, not a rounding error
a sampling profiler shows the real hot stack, which is rarely the one that was suspected
the system reclaims memory by killing the app, so respond to warnings rather than assuming a crash log
Q. Background execution, notifications and permissions
the system decides when a refresh task runs, based on usage and conditions; you request, you do not schedule
a background task has a deadline, and its handler must save and stop before the system kills the process
one is scheduled on device with no server, the other is delivered by APNs and needs a device token
the notification permission alert appears once per install; after a denial only Settings can change it
a notification arriving while the app is open shows nothing unless the delegate explicitly opts in
a content-available push is best-effort and rate-limited, never a guaranteed wake-up
a missing NS…UsageDescription crashes the app the first time the protected API is touched
the object you ask for permission, schedule local notifications on, and handle responses through.
R. Accessibility
an element needs a name, its current value and a trait describing what kind of control it is
an image that carries no information should be hidden from assistive technology rather than labeled
merging a row into a single element stops VoiceOver from reading five disconnected fragments
text must scale with the user's chosen size, and a hard-coded height clips it at the larger settings
the HIG's 44×44 point minimum hit area, which is independent of how small the glyph is drawn
meaning must survive color blindness and grayscale, so pair color with text, shape or position
the setting is a promise: substitute a cross-fade for large parallax or zoom transitions
the screen reader that speaks the interface, which is what every accessibility API ultimately feeds.
S. Testing and debugging
a unit test runs inside the app's process; a UI test drives the app from a separate one and sees only the UI
an async test awaits the call directly, while an expectation is for callback-based APIs
a test that passes only because the timeout is generous is already flaky, just not yet failing
depending on a protocol rather than a concrete type is what lets a test replace the network or the store
UI tests query elements by identifier, which makes identifiers infrastructure rather than decoration
a UI test controls starting state by launching with flags instead of reaching into the app
T. Signing, capabilities and App Store distribution
a certificate proves who signed the build; a provisioning profile says where it may run and with which entitlements
enabling a capability changes the App ID and therefore the profile; it is never only a checkbox in Xcode
a development profile enumerates devices, a distribution profile does not, which is why one build installs and the other refuses
the marketing version is what users see; the build number must increase with every upload of that version
external testing needs a review pass, internal testers do not, which changes how fast a fix reaches testers
declared data types and API reasons must match real behavior, and a mismatch is a rejection, not a warning
the most common rejection: placeholder content, broken links or a build that crashes on review.
digital goods consumed in the app must go through the store's payment system.
tracking across apps and sites owned by other companies requires the system prompt first.
an app may not download executable code that changes what was reviewed.
U. Human Interface Guidelines expectations
the back swipe, the tab bar and pull-to-refresh already mean something, so redefining them costs the user, not Apple
a modal should be short, single-purpose and obviously exitable, which is why deep stacks inside sheets feel wrong
destructive actions are confirmed or undoable, and are never the default button in an alert
system and semantic colors follow dark mode and increased contrast; a hard-coded hex value does not
adaptivity keys off size class, orientation and dynamic type, never off a device-name check
V. Framework awareness: which technology owns which job
a declarative stream API that predates async/await and still backs @Published.
a published property notifies subscribers, which is what an ObservableObject rebuild rides on.
when-in-use against always, and the fact that the user, not the request, decides.
maps, annotations and place search as a first-class SwiftUI view.
capture, playback and editing of time-based audio and video.
in-app purchases, subscriptions and their transaction state.
timeline-driven views that render outside the app.
exposing app actions to the system so Siri and Shortcuts can run them.
the layer tree the UI is actually composited from.
W. Xcode: targets, schemes and what the Simulator cannot tell you
WHAT is built: a product and the files, settings and dependencies that make it.
WHICH targets run for which action, and under which configuration.
HOW it is built: optimisation, assertions and the code stripped from a release.
the Simulator has no real hardware, sensors, memory pressure or cellular network.
what the store still slices per device, and what has been retired.
X. Structuring an app: layers, ownership and broadcast
presentation state and the actions a screen can take, with no view types in it.
the data and the rules that own it, independent of any screen.
a shared instance is convenient and untestable; a protocol parameter is neither.
one-to-many in-process messaging where the sender does not know the receivers.
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about iOS Development practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC