# Questions entretien iOS accessibilité en 2026 : VoiceOver et Dynamic Type > Préparez vos entretiens iOS avec les questions clés sur l'accessibilité : VoiceOver, Dynamic Type, traits sémantiques et audits d'accessibilité. - Published: 2026-03-12 - Updated: 2026-03-28 - Author: SharpSkill - Tags: ios, accessibility, voiceover, dynamic-type, swift - Reading time: 14 min --- L'accessibilité est devenue un critère incontournable dans le développement iOS. Les recruteurs évaluent désormais systématiquement la maîtrise de VoiceOver, Dynamic Type et des APIs d'accessibilité. Ces questions couvrent les concepts essentiels pour réussir un entretien technique. > **Pourquoi l'accessibilité en entretien ?** > > Plus d'1 milliard de personnes vivent avec un handicap. Apple impose des standards d'accessibilité stricts, et de nombreuses entreprises refusent de publier des apps non accessibles. Cette compétence différencie les candidats senior. ## Les fondamentaux de l'accessibilité iOS ### 1. Quels sont les principaux outils d'accessibilité iOS ? iOS propose un écosystème complet d'outils d'accessibilité destinés à différents types de handicaps. VoiceOver permet la lecture d'écran pour les personnes malvoyantes. Dynamic Type adapte la taille du texte. Switch Control permet le contrôle par contacteur externe. Voice Control offre la commande vocale complète. ```swift // AccessibilityTools.swift // Vérifier si VoiceOver est actif if UIAccessibility.isVoiceOverRunning { // Adapter l'interface pour VoiceOver showSimplifiedInterface() } // Vérifier si Reduce Motion est activé if UIAccessibility.isReduceMotionEnabled { // Désactiver les animations complexes animationDuration = 0 } // Vérifier si Bold Text est activé if UIAccessibility.isBoldTextEnabled { // Utiliser des polices plus épaisses applyBoldFonts() } // Observer les changements de paramètres NotificationCenter.default.addObserver( forName: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil, queue: .main ) { _ in // Réagir au changement updateAccessibilityLayout() } ``` Ces APIs permettent d'adapter dynamiquement l'interface selon les préférences utilisateur et les technologies d'assistance actives. ### 2. Comment fonctionne VoiceOver techniquement ? VoiceOver parcourt l'interface en identifiant les éléments accessibles. Chaque élément expose des propriétés comme son label, sa valeur, ses traits et ses actions. Le lecteur d'écran synthétise ces informations vocalement pendant que l'utilisateur navigue par gestes. ```swift // VoiceOverBasics.swift // En UIKit : configurer un élément accessible class CustomButton: UIButton { override var accessibilityLabel: String? { get { "Bouton de validation" } // Ce que VoiceOver lit set { } } override var accessibilityHint: String? { get { "Double-tapez pour confirmer la commande" } // Action possible set { } } override var accessibilityTraits: UIAccessibilityTraits { get { .button } // Type d'élément set { } } override var isAccessibilityElement: Bool { get { true } // Élément focusable par VoiceOver set { } } } // En SwiftUI : équivalent avec les modifiers struct ValidateButton: View { var body: some View { Button("Valider") { confirmOrder() } .accessibilityLabel("Bouton de validation") .accessibilityHint("Double-tapez pour confirmer la commande") } } ``` La hiérarchie d'accessibilité peut différer de la hiérarchie visuelle, permettant une navigation logique même avec des layouts complexes. ### 3. Quelle est la différence entre accessibilityLabel et accessibilityValue ? Le `accessibilityLabel` identifie l'élément de manière permanente, tandis que `accessibilityValue` représente son état actuel qui peut changer. Cette distinction est cruciale pour les contrôles dynamiques comme les sliders ou les switches. ```swift // LabelVsValue.swift // Exemple avec un slider de volume class VolumeSlider: UISlider { override var accessibilityLabel: String? { get { "Volume" } // Identité fixe de l'élément set { } } override var accessibilityValue: String? { get { "\(Int(value * 100))%" } // État actuel qui change set { } } } // VoiceOver lit : "Volume, 75%" // En SwiftUI avec un Toggle struct NotificationToggle: View { @Binding var isEnabled: Bool var body: some View { Toggle(isOn: $isEnabled) { Text("Notifications") } .accessibilityLabel("Notifications push") // accessibilityValue est automatique pour Toggle : "activé" ou "désactivé" } } // Pour un stepper personnalisé struct QuantityStepper: View { @State private var quantity = 1 var body: some View { Stepper("Quantité", value: $quantity, in: 1...99) .accessibilityLabel("Quantité d'articles") .accessibilityValue("\(quantity) article\(quantity > 1 ? "s" : "")") } } ``` VoiceOver annonce toujours le label suivi de la valeur, permettant à l'utilisateur de comprendre à la fois ce qu'est l'élément et son état actuel. ### 4. Expliquez les accessibilityTraits et leur importance Les traits informent VoiceOver sur le type et le comportement d'un élément. Ils déterminent comment l'élément est annoncé et quelles actions sont disponibles. Un bouton sans le trait `.button` ne sera pas annoncé comme tel. ```swift // AccessibilityTraits.swift // Traits courants en UIKit class CustomCell: UITableViewCell { override var accessibilityTraits: UIAccessibilityTraits { get { var traits: UIAccessibilityTraits = [] // Type d'élément traits.insert(.button) // Élément interactif // État actuel if isSelected { traits.insert(.selected) // Actuellement sélectionné } // Caractéristiques spéciales if !isEnabled { traits.insert(.notEnabled) // Désactivé } return traits } set { } } } // Traits disponibles // .button - Élément cliquable // .link - Lien hypertexte // .header - Titre de section // .image - Image // .selected - État sélectionné // .notEnabled - Désactivé // .adjustable - Valeur modifiable (slider) // .staticText - Texte non interactif // .searchField - Champ de recherche // .playsSound - Joue un son // .startsMediaSession - Lance une lecture média // En SwiftUI struct SectionHeader: View { let title: String var body: some View { Text(title) .font(.headline) .accessibilityAddTraits(.isHeader) // Annoncé comme titre } } struct SelectableRow: View { let isSelected: Bool var body: some View { HStack { Text("Option") if isSelected { Image(systemName: "checkmark") } } .accessibilityElement(children: .combine) .accessibilityAddTraits(isSelected ? .isSelected : []) } } ``` Les traits permettent aux technologies d'assistance d'offrir une expérience cohérente et prévisible à l'utilisateur. ## Dynamic Type et tailles de texte adaptatives ### 5. Comment implémenter Dynamic Type correctement ? Dynamic Type permet aux utilisateurs d'ajuster la taille du texte système. Une implémentation correcte utilise les styles de texte sémantiques et les contraintes flexibles pour que l'interface s'adapte automatiquement. ```swift // DynamicType.swift // En UIKit : utiliser les styles de texte préférés class ArticleCell: UITableViewCell { let titleLabel = UILabel() let bodyLabel = UILabel() func configure() { // Utiliser les styles de texte sémantiques titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) bodyLabel.font = UIFont.preferredFont(forTextStyle: .body) // CRUCIAL : activer l'ajustement automatique titleLabel.adjustsFontForContentSizeCategory = true bodyLabel.adjustsFontForContentSizeCategory = true // Permettre le multiline pour les grandes tailles titleLabel.numberOfLines = 0 bodyLabel.numberOfLines = 0 } } // En SwiftUI : c'est automatique avec les styles struct ArticleView: View { var body: some View { VStack(alignment: .leading) { Text("Titre de l'article") .font(.headline) // S'adapte automatiquement Text("Contenu de l'article...") .font(.body) } } } // Police personnalisée avec Dynamic Type extension UIFont { static func customFont( size: CGFloat, style: TextStyle ) -> UIFont { let customFont = UIFont(name: "CustomFont-Regular", size: size)! // Adapter à la catégorie de taille actuelle return UIFontMetrics(forTextStyle: style) .scaledFont(for: customFont) } } ``` Sans `adjustsFontForContentSizeCategory`, les labels gardent leur taille initiale même quand l'utilisateur modifie ses préférences. ### 6. Comment gérer les tailles d'accessibilité extrêmes ? Les tailles d'accessibilité (AX1 à AX5) peuvent tripler la taille du texte. L'interface doit s'adapter avec des layouts alternatifs : empiler verticalement au lieu d'horizontalement, masquer les éléments décoratifs, et ajuster les espacements. ```swift // LargeContentSize.swift // Détecter les tailles d'accessibilité struct AdaptiveLayout: View { @Environment(\.dynamicTypeSize) var dynamicTypeSize var body: some View { // Layout différent pour les grandes tailles if dynamicTypeSize.isAccessibilitySize { // Layout vertical pour grandes tailles VStack(alignment: .leading, spacing: 12) { iconAndTitle actionButtons } } else { // Layout horizontal standard HStack { iconAndTitle Spacer() actionButtons } } } var iconAndTitle: some View { HStack { Image(systemName: "bell.fill") .accessibilityHidden(true) // Décoratif, ignorer Text("Notifications") } } var actionButtons: some View { HStack(spacing: 16) { Button("Activer") { } Button("Paramètres") { } } } } // En UIKit : observer les changements class AdaptiveViewController: UIViewController { override func traitCollectionDidChange( _ previousTraitCollection: UITraitCollection? ) { super.traitCollectionDidChange(previousTraitCollection) // Vérifier si la catégorie de taille a changé if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory { updateLayoutForContentSize() } } func updateLayoutForContentSize() { let category = traitCollection.preferredContentSizeCategory if category.isAccessibilityCategory { // Activer le layout accessible stackView.axis = .vertical decorativeView.isHidden = true } else { // Layout standard stackView.axis = .horizontal decorativeView.isHidden = false } } } ``` Les catégories d'accessibilité commencent à `.accessibilityMedium` (AX1). Le design doit anticiper ces cas dès la conception. ### 7. Comment rendre les images accessibles ? Les images nécessitent un traitement différencié selon leur rôle : les images informatives ont besoin d'une description, les images décoratives doivent être ignorées, et les images interactives requièrent un label d'action. ```swift // AccessibleImages.swift // Image informative : décrire le contenu struct ProductImage: View { let product: Product var body: some View { AsyncImage(url: product.imageURL) { image in image .resizable() .accessibilityLabel(product.imageDescription) // "Photo du produit : iPhone 15 Pro, coloris titane" } placeholder: { ProgressView() .accessibilityLabel("Chargement de l'image") } } } // Image décorative : masquer de VoiceOver struct DecorationView: View { var body: some View { Image(systemName: "sparkles") .accessibilityHidden(true) // Ignorée par VoiceOver } } // Bouton image : décrire l'action, pas l'image struct FavoriteButton: View { @Binding var isFavorite: Bool var body: some View { Button { isFavorite.toggle() } label: { Image(systemName: isFavorite ? "heart.fill" : "heart") } // Ne pas décrire l'icône, décrire l'action .accessibilityLabel(isFavorite ? "Retirer des favoris" : "Ajouter aux favoris") } } // En 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 avec texte intégré (infographie) class InfographicView: UIImageView { override var accessibilityLabel: String? { get { // Transcrire le contenu textuel de l'infographie """ Infographie statistiques 2026. Croissance : 45%. Utilisateurs actifs : 2.3 millions. Satisfaction : 4.8 sur 5. """ } set { } } } ``` Une image mal décrite ou une image décorative non masquée dégrade significativement l'expérience VoiceOver. ## Groupement et navigation avancée ### 8. Comment regrouper des éléments pour VoiceOver ? Le groupement permet de combiner plusieurs éléments visuels en un seul élément accessible. Cela accélère la navigation et fournit un contexte complet en une seule annonce. ```swift // AccessibilityGrouping.swift // En SwiftUI : combiner les enfants 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) } } // Combine tous les textes en un seul élément .accessibilityElement(children: .combine) // VoiceOver lit : "iPhone 15 Pro, 1199€, 4.8 étoiles" } } // En SwiftUI : ignorer les enfants et définir manuellement 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") } // Ignorer la hiérarchie des enfants .accessibilityElement(children: .ignore) // Définir un label personnalisé .accessibilityLabel("Commande de \(order.itemCount) articles pour \(order.total)") .accessibilityHint("Double-tapez pour voir les détails") .accessibilityAddTraits(.isButton) } } // En UIKit : conteneur accessible class ProductCardView: UIView { let nameLabel = UILabel() let priceLabel = UILabel() let ratingLabel = UILabel() override var isAccessibilityElement: Bool { get { true } // Le conteneur est l'élément accessible set { } } override var accessibilityLabel: String? { get { // Combiner les informations "\(nameLabel.text ?? ""), \(priceLabel.text ?? ""), \(ratingLabel.text ?? "")" } set { } } } ``` Sans groupement, VoiceOver s'arrête sur chaque Text individuellement, ralentissant considérablement la navigation. ### 9. Comment implémenter les actions d'accessibilité personnalisées ? Les actions personnalisées permettent d'ajouter des fonctionnalités accessibles sans encombrer l'interface visuelle. L'utilisateur VoiceOver y accède par un balayage vertical. ```swift // CustomAccessibilityActions.swift // En SwiftUI : actions personnalisées 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 accessibles par balayage vertical .accessibilityAction(named: "Archiver") { onArchive() } .accessibilityAction(named: "Supprimer") { onDelete() } .accessibilityAction(named: email.isFlagged ? "Retirer le drapeau" : "Marquer d'un drapeau") { onFlag() } } } // En UIKit : override accessibilityCustomActions class EmailCell: UITableViewCell { var email: Email! var onArchive: (() -> Void)? var onDelete: (() -> Void)? override var accessibilityCustomActions: [UIAccessibilityCustomAction]? { get { [ UIAccessibilityCustomAction( name: "Archiver", target: self, selector: #selector(archiveAction) ), UIAccessibilityCustomAction( name: "Supprimer", target: self, selector: #selector(deleteAction) ) ] } set { } } @objc private func archiveAction() -> Bool { onArchive?() return true // true = action réussie } @objc private func deleteAction() -> Bool { onDelete?() return true } } ``` Les actions personnalisées remplacent avantageusement les gestes de swipe qui sont difficiles à découvrir avec VoiceOver. ### 10. Comment gérer le focus VoiceOver programmatiquement ? Contrôler le focus est essentiel après les changements d'interface : apparition de modale, chargement de contenu, navigation. Le focus doit guider l'utilisateur vers l'information pertinente. ```swift // AccessibilityFocus.swift // En SwiftUI avec @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("Rechercher", text: $searchText) .onSubmit { performSearch() } List(results) { result in ResultRow(result: result) .accessibilityFocused($focusedResult, equals: result) } } } func performSearch() { Task { results = await api.search(searchText) // Déplacer le focus sur le premier résultat if let first = results.first { focusedResult = first } } } } // En UIKit : poster une notification class ModalViewController: UIViewController { @IBOutlet weak var titleLabel: UILabel! override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // Annoncer et déplacer le focus UIAccessibility.post( notification: .screenChanged, // Nouvelle écran argument: titleLabel // Élément à focus ) } func showError(_ message: String) { errorLabel.text = message errorLabel.isHidden = false // Annoncer le changement de layout UIAccessibility.post( notification: .layoutChanged, // Changement dans l'écran actuel argument: errorLabel ) } func showLoadingComplete() { // Annoncer sans déplacer le focus UIAccessibility.post( notification: .announcement, argument: "Chargement terminé" ) } } ``` Les notifications `.screenChanged` et `.layoutChanged` informent VoiceOver des changements significatifs dans l'interface. ## Éléments ajustables et contrôles complexes ### 11. Comment rendre un slider personnalisé accessible ? Un slider personnalisé doit implémenter le trait `.adjustable` et répondre aux gestes d'incrémentation/décrémentation. VoiceOver utilise un balayage vertical pour modifier la valeur. ```swift // AccessibleSlider.swift // En SwiftUI : Slider est accessible par défaut struct VolumeControl: View { @Binding var volume: Double var body: some View { Slider(value: $volume, in: 0...100, step: 5) .accessibilityLabel("Volume") .accessibilityValue("\(Int(volume)) pour cent") } } // Slider personnalisé accessible 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("Note") .accessibilityValue("\(rating) étoile\(rating > 1 ? "s" : "") sur 5") .accessibilityAdjustableAction { direction in switch direction { case .increment: rating = min(5, rating + 1) case .decrement: rating = max(1, rating - 1) @unknown default: break } } } } // En UIKit : trait ajustable class StarRatingView: UIView { var rating: Int = 3 { didSet { updateStars() // Annoncer la nouvelle valeur UIAccessibility.post( notification: .announcement, argument: accessibilityValue ) } } override var accessibilityTraits: UIAccessibilityTraits { get { .adjustable } // Active les gestes d'ajustement set { } } override var accessibilityLabel: String? { get { "Note" } set { } } override var accessibilityValue: String? { get { "\(rating) étoiles sur 5" } set { } } // Appelé lors du balayage vers le haut override func accessibilityIncrement() { rating = min(5, rating + 1) } // Appelé lors du balayage vers le bas override func accessibilityDecrement() { rating = max(1, rating - 1) } } ``` Sans le trait `.adjustable`, un slider personnalisé nécessiterait de taper sur chaque étoile individuellement. ### 12. Comment créer un carrousel accessible ? Les carrousels posent des défis d'accessibilité : navigation non linéaire, contenu hors écran, et états multiples. La solution combine le trait `.adjustable` avec des annonces contextuelles. ```swift // AccessibleCarousel.swift 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("Carrousel d'images") .accessibilityValue("Image \(currentIndex + 1) sur \(images.count)") .accessibilityHint("Balayez pour changer d'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 } } } } } // En UIKit avec 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 { "Carrousel promotionnel" } set { } } override var accessibilityValue: String? { get { "Page \(currentPage + 1) sur \(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) } } ``` Cette approche permet de naviguer dans le carrousel sans dépendre des gestes de swipe standard. ### 13. Comment gérer l'accessibilité des formulaires ? Les formulaires accessibles associent chaque champ à son label, indiquent les erreurs clairement, et permettent une navigation fluide entre les champs. ```swift // AccessibleForms.swift 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("Adresse email") .accessibilityHint(emailError != nil ? "Erreur : \(emailError!)" : nil) if let error = emailError { Text(error) .foregroundColor(.red) .font(.caption) .accessibilityLabel("Erreur email : \(error)") } } Section { SecureField("Mot de passe", text: $password) .textContentType(.newPassword) .accessibilityLabel("Mot de passe") .accessibilityHint(passwordHint) PasswordStrengthIndicator(password: password) } Button("Créer le compte") { submitForm() } .disabled(!isFormValid) .accessibilityHint(isFormValid ? "Double-tapez pour créer votre compte" : "Formulaire incomplet") } } var passwordHint: String { if let error = passwordError { return "Erreur : \(error)" } return "Minimum 8 caractères avec majuscules et chiffres" } } // Indicateur de force du mot de passe accessible struct PasswordStrengthIndicator: View { let password: String var strength: PasswordStrength { // Logique de calcul 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("Force du mot de passe") .accessibilityValue(strength.description) // "Faible", "Moyen", "Fort", "Très fort" } } ``` Les hints d'accessibilité communiquent les erreurs et les instructions sans encombrer l'interface visuelle. ## Audits et tests d'accessibilité ### 14. Comment auditer l'accessibilité d'une app iOS ? L'audit combine des outils automatisés et des tests manuels. Xcode Accessibility Inspector analyse les éléments, VoiceOver valide l'expérience réelle, et les tests unitaires vérifient les propriétés d'accessibilité. ```swift // AccessibilityAudit.swift // Test unitaire des propriétés d'accessibilité 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) // Vérifier que l'élément est accessible XCTAssertTrue(view.isAccessibilityElement) // Vérifier le label XCTAssertEqual( view.accessibilityLabel, "iPhone, 999€, 4.5 étoiles" ) // Vérifier les traits XCTAssertTrue(view.accessibilityTraits.contains(.button)) } func testDynamicTypeSupport() { let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .body) // Vérifier l'ajustement automatique XCTAssertTrue(label.adjustsFontForContentSizeCategory) } } // Test UI avec VoiceOver simulé class AccessibilityUITests: XCTestCase { func testLoginFlowAccessibility() { let app = XCUIApplication() app.launch() // Accéder aux éléments par label d'accessibilité let emailField = app.textFields["Adresse email"] XCTAssertTrue(emailField.exists) emailField.tap() emailField.typeText("test@example.com") let passwordField = app.secureTextFields["Mot de passe"] XCTAssertTrue(passwordField.exists) let loginButton = app.buttons["Se connecter"] XCTAssertTrue(loginButton.exists) XCTAssertTrue(loginButton.isEnabled) } // Audit automatique iOS 17+ func testAccessibilityAudit() throws { let app = XCUIApplication() app.launch() // Audit automatique de l'écran try app.performAccessibilityAudit() } } ``` L'audit automatique iOS 17+ détecte les problèmes courants : labels manquants, contraste insuffisant, éléments trop petits. ### 15. Quelles sont les erreurs d'accessibilité les plus courantes ? Les erreurs fréquentes incluent les labels manquants, les images non décrites, l'absence de support Dynamic Type, et les éléments interactifs sans traits appropriés. ```swift // CommonMistakes.swift // ❌ ERREUR : Bouton sans label accessible Button { performAction() } label: { Image(systemName: "plus") } // VoiceOver lit : "Bouton" - inutile // ✅ CORRECT : Label explicite Button { performAction() } label: { Image(systemName: "plus") } .accessibilityLabel("Ajouter un article") // ❌ ERREUR : Taille de police fixe Text("Important") .font(.system(size: 16)) // Ne respecte pas Dynamic Type // ✅ CORRECT : Style de texte sémantique Text("Important") .font(.body) // S'adapte aux préférences // ❌ ERREUR : Image informative sans description Image("product-photo") // ✅ CORRECT : Description du contenu Image("product-photo") .accessibilityLabel("Photo du produit : Casque audio noir") // ❌ ERREUR : Élément cliquable sans trait button Text("En savoir plus") .onTapGesture { showDetails() } // VoiceOver ne sait pas que c'est interactif // ✅ CORRECT : Traits appropriés Text("En savoir plus") .onTapGesture { showDetails() } .accessibilityAddTraits(.isButton) .accessibilityHint("Double-tapez pour afficher les détails") // ❌ ERREUR : Contraste insuffisant Text("Texte gris clair") .foregroundColor(.gray.opacity(0.4)) // ✅ CORRECT : Ratio de contraste minimum 4.5:1 Text("Texte lisible") .foregroundColor(.secondary) // Respecte les guidelines // ❌ ERREUR : Zone tactile trop petite Button { } label: { Image(systemName: "xmark") .font(.caption) // Trop petit (< 44x44 points) } // ✅ CORRECT : Zone minimum de 44x44 points Button { } label: { Image(systemName: "xmark") } .frame(minWidth: 44, minHeight: 44) ``` Ces erreurs sont détectables avec Accessibility Inspector et corrigibles avec quelques lignes de code. ## Conclusion L'accessibilité iOS repose sur trois piliers : VoiceOver pour les utilisateurs malvoyants, Dynamic Type pour l'adaptation du texte, et les traits sémantiques pour une navigation cohérente. Maîtriser ces concepts démontre une expertise professionnelle recherchée. ### Checklist de révision - ✅ Configurer VoiceOver avec labels, hints et traits appropriés - ✅ Implémenter Dynamic Type avec les styles de texte sémantiques - ✅ Adapter les layouts pour les tailles d'accessibilité extrêmes - ✅ Grouper les éléments pour une navigation efficace - ✅ Gérer le focus programmatiquement après les changements d'interface - ✅ Rendre les contrôles personnalisés ajustables - ✅ Auditer régulièrement avec Accessibility Inspector et tests automatisés - ✅ Éviter les erreurs courantes : labels manquants, images non décrites, contraste insuffisant ### Ressources complémentaires La documentation Apple "Human Interface Guidelines - Accessibility" reste la référence. Tester régulièrement avec VoiceOver activé permet d'identifier les problèmes que les outils automatiques ne détectent pas. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/ios/questions-entretien-ios-accessibilite-voiceover-dynamic-type