Accessibility: Building UI That Works for Everyone

Learn to make Objective-C UIKit interfaces usable with VoiceOver and other assistive technologies by setting accessibility labels, hints, traits, and values; supporting Dynamic Type, keyboard and Switch Control navigation, sufficient contrast, and Reduced Motion; and manually wiring up accessibility on custom controls that don't get it for free.

Every lesson so far in this course has been about making an interface look right and behave correctly for a sighted user tapping and dragging with their fingers. That is not the only way people use an iPhone. Some users navigate entirely by listening to VoiceOver, Apple's built-in screen reader, describe what's on screen and speaking commands or performing gestures to move between elements, never looking at the display at all. Others need text scaled well beyond your designed layout to read it, navigate exclusively with an external keyboard or a single switch, or find rapid, elaborate animation physically uncomfortable rather than delightful. Accessibility is the practice of making sure your app still works — genuinely works, not just technically loads — for all of these people, and on Apple's platforms it is not an afterthought bolted on separately: it is a set of properties and behaviors built directly into UIKit that you, as the person writing the interface, are responsible for filling in correctly. This lesson works through VoiceOver and the property quartet that drives it — labels, hints, traits, and values — then covers Dynamic Type, keyboard and Switch Control navigation, contrast, Reduced Motion, and finally the point where all of this becomes your explicit responsibility: building a custom control from scratch, where none of it comes for free.

VoiceOver: The Primary Reason This Module Exists

VoiceOver is Apple's built-in screen reader, shipping on every iPhone, iPad, and Mac. When a user turns it on, the entire interaction model of the device changes: a single tap no longer activates whatever is under your finger — it moves VoiceOver's focus to that element and speaks a description of it, and a double-tap anywhere on the screen is what actually activates the currently focused element. Swiping left or right moves focus to the previous or next accessible element in an order UIKit determines (by default, roughly the order elements appear in the view hierarchy, though you can override it). None of this is optional or a niche feature tucked away in Settings for a handful of people — VoiceOver is how a substantial number of blind and low-vision users operate an Apple device as their only way of using it at all, every day, for everything from reading messages to banking to navigating a maps app.

