iOS Accessibility Interview Questions in 2026: VoiceOver and Dynamic Type

Prepare for iOS interviews with key accessibility questions: VoiceOver, Dynamic Type, semantic traits, and accessibility audits.

iOS accessibility interview questions on VoiceOver and Dynamic Type

Accessibility has become a critical skill in iOS development. Recruiters now systematically evaluate proficiency with VoiceOver, Dynamic Type, and accessibility APIs. These questions cover the essential concepts for succeeding in technical interviews.

Why accessibility matters in interviews

Over 1 billion people live with disabilities. Apple enforces strict accessibility standards, and many companies refuse to ship inaccessible apps. This skill differentiates senior candidates from the rest.

iOS Accessibility Fundamentals

1. What are the main iOS accessibility tools?

iOS provides a comprehensive ecosystem of accessibility tools targeting different types of disabilities. VoiceOver enables screen reading for visually impaired users. Dynamic Type adapts text size. Switch Control allows external switch control. Voice Control offers complete voice command functionality.

AccessibilityTools.swiftswift
// Check if VoiceOver is running
if UIAccessibility.isVoiceOverRunning {
    // Adapt the interface for VoiceOver
    showSimplifiedInterface()
}

// Check if Reduce Motion is enabled
if UIAccessibility.isReduceMotionEnabled {
    // Disable complex animations
    animationDuration = 0
}

// Check if Bold Text is enabled
if UIAccessibility.isBoldTextEnabled {
    // Use heavier fonts
    applyBoldFonts()
}

// Observe settings changes
NotificationCenter.default.addObserver(
    forName: UIAccessibility.voiceOverStatusDidChangeNotification,
    object: nil,
    queue: .main
) { _ in
    // React to the change
    updateAccessibilityLayout()
}

These APIs enable dynamic interface adaptation based on user preferences and active assistive technologies.

2. How does VoiceOver work technically?

VoiceOver traverses the interface by identifying accessible elements. Each element exposes properties like its label, value, traits, and actions. The screen reader synthesizes this information vocally as the user navigates with gestures.

VoiceOverBasics.swiftswift
// In UIKit: configure an accessible element
class CustomButton: UIButton {
    override var accessibilityLabel: String? {
        get { "Validation button" }  // What VoiceOver reads
        set { }
    }

    override var accessibilityHint: String? {
        get { "Double-tap to confirm the order" }  // Possible action
        set { }
    }

    override var accessibilityTraits: UIAccessibilityTraits {
        get { .button }  // Element type
        set { }
    }

    override var isAccessibilityElement: Bool {
        get { true }  // Element focusable by VoiceOver
        set { }
    }
}

// In SwiftUI: equivalent with modifiers
struct ValidateButton: View {
    var body: some View {
        Button("Validate") {
            confirmOrder()
        }
        .accessibilityLabel("Validation button")
        .accessibilityHint("Double-tap to confirm the order")
    }
}

The accessibility hierarchy can differ from the visual hierarchy, enabling logical navigation even with complex layouts.

3. What is the difference between accessibilityLabel and accessibilityValue?

The accessibilityLabel permanently identifies the element, while accessibilityValue represents its current state that can change. This distinction is crucial for dynamic controls like sliders or switches.

LabelVsValue.swiftswift
// Example with a volume slider
class VolumeSlider: UISlider {
    override var accessibilityLabel: String? {
        get { "Volume" }  // Fixed element identity
        set { }
    }

    override var accessibilityValue: String? {
        get { "\(Int(value * 100)) percent" }  // Current state that changes
        set { }
    }
}
// VoiceOver reads: "Volume, 75 percent"

// In SwiftUI with a Toggle
struct NotificationToggle: View {
    @Binding var isEnabled: Bool

    var body: some View {
        Toggle(isOn: $isEnabled) {
            Text("Notifications")
        }
        .accessibilityLabel("Push notifications")
        // accessibilityValue is automatic for Toggle: "on" or "off"
    }
}

