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é.

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.
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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
Prêt à réussir tes entretiens iOS ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
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.
// 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.
// 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.
// 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.
// 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.
Prêt à réussir tes entretiens iOS ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
É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.
// 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.
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.
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é.
// 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.
// ❌ 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.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Combine vs async/await en Swift : patterns de migration progressive
Guide complet sur la migration de Combine vers async/await en Swift : stratégies progressives, bridging patterns, et coexistence des deux paradigmes dans une codebase iOS.

Swift Macros : exemples pratiques de métaprogrammation
Guide complet sur Swift Macros : création de macros freestanding et attached, manipulation de l'AST avec swift-syntax, et exemples pratiques pour réduire le boilerplate.

StoreKit 2 en entretien : gestion des abonnements et receipts validation
Préparez vos entretiens iOS avec ce guide complet sur StoreKit 2, la gestion des abonnements, la validation des receipts et les questions techniques fréquentes.