The critical thing to understand is that VoiceOver does not see your interface the way you designed it. It has no idea that a particular view is styled to look like a button, that a certain icon means 'delete', or that a colored dot next to a name indicates an unread message. All VoiceOver has to work with is the accessibility information your code provides for each element — and if you provide nothing, VoiceOver falls back to guessing from whatever it can infer (a class name, an image's file name, sometimes nothing usable at all), which is exactly the experience that makes an app feel broken or actively hostile to a screen reader user. This is why accessibility is not a 'nice to have' layered on top of a finished UI — for a VoiceOver user, the accessibility information *is* the UI. If it's missing, wrong, or misleading, the interface does not exist for that user in any meaningful sense, regardless of how polished it looks on screen.

UIKit gives every UIView a small family of properties specifically so you can supply that missing context, and the rest of this lesson works through each one in turn: accessibilityLabel for what an element *is*, accessibilityHint for what happens if you activate it, accessibilityTraits for what *kind* of element it is, and accessibilityValue for its current state when it has one. Getting these right, consistently, across every screen in your app is the single highest-leverage thing you can do for VoiceOver users, and it is entirely within your control as the developer — Apple gives you the hooks, but nobody else can fill them in correctly for your specific UI except you.

Accessibility Labels: What an Element Is

accessibilityLabel is the single most important accessibility property, and it is exactly what the spec example shows: a short, human-readable string describing what an element is, which VoiceOver speaks aloud when the user's focus lands on it. button.accessibilityLabel = @"Submit"; sets that button's spoken description to the word 'Submit' — regardless of what the button visually displays, whether that's text, an icon, or nothing distinguishable at all. Many standard UIKit controls infer a reasonable label automatically (a UIButton with a title label usually picks up that title as its accessibility label for free), but the moment a control uses an icon instead of text, or a custom image, or no visible text at all, that automatic inference has nothing to work with, and you must set the label explicitly or VoiceOver will announce something unhelpful — often just 'button', with no indication of what it does.

A good label is concise, avoids redundant type information the trait system already conveys, and describes the element's *purpose*, not its appearance. accessibilityLabel = @"Delete" is a good label for a trash-can icon button; accessibilityLabel = @"Trash can icon" is not, because it describes the image rather than what tapping it does, and VoiceOver already appends the word 'Button' itself from the element's trait, so a label like @"Delete Button" ends up sounding redundant as 'Delete Button, Button' when spoken. The label should also not encode state that changes — a mute toggle's label should stay @"Mute", with its current on/off state expressed instead through accessibilityValue or a trait, which the next two sections cover, rather than swapping the label itself between @"Mute" and @"Unmute" and losing a stable identity for the control.

Labels matter just as much on things that aren't controls at all — an image showing a chart, a decorative icon next to a label, a photo in a feed. A UIImageView displaying a user's profile photo should have an accessibilityLabel like @"Profile photo of Jane Smith" rather than silence or a raw file name; a purely decorative image that conveys no information on its own (a background flourish, a spacer graphic) should instead be excluded from the accessibility tree entirely with isAccessibilityElement = NO, so VoiceOver skips over it rather than wasting the user's time announcing something meaningless.

Accessibility Hints: What Happens Next

accessibilityHint supplies a second, optional layer of information: a short phrase describing the *result* of activating the element, which VoiceOver speaks after the label and value, and typically only after a brief pause — giving an experienced user who already knows what the control does the chance to move on without waiting through it, while still being there for someone who needs the extra context. Where the label answers 'what is this', the hint answers 'what will happen if I double-tap it' — button.accessibilityHint = @"Submits the form and shows a confirmation screen." on that same Submit button tells the user something the label alone doesn't: that a successful submission leads somewhere new, which can be genuinely useful context before committing to the action.

Hints are for anything genuinely non-obvious about the *consequence* of an action, not a restatement of what the label already said. A hint like @"Taps the Submit button" on a button already labeled @"Submit" adds no information at all and simply wastes the listening user's time — every hint should describe a consequence, a destination, or a side effect that isn't already implied by the label and trait together. Most controls, honestly, don't need a hint at all: a well-labeled @"Delete" button with the button trait rarely needs to also explain that activating it deletes something, since that's already obvious from the label. Reserve hints for cases with real, non-obvious payoff — a button that triggers something that happens elsewhere on screen, takes the user to a different part of the app, or has an effect that isn't guessable from the label alone.

Because hints add extra speaking time to every interaction with that element, overusing them is a real cost, not a free enhancement — a screen where every single control has a verbose hint becomes tedious and slow to navigate for a VoiceOver user who is trying to move quickly through a familiar interface. Apple's own guidance is to keep hints genuinely rare and genuinely useful, which is a good discipline to carry into your own UI: write a hint only when you can articulate, concretely, what non-obvious information it adds beyond the label.

Accessibility Traits: What Kind of Element This Is

accessibilityTraits tells VoiceOver what *category* of element it's looking at, which changes both how VoiceOver announces it and what interactions it offers. Traits are a bitmask — an NSInteger-typed option set combined with bitwise-OR — because a single element can genuinely be more than one kind of thing at once: UIAccessibilityTraitButton marks something as tappable and activatable, so VoiceOver appends 'Button' to its spoken description and enables the double-tap-to-activate gesture; UIAccessibilityTraitHeader marks a section heading, which lets a VoiceOver user jump directly between headers with a dedicated navigation gesture, the same way a sighted user might visually scan a screen for section titles; UIAccessibilityTraitLink marks tappable text that navigates somewhere, distinct from a button in both its announcement and its typical semantics; UIAccessibilityTraitSelected marks an element as currently chosen, such as a selected tab or a checked list row; and UIAccessibilityTraitNotEnabled marks something present but currently non-interactive, so VoiceOver announces it as dimmed/disabled rather than letting the user activate it and wonder why nothing happened.

Standard UIKit controls come with sensible default traits already applied — a UIButton already carries the button trait, a UISwitch already carries traits appropriate to a toggle — which is one more reason standard controls are dramatically less work to make accessible than custom ones; the traits, along with the label inference mentioned earlier, are simply part of what you get by using the platform's own control classes rather than rolling your own. The trait system becomes something you set explicitly the moment you build a custom view that behaves like a button, a header, or a selected item but isn't an instance of the corresponding UIKit class — a UILabel acting as a section header, for instance, needs label.accessibilityTraits = UIAccessibilityTraitHeader; set explicitly, because a plain UILabel carries no traits suggesting it's structurally significant.

Combining traits with bitwise-OR is how you describe an element that is genuinely more than one thing: a custom row that is both tappable and currently selected might set cell.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitSelected; so VoiceOver announces both the ability to activate it and its current selected state in one pass. Getting the trait wrong isn't just cosmetic — a tappable element missing the button trait may not offer the double-tap activation gesture as clearly, and a disabled-looking control missing UIAccessibilityTraitNotEnabled will let a VoiceOver user attempt to activate it with no indication of why it silently does nothing.

Accessibility Values: Communicating Current State

accessibilityValue is for elements that have a current value or state beyond their basic identity — something that can change and that the user needs to hear, distinct from what the label says the element *is*. A UISlider showing volume doesn't just need a label like @"Volume"; it needs a value like @"60%" or @"Level 6 of 10" reflecting its current position, and UISlider actually supplies this automatically from its value/minimumValue/maximumValue, which is another example of a standard control doing accessibility work for you. A custom slider-like control you build yourself gets none of that for free — you must compute and set accessibilityValue yourself, every time the underlying value changes, or VoiceOver will announce the control's label with no indication of where it's currently set.

A UISwitch is a clean, everyday illustration of value versus label versus trait working together correctly: its label might be @"Wi-Fi", its trait marks it as a toggle-style control, and its value — supplied automatically by UISwitch itself — announces as 'on' or 'off' depending on its current state. If you build a custom toggle control instead of using UISwitch, replicating that exact three-part behavior — a stable label describing what the switch controls, a trait marking it as a button-like toggle, and a value that updates to 'on'/'off' (or @"1"/@"0", or better, the actual words) every time the toggle flips — is entirely on you.

A common mistake is folding a changing value into the label itself instead of using accessibilityValue for it — setting a progress indicator's label to @"Download progress 45 percent" and updating that whole string every time progress advances, rather than keeping a stable label like @"Download progress" and updating only the value to @"45%". The value-based approach is more robust: some VoiceOver interactions and third-party assistive tools read label and value as logically distinct pieces of information, and keeping them separate is both more correct semantically and easier to maintain as the value changes rapidly, such as during an active download or a live slider drag.

Dynamic Type: Text That Scales With the User's Preference

Dynamic Type is the system-wide setting, configured in Settings, that lets a user choose their preferred text size across every app that supports it — from a compact size for someone who wants to see more content at once, up through several accessibility sizes considerably larger than an app's normal default, for a user with low vision who genuinely cannot read small text at all. Supporting it is not a nice cosmetic touch; for a meaningful number of users, an app that ignores Dynamic Type and renders everything at a fixed small size is functionally unreadable, no different in effect from an app that's simply broken for them.

The mechanism is UIFont's preferred-font APIs rather than fixed point sizes: instead of [UIFont systemFontOfSize:14], you request a font for a semantic text style with [UIFont preferredFontForTextStyle:UIFontTextStyleBody] (or UIFontTextStyleHeadline, UIFontTextStyleCaption1, and similar constants for other roles), and UIKit returns a font already sized according to the user's current Dynamic Type setting for that particular role. Labels that use adjustsFontForContentSizeCategory = YES alongside a preferred font will automatically re-render at the new size if the user changes their Dynamic Type setting while your app is running, without any extra code on your part beyond having used a preferred font in the first place.

Supporting Dynamic Type well is a layout responsibility as much as a font one: text that scales up considerably needs a layout built with Auto Layout constraints, not fixed frame heights, so a label can grow taller and wrap to more lines without clipping or overlapping neighboring views — a fixed-height label showing text at the largest accessibility size, cut off mid-word, defeats the entire purpose of having supported Dynamic Type at the font level at all. A UI genuinely worth calling accessible is tested, at minimum by eye, at one of the larger accessibility text sizes, not just at the system's default size.

Keyboard/Switch Control Navigation, Contrast, and Reduced Motion

VoiceOver is not the only assistive technology UIKit supports. Switch Control lets a user operate the entire device using one or a small number of physical switches — often a single button — instead of touching the screen at all: the system scans through focusable elements one at a time, highlighting each in turn, and the user presses their switch at the moment the element they want is highlighted to select it. Full keyboard navigation, using an external hardware keyboard with an iPad or Mac, works similarly, moving a focus ring between interactive elements with the Tab key or arrow keys and activating the focused element with Return or Space. Both of these mechanisms rely on the same underlying signal your accessibility work already establishes: an element needs to be marked as an accessibility element and correctly traited as interactive for it to show up as a stop in the scan or tab order at all, which is one more reason the label/hint/trait/value work already covered in this lesson pays off well beyond VoiceOver alone.

The order in which Switch Control and keyboard navigation move between elements generally follows the same accessibility ordering VoiceOver uses — by default, roughly the order elements are added to the view hierarchy, top-to-bottom and left-to-right for a typical layout, though accessibilityElements on a container view lets you override that order explicitly when the visual layout and the logical reading order diverge, such as a layout using a grid or absolute positioning where hierarchy order doesn't match visual order. A UI where focus jumps around unpredictably or skips important controls entirely is a genuine usability failure for someone navigating this way, even if the same screen looks perfectly ordinary to a sighted, touch-based user.

Contrast is a simpler concept with an equally real impact: sufficient color difference between foreground content (text, icons, meaningful graphics) and its background is what makes that content legible for users with low vision, color blindness, or simply a bright ambient environment washing out a dim screen. Apple's Human Interface Guidelines recommend concrete minimum contrast ratios for text against its background, and the practical habit worth building is simply not relying on subtle, low-contrast color pairings — light gray text on white, pale color-on-color combinations that look fine to a designer's eye on a calibrated monitor but fail badly for a real user in a real lighting condition. UIColor's semantic system colors (labelColor, secondaryLabelColor, and similar) are tuned by Apple to meet reasonable contrast expectations automatically, and preferring them over arbitrary custom colors for text is a straightforward way to inherit that work rather than reinventing it.

Reduced Motion rounds out this section because it's the same kind of respect for a stated user preference, just for motion instead of navigation or color. Some users find elaborate motion — large parallax effects, zooming transitions, extended spring animations — genuinely uncomfortable, in some cases to the point of physical symptoms like dizziness or nausea, not merely a matter of taste. In code, that setting is exposed as UIAccessibility.isReduceMotionEnabled, a read-only Boolean you check before choosing how to animate a transition: the common, well-regarded pattern is to substitute a simpler, calmer transition (a plain cross-fade) in place of an elaborate one (a large parallax slide or a bouncy spring) rather than treating it as an all-or-nothing switch, and to skip purely decorative, ornamental animation entirely when the setting is on. Because this setting can change at any moment while your app is running, a robust implementation also listens for UIAccessibilityReduceMotionStatusDidChangeNotification rather than checking it only once at launch, so an app already on screen adapts immediately if the user changes the setting mid-session.

Accessible Custom Controls: A Worked Example

Every property covered in this lesson so far comes largely for free when you build your UI out of standard UIKit classes — UIButton, UISwitch, UISlider, and the rest already carry sensible default labels, traits, and values, because Apple's engineers already did that work once, for everyone, inside the framework. The moment you build a custom control from scratch — a UIView subclass with your own touch handling, drawn or composed entirely out of layers, images, and gesture recognizers rather than a stock control — every bit of that default accessibility behavior disappears, because as far as UIKit's accessibility system is concerned, a plain custom UIView is just an anonymous rectangle with no inherent meaning at all. Nothing about labels, hints, traits, or values comes automatically; you must wire up every one of them by hand, deliberately, or the control is functionally invisible to VoiceOver and unusable by anyone relying on it.

Consider a custom star-rating control: a row of five star-shaped views the user taps or drags across to set a rating from one to five, built entirely with CAShapeLayers and a pan gesture recognizer rather than any stock UIKit component. Visually it works perfectly for a sighted, touch-based user. For VoiceOver, it is nothing at all unless you explicitly make it something: you'd start by setting isAccessibilityElement = YES on the container view so VoiceOver treats the whole control as a single focusable stop rather than trying (and likely failing) to make sense of its internal layer structure; then accessibilityLabel = @"Rating" so VoiceOver announces what the control represents; then a trait combination appropriate to an adjustable control, UIAccessibilityTraitAdjustable, which tells VoiceOver this element responds to swipe-up and swipe-down gestures to increment and decrement its value, rather than only a tap; and then accessibilityValue, updated every time the rating changes, to something like @"3 out of 5 stars" so the current rating is actually communicated.

The adjustable trait carries one more piece of required work: UIKit expects a control marked adjustable to implement accessibilityIncrement and accessibilityDecrement, two methods VoiceOver calls when the user performs its swipe-up/swipe-down gesture on the focused element, and it is entirely your responsibility to implement them to actually change the control's underlying value and then update accessibilityValue to reflect the new state — without that, a control claiming to be adjustable would respond to the gesture with nothing happening at all, which is arguably worse than not claiming adjustability in the first place, since it actively promises an interaction it doesn't deliver. This is the pattern to internalize for any custom control you ever build: work out, concretely, what a sighted user perceives and can do with your fingers, and then deliberately translate every one of those things — what it is, what happens if you activate it, what kind of thing it is, and what its current state is — into the label/trait/value/hint vocabulary this whole lesson has covered, because for a custom view, that translation is the only accessibility support that will ever exist.

The Core Accessibility Properties on UIView

isAccessibilityElement = YES is required before any of the other properties on a plain custom UIView matter at all — without it, VoiceOver skips the view or tries to read its subviews individually. Standard UIKit controls (UIButton, UISwitch, UISlider) already set sensible traits/values by default; you only need to set these explicitly on custom-built controls or when the defaults don't fit. UIFont preferredFontForTextStyle: plus adjustsFontForContentSizeCategory = YES is the Dynamic Type equivalent of this property family — request a role, not a fixed point size.

view.isAccessibilityElement = YES;                     // include this view in the accessibility tree
view.accessibilityLabel = @"Submit";                    // what the element IS
view.accessibilityHint = @"Submits the form.";           // what happens if activated (use sparingly)
view.accessibilityTraits = UIAccessibilityTraitButton;   // what KIND of element this is
view.accessibilityValue = @"3 out of 5 stars";            // current state/value, when applicable

// Combine traits with bitwise-OR when an element is more than one kind of thing:
view.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitSelected;

// Check user preferences at runtime:
if (UIAccessibility.isReduceMotionEnabled) {
    // use a simpler transition, or skip decorative animation entirely
}

Code Examples

This button displays only an icon — a paper-plane glyph — with no visible text at all, so UIKit has nothing to infer a spoken description from automatically. Setting button.accessibilityLabel = @"Submit"; gives VoiceOver a clear, human-readable description to announce when focus lands on this button, exactly matching the spec's example. Because UIButton already carries the button trait by default, VoiceOver will announce something like 'Submit, Button' without any further trait configuration needed here.

button.accessibilityLabel = @"Submit";

The label stays fixed at @"Mute" regardless of the current state — it names what the control controls, not its current value. The hint explains a non-obvious consequence (silencing the whole call, not just this device's speaker) that isn't guessable from the label alone. The button trait tells VoiceOver this is activatable, and accessibilityValue — recomputed in updateAccessibilityValue every time toggle runs — is what actually communicates whether the control is currently on or off, kept deliberately separate from the stable label rather than folded into a changing string like @"Unmute". Because this is a fully custom UIView with its own gesture recognizer rather than a UISwitch or UIButton, none of isAccessibilityElement, the label, the trait, or the value would exist without being set explicitly here — that is the core lesson of the custom-controls section applied concretely.

self.accessibilityValue = self.isMuted ? @"On" : @"Off";

Requesting preferredFontForTextStyle:UIFontTextStyleBody] instead of a fixed point size gives back a font already scaled to the user's current Dynamic Type setting. Setting adjustsFontForContentSizeCategory = YES means this label will automatically re-render at a new size if the user changes their text-size preference while the app is running, with no extra code required. numberOfLines = 0 matters just as much as the font choice — at a large accessibility text size, this string may need three or four lines instead of one, and a label capped at a single line would clip the text no matter how correctly the font itself scaled.

bodyLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

The UIAccessibilityTraitAdjustable trait tells VoiceOver this element responds to an up/down swipe gesture rather than only a tap, but that trait is a promise the code must keep: accessibilityIncrement and accessibilityDecrement are the methods VoiceOver actually calls on those gestures, and it is entirely up to this implementation to change currentRating, refresh accessibilityValue so the new value is announced, and update the visual drawing to match. Omitting either method while still setting the adjustable trait would leave the swipe gesture doing nothing, which is worse than not claiming adjustability at all.

self.accessibilityTraits = UIAccessibilityTraitAdjustable;

Common mistake: Setting UIAccessibilityTraitAdjustable without implementing accessibilityIncrement/accessibilityDecrement makes the control claim an interaction it silently fails to deliver.

UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button setImage:[UIImage systemImageNamed:@"paperplane.fill"] forState:UIControlStateNormal];
button.accessibilityLabel = @"Submit";
@interface MuteToggleView : UIView
@property (nonatomic, assign, getter=isMuted) BOOL muted;
@end