// For a custom stepper
struct QuantityStepper: View {
    @State private var quantity = 1

    var body: some View {
        Stepper("Quantity", value: $quantity, in: 1...99)
            .accessibilityLabel("Item quantity")
            .accessibilityValue("\(quantity) item\(quantity > 1 ? "s" : "")")
    }
}

VoiceOver always announces the label followed by the value, allowing users to understand both what the element is and its current state.

4. Explain accessibilityTraits and their importance

Traits inform VoiceOver about an element's type and behavior. They determine how the element is announced and what actions are available. A button without the .button trait will not be announced as one.

AccessibilityTraits.swiftswift
// Common traits in UIKit
class CustomCell: UITableViewCell {
    override var accessibilityTraits: UIAccessibilityTraits {
        get {
            var traits: UIAccessibilityTraits = []

            // Element type
            traits.insert(.button)           // Interactive element

            // Current state
            if isSelected {
                traits.insert(.selected)     // Currently selected
            }

            // Special characteristics
            if !isEnabled {
                traits.insert(.notEnabled)   // Disabled
            }

            return traits
        }
        set { }
    }
}

// Available traits
// .button         - Clickable element
// .link           - Hyperlink
// .header         - Section header
// .image          - Image
// .selected       - Selected state
// .notEnabled     - Disabled
// .adjustable     - Adjustable value (slider)
// .staticText     - Non-interactive text
// .searchField    - Search field
// .playsSound     - Plays a sound
// .startsMediaSession - Starts media playback

// In SwiftUI
struct SectionHeader: View {
    let title: String

    var body: some View {
        Text(title)
            .font(.headline)
            .accessibilityAddTraits(.isHeader)  // Announced as header
    }
}

struct SelectableRow: View {
    let isSelected: Bool

    var body: some View {
        HStack {
            Text("Option")
            if isSelected {
                Image(systemName: "checkmark")
            }
        }
        .accessibilityElement(children: .combine)
        .accessibilityAddTraits(isSelected ? .isSelected : [])
    }
}

Traits enable assistive technologies to provide a consistent and predictable experience for users.

Dynamic Type and Adaptive Text Sizes

5. How do you implement Dynamic Type correctly?

Dynamic Type allows users to adjust the system text size. Correct implementation uses semantic text styles and flexible constraints so the interface adapts automatically.

DynamicType.swiftswift
// In UIKit: use preferred text styles
class ArticleCell: UITableViewCell {
    let titleLabel = UILabel()
    let bodyLabel = UILabel()

    func configure() {
        // Use semantic text styles
        titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
        bodyLabel.font = UIFont.preferredFont(forTextStyle: .body)

        // CRUCIAL: enable automatic adjustment
        titleLabel.adjustsFontForContentSizeCategory = true
        bodyLabel.adjustsFontForContentSizeCategory = true

        // Allow multiline for large sizes
        titleLabel.numberOfLines = 0
        bodyLabel.numberOfLines = 0
    }
}

// In SwiftUI: it's automatic with styles
struct ArticleView: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Article Title")
                .font(.headline)  // Adapts automatically

            Text("Article content...")
                .font(.body)
        }
    }
}

// Custom font with Dynamic Type
extension UIFont {
    static func customFont(
        size: CGFloat,
        style: TextStyle
    ) -> UIFont {
        let customFont = UIFont(name: "CustomFont-Regular", size: size)!

        // Adapt to current size category
        return UIFontMetrics(forTextStyle: style)
            .scaledFont(for: customFont)
    }
}

Without adjustsFontForContentSizeCategory, labels keep their initial size even when the user changes preferences.

6. How do you handle extreme accessibility sizes?

Accessibility sizes (AX1 to AX5) can triple text size. The interface must adapt with alternative layouts: stack vertically instead of horizontally, hide decorative elements, and adjust spacing.

LargeContentSize.swiftswift
// Detect accessibility sizes
struct AdaptiveLayout: View {
    @Environment(\.dynamicTypeSize) var dynamicTypeSize

