Perguntas de entrevista sobre acessibilidade iOS em 2026: VoiceOver e Dynamic Type

Prepare-se para entrevistas iOS com perguntas-chave de acessibilidade: VoiceOver, Dynamic Type, traits semânticos e auditorias.

Perguntas de entrevista sobre acessibilidade iOS: VoiceOver e Dynamic Type

A acessibilidade tornou-se uma habilidade crítica no desenvolvimento iOS. Os recrutadores agora avaliam sistematicamente o domínio de VoiceOver, Dynamic Type e das APIs de acessibilidade. Estas perguntas cobrem os conceitos essenciais para ter sucesso em entrevistas técnicas.

Por que a acessibilidade importa nas entrevistas

Mais de 1 bilhão de pessoas vivem com alguma deficiência. A Apple impõe padrões rigorosos de acessibilidade, e muitas empresas se recusam a publicar apps inacessíveis. Essa competência diferencia os candidatos sêniores dos demais.

Fundamentos de acessibilidade iOS

1. Quais são as principais ferramentas de acessibilidade do iOS?

O iOS oferece um ecossistema completo de ferramentas de acessibilidade voltadas para diferentes tipos de deficiência. O VoiceOver permite a leitura de tela para pessoas com deficiência visual. O Dynamic Type adapta o tamanho do texto. O Switch Control habilita o controle por interruptores externos. O Voice Control oferece comandos de voz completos.

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()
}

Essas APIs permitem adaptar dinamicamente a interface conforme as preferências do usuário e as tecnologias assistivas ativas.

2. Como o VoiceOver funciona tecnicamente?

O VoiceOver percorre a interface identificando os elementos acessíveis. Cada elemento expõe propriedades como label, value, traits e ações. O leitor de tela sintetiza vocalmente essas informações enquanto o usuário navega com gestos.

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")
    }
}

A hierarquia de acessibilidade pode diferir da hierarquia visual, permitindo navegação lógica mesmo em layouts complexos.

3. Qual é a diferença entre accessibilityLabel e accessibilityValue?

O accessibilityLabel identifica permanentemente o elemento, enquanto o accessibilityValue representa seu estado atual, que pode mudar. Essa distinção é fundamental para controles dinâmicos como sliders ou 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" : "")")
    }
}

O VoiceOver sempre anuncia o label seguido pelo value, permitindo ao usuário entender o que é o elemento e qual seu estado atual.

4. Explique accessibilityTraits e sua importância

Os traits informam ao VoiceOver o tipo e o comportamento de um elemento. Eles determinam como o elemento será anunciado e quais ações estão disponíveis. Um botão sem o trait .button não será anunciado como tal.

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 : [])
    }
}

Os traits permitem que as tecnologias assistivas ofereçam uma experiência consistente e previsível para os usuários.

Dynamic Type e tamanhos de texto adaptáveis

5. Como implementar Dynamic Type corretamente?

O Dynamic Type permite que o usuário ajuste o tamanho do texto do sistema. Uma implementação correta utiliza estilos de texto semânticos e constraints flexíveis para que a interface se adapte automaticamente.

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)
    }
}

Sem adjustsFontForContentSizeCategory, os labels mantêm seu tamanho inicial mesmo quando o usuário altera as preferências.

6. Como lidar com tamanhos de acessibilidade extremos?

Os tamanhos de acessibilidade (AX1 a AX5) podem triplicar o tamanho do texto. A interface deve se adaptar com layouts alternativos: empilhar verticalmente em vez de horizontalmente, ocultar elementos decorativos e ajustar o espaçamento.

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
        }
    }
}

As categorias de acessibilidade começam em .accessibilityMedium (AX1). O design precisa antecipar esses casos desde o início.

Pronto para mandar bem nas entrevistas de iOS?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

7. Como tornar imagens acessíveis?

As imagens exigem tratamento diferente conforme seu papel: as informativas precisam de descrição, as decorativas devem ser ignoradas e as interativas exigem labels de ação.

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 { }
    }
}

Uma imagem mal descrita ou uma decorativa não mascarada degrada significativamente a experiência com VoiceOver.