@implementation MuteToggleView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.isAccessibilityElement = YES;
        self.accessibilityLabel = @"Mute";
        self.accessibilityHint = @"Silences audio for this call.";
        self.accessibilityTraits = UIAccessibilityTraitButton;
        [self updateAccessibilityValue];

        UITapGestureRecognizer *tap =
            [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggle)];
        [self addGestureRecognizer:tap];
    }
    return self;
}

- (void)toggle {
    self.muted = !self.muted;
    [self updateAccessibilityValue];
    [self setNeedsDisplay];
}

- (void)updateAccessibilityValue {
    self.accessibilityValue = self.isMuted ? @"On" : @"Off";
}

@end
UILabel *bodyLabel = [[UILabel alloc] init];
bodyLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
bodyLabel.adjustsFontForContentSizeCategory = YES;
bodyLabel.numberOfLines = 0; // allow wrapping to multiple lines as text scales up
bodyLabel.text = @"Your order has shipped and is on its way.";
- (void)configureAccessibility {
    self.isAccessibilityElement = YES;
    self.accessibilityLabel = @"Rating";
    self.accessibilityTraits = UIAccessibilityTraitAdjustable;
    [self refreshAccessibilityValue];
}

- (void)refreshAccessibilityValue {
    self.accessibilityValue =
        [NSString stringWithFormat:@"%ld out of 5 stars", (long)self.currentRating];
}