    var body: some View {
        // Different layout for large sizes
        if dynamicTypeSize.isAccessibilitySize {
            // Vertical layout for large sizes
            VStack(alignment: .leading, spacing: 12) {
                iconAndTitle
                actionButtons
            }
        } else {
            // Standard horizontal layout
            HStack {
                iconAndTitle
                Spacer()
                actionButtons
            }
        }
    }

    var iconAndTitle: some View {
        HStack {
            Image(systemName: "bell.fill")
                .accessibilityHidden(true)  // Decorative, ignore
            Text("Notifications")
        }
    }

    var actionButtons: some View {
        HStack(spacing: 16) {
            Button("Enable") { }
            Button("Settings") { }
        }
    }
}

// In UIKit: observe changes
class AdaptiveViewController: UIViewController {
    override func traitCollectionDidChange(
        _ previousTraitCollection: UITraitCollection?
    ) {
        super.traitCollectionDidChange(previousTraitCollection)

        // Check if size category changed
        if traitCollection.preferredContentSizeCategory !=
           previousTraitCollection?.preferredContentSizeCategory {
            updateLayoutForContentSize()
        }
    }

    func updateLayoutForContentSize() {
        let category = traitCollection.preferredContentSizeCategory

        if category.isAccessibilityCategory {
            // Enable accessible layout
            stackView.axis = .vertical
            decorativeView.isHidden = true
        } else {
            // Standard layout
            stackView.axis = .horizontal
            decorativeView.isHidden = false
        }
    }
}

Accessibility categories start at .accessibilityMedium (AX1). Design must anticipate these cases from the start.

Ready to ace your iOS interviews?

Practice with our interactive simulators, flashcards, and technical tests.

7. How do you make images accessible?

Images require different treatment based on their role: informative images need descriptions, decorative images should be ignored, and interactive images require action labels.

AccessibleImages.swiftswift
// Informative image: describe the content
struct ProductImage: View {
    let product: Product

    var body: some View {
        AsyncImage(url: product.imageURL) { image in
            image
                .resizable()
                .accessibilityLabel(product.imageDescription)
                // "Product photo: iPhone 15 Pro, titanium color"
        } placeholder: {
            ProgressView()
                .accessibilityLabel("Loading image")
        }
    }
}

// Decorative image: hide from VoiceOver
struct DecorationView: View {
    var body: some View {
        Image(systemName: "sparkles")
            .accessibilityHidden(true)  // Ignored by VoiceOver
    }
}

// Image button: describe the action, not the image
struct FavoriteButton: View {
    @Binding var isFavorite: Bool

    var body: some View {
        Button {
            isFavorite.toggle()
        } label: {
            Image(systemName: isFavorite ? "heart.fill" : "heart")
        }
        // Don't describe the icon, describe the action
        .accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
    }
}

// In UIKit
class ProductImageView: UIImageView {
    override var isAccessibilityElement: Bool {
        get { true }
        set { }
    }

    func configure(with product: Product) {
        image = product.image
        accessibilityLabel = "Photo: \(product.name), \(product.color)"
    }
}

// Image with embedded text (infographic)
class InfographicView: UIImageView {
    override var accessibilityLabel: String? {
        get {
            // Transcribe the textual content of the infographic
            """
            Statistics infographic 2026.
            Growth: 45%.
            Active users: 2.3 million.
            Satisfaction: 4.8 out of 5.
            """
        }
        set { }
    }
}

A poorly described image or an unmasked decorative image significantly degrades the VoiceOver experience.

Grouping and Advanced Navigation

8. How do you group elements for VoiceOver?

Grouping combines multiple visual elements into a single accessible element. This speeds up navigation and provides complete context in a single announcement.

AccessibilityGrouping.swiftswift
// In SwiftUI: combine children
struct ProductCard: View {
    let product: Product