Agrupamento e navegação avançada

8. Como agrupar elementos para o VoiceOver?

O agrupamento combina vários elementos visuais em um único elemento acessível. Isso acelera a navegação e fornece o contexto completo em um único anúncio.

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 { }
    }
}

Sem agrupamento, o VoiceOver para em cada Text individualmente, tornando a navegação muito mais lenta.

9. Como implementar ações de acessibilidade personalizadas?

As ações personalizadas adicionam funcionalidade acessível sem sobrecarregar a interface visual. Os usuários do VoiceOver acessam essas ações com um swipe vertical.

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
    }
}

As ações personalizadas substituem com vantagem os gestos de swipe, difíceis de descobrir com o VoiceOver.

10. Como gerenciar o foco do VoiceOver de forma programática?

Controlar o foco é essencial após mudanças na interface: surgimento de modal, carregamento de conteúdo, navegação. O foco deve guiar o usuário até a informação relevante.

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"
        )
    }
}

As notificações .screenChanged e .layoutChanged informam o VoiceOver sobre mudanças significativas na interface.

Pronto para mandar bem nas entrevistas de iOS?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Elementos ajustáveis e controles complexos

11. Como tornar um slider personalizado acessível?

Um slider personalizado precisa implementar o trait .adjustable e responder a gestos de incremento/decremento. O VoiceOver usa o swipe vertical para alterar o valor.

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)
    }
}

Sem o trait .adjustable, um slider personalizado obrigaria o usuário a tocar em cada estrela individualmente.

12. Como criar um carrossel acessível?

Os carrosséis impõem desafios de acessibilidade: navegação não linear, conteúdo fora da tela e múltiplos estados. A solução combina o trait .adjustable com anúncios contextuais.

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)
    }
}

Essa abordagem permite navegar pelo carrossel sem depender dos gestos padrão de swipe.

13. Como lidar com a acessibilidade de formulários?

Formulários acessíveis associam cada campo ao seu label, indicam erros com clareza e permitem navegação fluida entre campos.

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"
    }
}

Os hints de acessibilidade comunicam erros e instruções sem sobrecarregar a interface visual.

Auditorias e testes de acessibilidade

14. Como auditar a acessibilidade de um app iOS?

A auditoria combina ferramentas automatizadas e testes manuais. O Accessibility Inspector do Xcode analisa os elementos, o VoiceOver valida a experiência real e os testes unitários verificam as propriedades de acessibilidade.

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()
    }
}

A auditoria automática do iOS 17+ detecta problemas comuns: labels ausentes, contraste insuficiente, elementos pequenos demais.

15. Quais são os erros de acessibilidade mais comuns?

Os erros frequentes incluem labels ausentes, imagens sem descrição, falta de suporte a Dynamic Type e elementos interativos sem traits adequados.

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)

Esses erros são detectáveis pelo Accessibility Inspector e corrigidos com poucas linhas de código.

Conclusão

A acessibilidade iOS apoia-se em três pilares: o VoiceOver para pessoas com deficiência visual, o Dynamic Type para a adaptação do texto e os traits semânticos para uma navegação consistente. Dominar esses conceitos demonstra um conhecimento profissional muito procurado pelas empresas.

Checklist de revisão

  • Configurar o VoiceOver com labels, hints e traits adequados
  • Implementar Dynamic Type com estilos de texto semânticos
  • Adaptar layouts para tamanhos de acessibilidade extremos
  • Agrupar elementos para uma navegação eficiente
  • Gerenciar o foco programaticamente após mudanças na interface
  • Tornar ajustáveis os controles personalizados
  • Auditar regularmente com o Accessibility Inspector e testes automatizados
  • Evitar erros comuns: labels ausentes, imagens sem descrição, contraste insuficiente

Recursos adicionais

A documentação oficial da Apple "Human Interface Guidelines - Accessibility" continua sendo a referência. Testar regularmente com o VoiceOver ativado ajuda a identificar problemas que as ferramentas automáticas não detectam.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

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

Compartilhar

Artigos relacionados