// VoiceOver calls these when the user swipes up / down on the focused element.
- (void)accessibilityIncrement {
    self.currentRating = MIN(self.currentRating + 1, 5);
    [self refreshAccessibilityValue];
    [self setNeedsDisplay];
}

- (void)accessibilityDecrement {
    self.currentRating = MAX(self.currentRating - 1, 1);
    [self refreshAccessibilityValue];
    [self setNeedsDisplay];
}

Accessibility as a Correctness Requirement, Not a Feature

Teams that treat accessibility as a late-stage checklist item routinely end up retrofitting dozens of custom controls one at a time, long after the UI has shipped and users have already reported that core flows — checkout, login, navigation — simply don't work with VoiceOver. Teams that instead build accessibility support into each control at the moment it's created, using the same label/hint/trait/value discipline this lesson covers, pay a small, steady cost per control instead of a large, disruptive one at the end. On Apple's platforms this is not only good practice — App Store review and, in various jurisdictions, real legal frameworks around digital accessibility, treat a genuinely broken assistive-technology experience as a legitimate defect, on par with a crash, rather than an optional nicety.

Shipping a custom UIView-based control (an icon-only button, a custom switch, a custom slider) with no isAccessibilityElement, label, trait, or value set at all.

Set isAccessibilityElement = YES and explicitly configure accessibilityLabel, accessibilityTraits, and accessibilityValue (plus accessibilityHint when genuinely useful) on every custom control, mirroring what the equivalent standard UIKit control would provide automatically.