    var body: some View {
        VStack(alignment: .leading) {
            Text(product.name)
                .font(.headline)
            Text(product.price)
                .font(.subheadline)
            HStack {
                Image(systemName: "star.fill")
                Text(product.rating)
            }
        }
        // Combines all text into a single element
        .accessibilityElement(children: .combine)
        // VoiceOver reads: "iPhone 15 Pro, $1199, 4.8 stars"
    }
}

// In SwiftUI: ignore children and define manually
struct OrderSummary: View {
    let order: Order

    var body: some View {
        HStack {
            Image(systemName: "bag")
            VStack(alignment: .leading) {
                Text(order.itemCount)
                Text(order.total)
            }
            Image(systemName: "chevron.right")
        }
        // Ignore children hierarchy
        .accessibilityElement(children: .ignore)
        // Define custom label
        .accessibilityLabel("Order of \(order.itemCount) items for \(order.total)")
        .accessibilityHint("Double-tap to view details")
        .accessibilityAddTraits(.isButton)
    }
}

// In UIKit: accessible container
class ProductCardView: UIView {
    let nameLabel = UILabel()
    let priceLabel = UILabel()
    let ratingLabel = UILabel()

    override var isAccessibilityElement: Bool {
        get { true }  // The container is the accessible element
        set { }
    }

    override var accessibilityLabel: String? {
        get {
            // Combine information
            "\(nameLabel.text ?? ""), \(priceLabel.text ?? ""), \(ratingLabel.text ?? "")"
        }
        set { }
    }
}

Without grouping, VoiceOver stops on each Text individually, significantly slowing down navigation.

9. How do you implement custom accessibility actions?

Custom actions add accessible functionality without cluttering the visual interface. VoiceOver users access them via vertical swipe.

CustomAccessibilityActions.swiftswift
// In SwiftUI: custom actions
struct EmailRow: View {
    let email: Email
    let onArchive: () -> Void
    let onDelete: () -> Void
    let onFlag: () -> Void

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(email.sender)
                    .font(.headline)
                Text(email.subject)
                    .font(.subheadline)
            }
            Spacer()
            if email.isFlagged {
                Image(systemName: "flag.fill")
            }
        }
        .accessibilityElement(children: .combine)
        .accessibilityLabel("\(email.sender), \(email.subject)")
        // Actions accessible via vertical swipe
        .accessibilityAction(named: "Archive") {
            onArchive()
        }
        .accessibilityAction(named: "Delete") {
            onDelete()
        }
        .accessibilityAction(named: email.isFlagged ? "Remove flag" : "Flag") {
            onFlag()
        }
    }
}

// In UIKit: override accessibilityCustomActions
class EmailCell: UITableViewCell {
    var email: Email!
    var onArchive: (() -> Void)?
    var onDelete: (() -> Void)?

    override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
        get {
            [
                UIAccessibilityCustomAction(
                    name: "Archive",
                    target: self,
                    selector: #selector(archiveAction)
                ),
                UIAccessibilityCustomAction(
                    name: "Delete",
                    target: self,
                    selector: #selector(deleteAction)
                )
            ]
        }
        set { }
    }

    @objc private func archiveAction() -> Bool {
        onArchive?()
        return true  // true = action succeeded
    }

    @objc private func deleteAction() -> Bool {
        onDelete?()
        return true
    }
}

Custom actions advantageously replace swipe gestures that are difficult to discover with VoiceOver.

10. How do you manage VoiceOver focus programmatically?

Controlling focus is essential after interface changes: modal appearance, content loading, navigation. Focus should guide users to relevant information.

AccessibilityFocus.swiftswift
// In SwiftUI with @AccessibilityFocusState (iOS 15+)
struct SearchView: View {
    @State private var searchText = ""
    @State private var results: [Result] = []
    @AccessibilityFocusState private var focusedResult: Result?

    var body: some View {
        VStack {
            TextField("Search", text: $searchText)
                .onSubmit {
                    performSearch()
                }

            List(results) { result in
                ResultRow(result: result)
                    .accessibilityFocused($focusedResult, equals: result)
            }
        }
    }

