iOS-Accessibility-Interviewfragen 2026: VoiceOver und Dynamic Type
Vorbereitung auf iOS-Interviews mit zentralen Accessibility-Fragen: VoiceOver, Dynamic Type, semantische Traits und Audits.

Barrierefreiheit ist zu einer entscheidenden Fähigkeit in der iOS-Entwicklung geworden. Recruiter prüfen mittlerweile systematisch das Beherrschen von VoiceOver, Dynamic Type und Accessibility-APIs. Die folgenden Fragen decken die wesentlichen Konzepte ab, um in technischen Interviews erfolgreich zu sein.
Über eine Milliarde Menschen leben mit einer Behinderung. Apple setzt strenge Accessibility-Standards durch, und viele Unternehmen veröffentlichen keine unzugänglichen Apps. Diese Kompetenz hebt Senior-Kandidaten von der Masse ab.
Grundlagen der iOS-Accessibility
1. Welche sind die wichtigsten Accessibility-Werkzeuge unter iOS?
iOS bietet ein umfassendes Ökosystem von Accessibility-Werkzeugen, die unterschiedliche Arten von Behinderungen adressieren. VoiceOver ermöglicht Bildschirmausgabe für blinde und sehbehinderte Nutzer. Dynamic Type passt die Textgröße an. Switch Control unterstützt externe Schalter. Voice Control bietet vollständige Sprachsteuerung.
// 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()
}Diese APIs erlauben es, die Oberfläche dynamisch an die Nutzerpräferenzen und aktiven Hilfstechnologien anzupassen.
2. Wie funktioniert VoiceOver technisch?
VoiceOver durchläuft die Oberfläche und identifiziert die zugänglichen Elemente. Jedes Element stellt Eigenschaften wie Label, Value, Traits und Aktionen bereit. Der Screenreader synthetisiert diese Informationen vokal, während der Nutzer per Geste navigiert.
// 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")
}
}Die Accessibility-Hierarchie kann von der visuellen Hierarchie abweichen, was logische Navigation auch bei komplexen Layouts erlaubt.
3. Was ist der Unterschied zwischen accessibilityLabel und accessibilityValue?
Das accessibilityLabel identifiziert das Element dauerhaft, während accessibilityValue seinen aktuellen, veränderlichen Zustand abbildet. Diese Unterscheidung ist entscheidend für dynamische Steuerelemente wie Slider oder Switches.
// 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 gibt stets das Label gefolgt vom Value aus, sodass Nutzer sowohl das Element als auch dessen aktuellen Zustand erfassen.
4. Erkläre accessibilityTraits und ihre Bedeutung
Traits informieren VoiceOver über Typ und Verhalten eines Elements. Sie bestimmen, wie das Element angesagt wird und welche Aktionen verfügbar sind. Ein Button ohne den Trait .button wird nicht als solcher angekündigt.
// 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 sorgen dafür, dass Hilfstechnologien eine konsistente und vorhersehbare Erfahrung bieten.
Dynamic Type und adaptive Schriftgrößen
5. Wie wird Dynamic Type korrekt implementiert?
Dynamic Type erlaubt es Nutzern, die Systemschriftgröße anzupassen. Eine korrekte Implementierung verwendet semantische Textstile und flexible Constraints, damit sich die Oberfläche automatisch anpasst.
// 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)
}
}Ohne adjustsFontForContentSizeCategory behalten Labels ihre Ausgangsgröße, selbst wenn der Nutzer seine Präferenzen ändert.
6. Wie geht man mit extremen Accessibility-Größen um?
Die Accessibility-Größen (AX1 bis AX5) können den Text bis aufs Dreifache vergrößern. Die Oberfläche muss mit alternativen Layouts reagieren: vertikale statt horizontaler Anordnung, Ausblenden dekorativer Elemente und Anpassung der Abstände.
// 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
}
}
}Die Accessibility-Kategorien beginnen bei .accessibilityMedium (AX1). Das Design muss diese Fälle von Anfang an berücksichtigen.
Bereit für deine iOS-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
7. Wie macht man Bilder zugänglich?
Bilder erfordern eine unterschiedliche Behandlung je nach Funktion: informative Bilder benötigen Beschreibungen, dekorative sollen ignoriert werden, interaktive verlangen aktionsbezogene Labels.
// 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 { }
}
}Ein schlecht beschriebenes oder ein nicht ausgeblendetes dekoratives Bild verschlechtert die VoiceOver-Erfahrung deutlich.
Gruppierung und fortgeschrittene Navigation
8. Wie gruppiert man Elemente für VoiceOver?
Die Gruppierung fasst mehrere visuelle Elemente zu einem zugänglichen zusammen. Das beschleunigt die Navigation und liefert den vollständigen Kontext in einer einzigen Ansage.
// 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 { }
}
}Ohne Gruppierung hält VoiceOver bei jedem einzelnen Text an, was die Navigation deutlich verlangsamt.
9. Wie implementiert man benutzerdefinierte Accessibility-Aktionen?
Benutzerdefinierte Aktionen ergänzen zugängliche Funktionen, ohne die visuelle Oberfläche zu überladen. VoiceOver-Nutzer rufen sie per vertikalem Swipe auf.
// 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
}
}Benutzerdefinierte Aktionen ersetzen vorteilhaft die schwer auffindbaren Swipe-Gesten unter VoiceOver.
10. Wie steuert man den VoiceOver-Fokus programmatisch?
Die Fokussteuerung ist nach Änderungen an der Oberfläche essenziell: Modal erscheint, Inhalt lädt, Navigation. Der Fokus muss Nutzer zur relevanten Information führen.
// 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"
)
}
}Die Notifications .screenChanged und .layoutChanged informieren VoiceOver über bedeutende Änderungen der Oberfläche.
Bereit für deine iOS-Interviews?
Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.
Anpassbare Elemente und komplexe Steuerungen
11. Wie macht man einen benutzerdefinierten Slider zugänglich?
Ein benutzerdefinierter Slider muss den Trait .adjustable implementieren und auf Increment-/Decrement-Gesten reagieren. VoiceOver verwendet vertikale Swipes, um den Wert zu ändern.
// 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)
}
}Ohne den Trait .adjustable müsste der Nutzer jeden Stern einzeln antippen.
12. Wie erstellt man ein zugängliches Karussell?
Karussells stellen Accessibility-Herausforderungen: nicht-lineare Navigation, außerhalb des Bildschirms liegende Inhalte und mehrere Zustände. Die Lösung kombiniert den Trait .adjustable mit kontextuellen Ansagen.
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)
}
}Dieser Ansatz ermöglicht das Navigieren im Karussell, ohne auf Standard-Swipe-Gesten angewiesen zu sein.
13. Wie geht man mit der Accessibility von Formularen um?
Zugängliche Formulare verknüpfen jedes Feld mit seinem Label, weisen Fehler klar aus und ermöglichen flüssige Navigation zwischen den Feldern.
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 kommunizieren Fehler und Anweisungen, ohne die visuelle Oberfläche zu überladen.
Audits und Tests der Barrierefreiheit
14. Wie auditiert man die Barrierefreiheit einer iOS-App?
Ein Audit kombiniert automatisierte Werkzeuge und manuelle Tests. Der Accessibility Inspector von Xcode analysiert Elemente, VoiceOver validiert die reale Nutzererfahrung und Unit-Tests prüfen Accessibility-Eigenschaften.
// 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()
}
}Das automatische Audit unter iOS 17+ erkennt häufige Probleme: fehlende Labels, ungenügenden Kontrast, zu kleine Elemente.
15. Welche Accessibility-Fehler sind am häufigsten?
Häufige Fehler sind fehlende Labels, nicht beschriebene Bilder, fehlende Dynamic-Type-Unterstützung und interaktive Elemente ohne passende Traits.
// 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)Diese Fehler lassen sich mit dem Accessibility Inspector erkennen und mit wenigen Codezeilen beheben.
Fazit
Die iOS-Barrierefreiheit ruht auf drei Säulen: VoiceOver für sehbehinderte Nutzer, Dynamic Type für die Textanpassung und semantische Traits für eine konsistente Navigation. Die Beherrschung dieser Konzepte demonstriert professionelles Know-how, das Arbeitgeber aktiv suchen.
Prüfliste
- VoiceOver mit passenden Labels, Hints und Traits konfigurieren
- Dynamic Type mit semantischen Textstilen umsetzen
- Layouts an extreme Accessibility-Größen anpassen
- Elemente für effiziente Navigation gruppieren
- Fokus nach Oberflächenänderungen programmatisch steuern
- Eigene Steuerungen anpassbar machen
- Regelmäßig mit dem Accessibility Inspector und automatisierten Tests auditieren
- Häufige Fehler vermeiden: fehlende Labels, unbeschriebene Bilder, unzureichender Kontrast
Weitere Ressourcen
Die offizielle Apple-Dokumentation "Human Interface Guidelines - Accessibility" bleibt die zentrale Referenz. Regelmäßiges Testen mit aktiviertem VoiceOver hilft, Probleme aufzudecken, die automatische Tools nicht erfassen.
Fang an zu üben!
Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.
Tags
Teilen
Verwandte Artikel

Combine vs async/await in Swift: Progressive Migrationsmuster
Vollständiger Leitfaden zur Migration von Combine zu async/await in Swift: progressive Strategien, Bridging-Muster und Paradigmen-Koexistenz in iOS-Codebasen.

Swift Macros: praktische Beispiele für Metaprogrammierung
Vollständiger Leitfaden zu Swift Macros: Erstellung freistehender und attached Makros, AST-Manipulation mit swift-syntax und praktische Beispiele zur Vermeidung von Boilerplate-Code.

StoreKit 2 Interview: Abonnementverwaltung und Beleg-Validierung
Beherrschen Sie iOS-Interview-Fragen zu StoreKit 2, Abonnementverwaltung, Beleg-Validierung und In-App-Kauf-Implementierung mit praktischen Swift-Codebeispielen.