Standard controls come with sensible accessibility defaults built into UIKit; a custom UIView subclass gets none of that automatically, and skipping this setup makes the control effectively invisible or meaningless to VoiceOver.

Writing accessibilityHint text that just restates the label, e.g. an accessibilityHint of "Taps the Submit button" on a button already labeled "Submit".

Reserve hints for genuinely non-obvious consequences of activating the element — what happens next, or elsewhere — and leave the hint unset entirely when the label and trait already make the outcome clear.

A redundant hint adds spoken time to every interaction with that element for no informational benefit, making the interface slower and more tedious to navigate for VoiceOver users without helping anyone.

Building a layout with fixed-height labels and hardcoded point sizes (UIFont systemFontOfSize:14) that clip or overlap once the user selects a larger Dynamic Type size.

Use UIFont preferredFontForTextStyle: for a semantic role, set adjustsFontForContentSizeCategory = YES, and build the surrounding layout with Auto Layout constraints (not fixed frame heights) so labels can grow to accommodate larger scaled text.

Supporting Dynamic Type at the font level only helps if the surrounding layout can actually accommodate the larger rendered text; a fixed-height container clips text regardless of how correctly the font itself scaled.

Running the same large-scale parallax, zoom, or bounce-heavy transition for every user regardless of their Reduce Motion setting.