    func performSearch() {
        Task {
            results = await api.search(searchText)
            // Move focus to first result
            if let first = results.first {
                focusedResult = first
            }
        }
    }
}

// In UIKit: post a notification
class ModalViewController: UIViewController {
    @IBOutlet weak var titleLabel: UILabel!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Announce and move focus
        UIAccessibility.post(
            notification: .screenChanged,  // New screen
            argument: titleLabel           // Element to focus
        )
    }

    func showError(_ message: String) {
        errorLabel.text = message
        errorLabel.isHidden = false

        // Announce layout change
        UIAccessibility.post(
            notification: .layoutChanged,  // Change in current screen
            argument: errorLabel
        )
    }

    func showLoadingComplete() {
        // Announce without moving focus
        UIAccessibility.post(
            notification: .announcement,
            argument: "Loading complete"
        )
    }
}

The .screenChanged and .layoutChanged notifications inform VoiceOver of significant interface changes.

Ready to ace your iOS interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Adjustable Elements and Complex Controls

11. How do you make a custom slider accessible?

A custom slider must implement the .adjustable trait and respond to increment/decrement gestures. VoiceOver uses vertical swipe to modify the value.

AccessibleSlider.swiftswift
// In SwiftUI: Slider is accessible by default
struct VolumeControl: View {
    @Binding var volume: Double

    var body: some View {
        Slider(value: $volume, in: 0...100, step: 5)
            .accessibilityLabel("Volume")
            .accessibilityValue("\(Int(volume)) percent")
    }
}

// Custom accessible slider
struct CustomRatingSlider: View {
    @Binding var rating: Int

    var body: some View {
        HStack {
            ForEach(1...5, id: \.self) { star in
                Image(systemName: star <= rating ? "star.fill" : "star")
                    .onTapGesture { rating = star }
            }
        }
        .accessibilityElement(children: .ignore)
        .accessibilityLabel("Rating")
        .accessibilityValue("\(rating) star\(rating > 1 ? "s" : "") out of 5")
        .accessibilityAdjustableAction { direction in
            switch direction {
            case .increment:
                rating = min(5, rating + 1)
            case .decrement:
                rating = max(1, rating - 1)
            @unknown default:
                break
            }
        }
    }
}

// In UIKit: adjustable trait
class StarRatingView: UIView {
    var rating: Int = 3 {
        didSet {
            updateStars()
            // Announce the new value
            UIAccessibility.post(
                notification: .announcement,
                argument: accessibilityValue
            )
        }
    }

    override var accessibilityTraits: UIAccessibilityTraits {
        get { .adjustable }  // Enables adjustment gestures
        set { }
    }

    override var accessibilityLabel: String? {
        get { "Rating" }
        set { }
    }

    override var accessibilityValue: String? {
        get { "\(rating) stars out of 5" }
        set { }
    }

    // Called on swipe up
    override func accessibilityIncrement() {
        rating = min(5, rating + 1)
    }

    // Called on swipe down
    override func accessibilityDecrement() {
        rating = max(1, rating - 1)
    }
}

Without the .adjustable trait, a custom slider would require tapping each star individually.

Carousels pose accessibility challenges: non-linear navigation, off-screen content, and multiple states. The solution combines the .adjustable trait with contextual announcements.

AccessibleCarousel.swiftswift
struct ImageCarousel: View {
    let images: [CarouselImage]
    @State private var currentIndex = 0

    var body: some View {
        TabView(selection: $currentIndex) {
            ForEach(images.indices, id: \.self) { index in
                CarouselItem(image: images[index])
                    .tag(index)
            }
        }
        .tabViewStyle(.page)
        .accessibilityElement(children: .contain)
        .accessibilityLabel("Image carousel")
        .accessibilityValue("Image \(currentIndex + 1) of \(images.count)")
        .accessibilityHint("Swipe to change image")
        .accessibilityAdjustableAction { direction in
            withAnimation {
                switch direction {
                case .increment:
                    currentIndex = min(images.count - 1, currentIndex + 1)
                case .decrement:
                    currentIndex = max(0, currentIndex - 1)
                @unknown default:
                    break
                }
            }
        }
    }
}

// In UIKit with UIPageViewController
class CarouselAccessibilityContainer: UIView {
    var currentPage = 0
    var totalPages = 5

    override var isAccessibilityElement: Bool {
        get { true }
        set { }
    }

    override var accessibilityTraits: UIAccessibilityTraits {
        get { .adjustable }
        set { }
    }

    override var accessibilityLabel: String? {
        get { "Promotional carousel" }
        set { }
    }

    override var accessibilityValue: String? {
        get { "Page \(currentPage + 1) of \(totalPages)" }
        set { }
    }

    override func accessibilityIncrement() {
        guard currentPage < totalPages - 1 else { return }
        currentPage += 1
        delegate?.scrollToPage(currentPage)
    }

    override func accessibilityDecrement() {
        guard currentPage > 0 else { return }
        currentPage -= 1
        delegate?.scrollToPage(currentPage)
    }
}

This approach enables carousel navigation without relying on standard swipe gestures.

13. How do you handle form accessibility?

Accessible forms associate each field with its label, clearly indicate errors, and allow smooth navigation between fields.

AccessibleForms.swiftswift
struct RegistrationForm: View {
    @State private var email = ""
    @State private var password = ""
    @State private var emailError: String?
    @State private var passwordError: String?

    var body: some View {
        Form {
            Section {
                TextField("Email", text: $email)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .accessibilityLabel("Email address")
                    .accessibilityHint(emailError != nil ? "Error: \(emailError!)" : nil)

                if let error = emailError {
                    Text(error)
                        .foregroundColor(.red)
                        .font(.caption)
                        .accessibilityLabel("Email error: \(error)")
                }
            }

            Section {
                SecureField("Password", text: $password)
                    .textContentType(.newPassword)
                    .accessibilityLabel("Password")
                    .accessibilityHint(passwordHint)

                PasswordStrengthIndicator(password: password)
            }

            Button("Create account") {
                submitForm()
            }
            .disabled(!isFormValid)
            .accessibilityHint(isFormValid ? "Double-tap to create your account" : "Form incomplete")
        }
    }

    var passwordHint: String {
        if let error = passwordError {
            return "Error: \(error)"
        }
        return "Minimum 8 characters with uppercase and numbers"
    }
}

// Accessible password strength indicator
struct PasswordStrengthIndicator: View {
    let password: String

    var strength: PasswordStrength {
        // Calculation logic
        PasswordStrength.calculate(password)
    }

    var body: some View {
        HStack(spacing: 4) {
            ForEach(0..<4) { index in
                Rectangle()
                    .fill(index < strength.level ? strength.color : Color.gray.opacity(0.3))
                    .frame(height: 4)
            }
        }
        .accessibilityElement(children: .ignore)
        .accessibilityLabel("Password strength")
        .accessibilityValue(strength.description)  // "Weak", "Medium", "Strong", "Very strong"
    }
}

Accessibility hints communicate errors and instructions without cluttering the visual interface.

Accessibility Audits and Testing

14. How do you audit an iOS app's accessibility?

Auditing combines automated tools and manual testing. Xcode Accessibility Inspector analyzes elements, VoiceOver validates the real experience, and unit tests verify accessibility properties.

AccessibilityAudit.swiftswift
// Unit test accessibility properties
import XCTest
@testable import MyApp

class AccessibilityTests: XCTestCase {

    func testProductCardAccessibility() {
        let product = Product(name: "iPhone", price: "$999", rating: "4.5")
        let view = ProductCardView(product: product)

        // Verify element is accessible
        XCTAssertTrue(view.isAccessibilityElement)

        // Verify label
        XCTAssertEqual(
            view.accessibilityLabel,
            "iPhone, $999, 4.5 stars"
        )

        // Verify traits
        XCTAssertTrue(view.accessibilityTraits.contains(.button))
    }