Check UIAccessibility.isReduceMotionEnabled (and observe UIAccessibilityReduceMotionStatusDidChangeNotification for changes mid-session) and substitute a simpler transition, such as a cross-fade, or skip purely decorative animation entirely when it's on.

For some users, elaborate motion is not a matter of taste but a source of real physical discomfort; ignoring Reduce Motion forces an uncomfortable experience on users who have explicitly told the system they don't want it.

What is the primary purpose of a VoiceOver user's swipe gesture between elements, and what does a double-tap do? A. Swipe scrolls the whole screen; double-tap zooms in B. Swipe moves VoiceOver's focus between accessible elements; double-tap activates the currently focused element C. Swipe activates the element under the finger; double-tap moves focus D. Both gestures do the same thing, activating whatever is swiped or tapped

B. Swipe moves VoiceOver's focus between accessible elements; double-tap activates the currently focused element — With VoiceOver on, a single tap or swipe no longer directly activates what's under the finger — it moves focus to that element and speaks its description; a double-tap anywhere on screen activates whatever element currently has focus.

In the spec example button.accessibilityLabel = @"Submit";, what does this property control? A. The button's current on/off state, like a switch B. A short, human-readable description of what the element is, spoken by VoiceOver when it receives focus C. Supplementary text describing what happens when the button is activated D. Which category of element (button, header, link) VoiceOver treats it as

B. A short, human-readable description of what the element is, spoken by VoiceOver when it receives focus — accessibilityLabel answers 'what is this' — a concise description VoiceOver speaks aloud. Hint answers what happens next, trait answers what kind of element it is, and value communicates current state — this example sets only the label.

Why should a mute toggle's accessibilityLabel stay fixed at "Mute" rather than switching between "Mute" and "Unmute" as its state changes? A. accessibilityLabel cannot be changed programmatically after it's first set B. Current, changing state belongs in accessibilityValue, keeping the label a stable identity for the control C. VoiceOver ignores accessibilityLabel entirely for toggle-style controls D. Switching labels would cause a crash in UIKit's accessibility system

B. Current, changing state belongs in accessibilityValue, keeping the label a stable identity for the control — The label should describe what the control is/controls, stably; a changing current state (on/off, a value, a count) belongs in accessibilityValue, which is designed specifically to represent state that changes.

What must a control implement if it sets accessibilityTraits to include UIAccessibilityTraitAdjustable? A. accessibilityLabel and accessibilityHint only B. Nothing further — the trait alone enables the increment/decrement gestures automatically C. accessibilityIncrement and accessibilityDecrement, so VoiceOver's swipe-up/swipe-down gestures actually change the control's value D. A custom UIGestureRecognizer subclass registered on the view