    func testDynamicTypeSupport() {
        let label = UILabel()
        label.font = UIFont.preferredFont(forTextStyle: .body)

        // Verify automatic adjustment
        XCTAssertTrue(label.adjustsFontForContentSizeCategory)
    }
}

// UI test with simulated VoiceOver
class AccessibilityUITests: XCTestCase {

    func testLoginFlowAccessibility() {
        let app = XCUIApplication()
        app.launch()

        // Access elements by accessibility label
        let emailField = app.textFields["Email address"]
        XCTAssertTrue(emailField.exists)

        emailField.tap()
        emailField.typeText("test@example.com")

        let passwordField = app.secureTextFields["Password"]
        XCTAssertTrue(passwordField.exists)

        let loginButton = app.buttons["Sign in"]
        XCTAssertTrue(loginButton.exists)
        XCTAssertTrue(loginButton.isEnabled)
    }

    // Automatic audit iOS 17+
    func testAccessibilityAudit() throws {
        let app = XCUIApplication()
        app.launch()

        // Automatic screen audit
        try app.performAccessibilityAudit()
    }
}

The iOS 17+ automatic audit detects common issues: missing labels, insufficient contrast, elements too small.

15. What are the most common accessibility mistakes?

Frequent mistakes include missing labels, undescribed images, lack of Dynamic Type support, and interactive elements without appropriate traits.

CommonMistakes.swiftswift
// WRONG: Button without accessible label
Button {
    performAction()
} label: {
    Image(systemName: "plus")
}
// VoiceOver reads: "Button" - useless

// CORRECT: Explicit label
Button {
    performAction()
} label: {
    Image(systemName: "plus")
}
.accessibilityLabel("Add item")


// WRONG: Fixed font size
Text("Important")
    .font(.system(size: 16))  // Doesn't respect Dynamic Type

// CORRECT: Semantic text style
Text("Important")
    .font(.body)  // Adapts to preferences


// WRONG: Informative image without description
Image("product-photo")

// CORRECT: Content description
Image("product-photo")
    .accessibilityLabel("Product photo: Black audio headphones")


// WRONG: Clickable element without button trait
Text("Learn more")
    .onTapGesture { showDetails() }
// VoiceOver doesn't know it's interactive

// CORRECT: Appropriate traits
Text("Learn more")
    .onTapGesture { showDetails() }
    .accessibilityAddTraits(.isButton)
    .accessibilityHint("Double-tap to show details")


// WRONG: Insufficient contrast
Text("Light gray text")
    .foregroundColor(.gray.opacity(0.4))

// CORRECT: Minimum contrast ratio 4.5:1
Text("Readable text")
    .foregroundColor(.secondary)  // Respects guidelines


// WRONG: Touch target too small
Button { } label: {
    Image(systemName: "xmark")
        .font(.caption)  // Too small (< 44x44 points)
}

// CORRECT: Minimum 44x44 point area
Button { } label: {
    Image(systemName: "xmark")
}
.frame(minWidth: 44, minHeight: 44)

These mistakes are detectable with Accessibility Inspector and fixable with a few lines of code.

Conclusion

iOS accessibility rests on three pillars: VoiceOver for visually impaired users, Dynamic Type for text adaptation, and semantic traits for consistent navigation. Mastering these concepts demonstrates professional expertise that employers actively seek.

Review Checklist

  • Configure VoiceOver with appropriate labels, hints, and traits
  • Implement Dynamic Type with semantic text styles
  • Adapt layouts for extreme accessibility sizes
  • Group elements for efficient navigation
  • Manage focus programmatically after interface changes
  • Make custom controls adjustable
  • Regularly audit with Accessibility Inspector and automated tests
  • Avoid common mistakes: missing labels, undescribed images, insufficient contrast

Additional Resources

Apple's official "Human Interface Guidelines - Accessibility" documentation remains the reference. Regular testing with VoiceOver enabled helps identify issues that automated tools cannot detect.

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Tags

#ios
#accessibility
#voiceover
#dynamic-type
#swift

Share

Related articles