C. accessibilityIncrement and accessibilityDecrement, so VoiceOver's swipe-up/swipe-down gestures actually change the control's value — UIAccessibilityTraitAdjustable is a promise that the element responds to VoiceOver's up/down swipe gesture; accessibilityIncrement and accessibilityDecrement are the methods VoiceOver actually invokes, and the developer must implement them to change state and update accessibilityValue.

What is the correct way to support Dynamic Type for a UILabel's font, instead of a fixed point size like [UIFont systemFontOfSize:14]? A. Manually check the device model and pick a size accordingly B. Use [UIFont preferredFontForTextStyle:] for a semantic role and set adjustsFontForContentSizeCategory = YES C. Dynamic Type only affects system apps and cannot be supported in custom code D. Set the label's minimumScaleFactor to 0.1 and let iOS shrink the text instead

B. Use [UIFont preferredFontForTextStyle:] for a semantic role and set adjustsFontForContentSizeCategory = YES — preferredFontForTextStyle: returns a font already sized to the user's current Dynamic Type setting for a given semantic role (body, headline, etc.), and adjustsFontForContentSizeCategory = YES keeps it updating automatically if the setting changes while the app runs.

What should an app do in response to UIAccessibility.isReduceMotionEnabled being true? A. Disable the app's UI entirely until the user turns the setting off B. Ignore it — Reduce Motion only affects system animations, not app-level ones C. Substitute simpler transitions (such as a cross-fade) for elaborate ones, and skip purely decorative animation, while still observing changes to the setting at runtime D. Replace all animations with a single fixed 5-second delay

C. Substitute simpler transitions (such as a cross-fade) for elaborate ones, and skip purely decorative animation, while still observing changes to the setting at runtime — Reduce Motion signals that a user finds elaborate motion uncomfortable, sometimes physically so; a well-behaved app substitutes calmer transitions or removes purely decorative motion, and should also observe UIAccessibilityReduceMotionStatusDidChangeNotification since the setting can change while the app is running.

You have a custom UIView subclass called StepperControl representing a plus/minus quantity stepper (a minus button, a number label, and a plus button, all drawn manually rather than using three separate UIKit controls). Describe the accessibility properties you would set, and on which sub-elements, to make it fully usable with VoiceOver, including how you would expose increment/decrement.

Set isAccessibilityElement = YES on the container view itself (not the three sub-views individually), accessibilityLabel = @"Quantity", accessibilityTraits = UIAccessibilityTraitAdjustable, and accessibilityValue updated to the current count as a string (e.g. @"3") every time it changes. Implement accessibilityIncrement to call the same logic the plus button's tap handler calls, and accessibilityDecrement to call the same logic the minus button's tap handler calls, updating accessibilityValue after each. This mirrors a single UIStepper's accessible behavior rather than forcing a VoiceOver user to hunt for and individually activate three separate tiny tap targets.

Consider treating the whole control as a single adjustable accessibility element (label like "Quantity", UIAccessibilityTraitAdjustable, and a value like "3") with accessibilityIncrement/accessibilityDecrement implemented, rather than exposing three separate tappable sub-elements for minus, the number, and plus.