# Pytania rekrutacyjne o dostępność iOS w 2026: VoiceOver i Dynamic Type > Przygotuj się do rozmów iOS z kluczowymi pytaniami o dostępność: VoiceOver, Dynamic Type, semantyczne traits i audyty. - Published: 2026-03-12 - Updated: 2026-05-01 - Author: SharpSkill - Tags: ios, accessibility, voiceover, dynamic-type, swift - Reading time: 14 min --- Dostępność stała się krytyczną kompetencją w rozwoju iOS. Rekruterzy systematycznie sprawdzają znajomość VoiceOver, Dynamic Type oraz API dostępności. Poniższe pytania obejmują podstawowe pojęcia niezbędne do powodzenia w rozmowie technicznej. > **Dlaczego dostępność liczy się w rekrutacji** > > Ponad miliard ludzi żyje z niepełnosprawnością. Apple egzekwuje rygorystyczne standardy dostępności, a wiele firm odmawia publikowania niedostępnych aplikacji. Ta umiejętność wyróżnia kandydatów na stanowiska seniorskie spośród reszty. ## Podstawy dostępności w iOS ### 1. Jakie są główne narzędzia dostępności w iOS? iOS oferuje kompletny ekosystem narzędzi dostępności kierowanych do różnych grup niepełnosprawności. VoiceOver umożliwia odczyt ekranu osobom niewidomym i słabowidzącym. Dynamic Type dostosowuje rozmiar tekstu. Switch Control pozwala na sterowanie zewnętrznymi przełącznikami. Voice Control oferuje pełne sterowanie głosem. ```swift // AccessibilityTools.swift // 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() } ``` Te API umożliwiają dynamiczne dostosowywanie interfejsu do preferencji użytkownika i aktywnych technologii asystujących. ### 2. Jak technicznie działa VoiceOver? VoiceOver przemierza interfejs, identyfikując dostępne elementy. Każdy element udostępnia właściwości takie jak label, value, traits oraz akcje. Czytnik ekranu wokalnie syntezuje te informacje, gdy użytkownik nawiguje gestami. ```swift // VoiceOverBasics.swift // 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") } } ``` Hierarchia dostępności może różnić się od hierarchii wizualnej, co umożliwia logiczną nawigację nawet w złożonych układach. ### 3. Jaka jest różnica między accessibilityLabel a accessibilityValue? `accessibilityLabel` trwale identyfikuje element, natomiast `accessibilityValue` reprezentuje jego aktualny, zmienny stan. To rozróżnienie jest kluczowe dla dynamicznych kontrolek, takich jak suwaki czy przełączniki. ```swift // LabelVsValue.swift // 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 zawsze ogłasza najpierw label, a potem value, dzięki czemu użytkownik wie, czym jest element i jaki jest jego aktualny stan. ### 4. Wyjaśnij accessibilityTraits i ich znaczenie Traits informują VoiceOver o typie i zachowaniu elementu. Decydują o tym, jak element jest ogłaszany i jakie akcje są dostępne. Przycisk bez trait `.button` nie zostanie ogłoszony jako przycisk. ```swift // AccessibilityTraits.swift // 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 umożliwiają technologiom asystującym dostarczanie spójnego i przewidywalnego doświadczenia użytkownikom. ## Dynamic Type i adaptacyjne rozmiary tekstu ### 5. Jak poprawnie wdrożyć Dynamic Type? Dynamic Type pozwala użytkownikowi regulować systemowy rozmiar tekstu. Poprawne wdrożenie wykorzystuje semantyczne style tekstu i elastyczne ograniczenia, dzięki czemu interfejs dostosowuje się automatycznie. ```swift // DynamicType.swift // 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) } } ``` Bez `adjustsFontForContentSizeCategory` etykiety zachowują swój początkowy rozmiar nawet wtedy, gdy użytkownik zmienia preferencje. ### 6. Jak obsłużyć ekstremalne rozmiary dostępności? Rozmiary dostępności (od AX1 do AX5) mogą trzykrotnie powiększyć tekst. Interfejs musi się dostosować przy pomocy alternatywnych układów: ułożenie pionowe zamiast poziomego, ukrycie elementów dekoracyjnych i regulacja odstępów. ```swift // LargeContentSize.swift // 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 } } } ``` Kategorie dostępności zaczynają się od `.accessibilityMedium` (AX1). Projekt musi przewidywać te przypadki od samego początku. ### 7. Jak uczynić obrazy dostępnymi? Obrazy wymagają różnego traktowania w zależności od ich roli: informacyjne potrzebują opisu, dekoracyjne należy ignorować, a interaktywne wymagają etykiet akcji. ```swift // AccessibleImages.swift // 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 { } } } ``` Źle opisany obraz lub niezamaskowany obraz dekoracyjny znacząco pogarsza doświadczenie z VoiceOver. ## Grupowanie i zaawansowana nawigacja ### 8. Jak grupować elementy dla VoiceOver? Grupowanie łączy kilka elementów wizualnych w jeden element dostępny. Przyspiesza to nawigację i zapewnia pełny kontekst w pojedynczym ogłoszeniu. ```swift // AccessibilityGrouping.swift // 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 { } } } ``` Bez grupowania VoiceOver zatrzymuje się na każdym Texcie z osobna, znacznie spowalniając nawigację. ### 9. Jak wdrożyć niestandardowe akcje dostępności? Niestandardowe akcje dodają funkcjonalność dostępną bez zaśmiecania interfejsu wizualnego. Użytkownicy VoiceOver wywołują je pionowym swipem. ```swift // CustomAccessibilityActions.swift // 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 } } ``` Niestandardowe akcje korzystnie zastępują gesty swipe, które przy VoiceOver są trudne do odkrycia. ### 10. Jak programowo zarządzać fokusem VoiceOver? Kontrola fokusu jest kluczowa po zmianach interfejsu: pojawieniu się modala, ładowaniu treści czy nawigacji. Fokus powinien prowadzić użytkownika do istotnych informacji. ```swift // AccessibilityFocus.swift // 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" ) } } ``` Powiadomienia `.screenChanged` i `.layoutChanged` informują VoiceOver o istotnych zmianach w interfejsie. ## Elementy regulowane i złożone kontrolki ### 11. Jak uczynić niestandardowy suwak dostępnym? Niestandardowy suwak musi zaimplementować trait `.adjustable` i reagować na gesty inkrementacji/dekrementacji. VoiceOver używa pionowego swipe'a do zmiany wartości. ```swift // AccessibleSlider.swift // 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) } } ``` Bez traita `.adjustable` niestandardowy suwak wymagałby od użytkownika dotykania każdej gwiazdki osobno. ### 12. Jak stworzyć dostępną karuzelę? Karuzele stwarzają wyzwania w zakresie dostępności: nieliniowa nawigacja, treść poza ekranem i wiele stanów. Rozwiązanie łączy trait `.adjustable` z kontekstowymi ogłoszeniami. ```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("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) } } ``` To podejście pozwala nawigować po karuzeli bez polegania na standardowych gestach swipe. ### 13. Jak obsłużyć dostępność formularzy? Dostępne formularze wiążą każde pole z jego etykietą, jasno wskazują błędy i umożliwiają płynną nawigację między polami. ```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("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" } } ``` Wskazówki dostępności komunikują błędy i instrukcje bez przeładowywania interfejsu wizualnego. ## Audyty i testy dostępności ### 14. Jak audytować dostępność aplikacji iOS? Audyt łączy narzędzia automatyczne i testy manualne. Accessibility Inspector w Xcode analizuje elementy, VoiceOver weryfikuje rzeczywiste doświadczenie, a testy jednostkowe sprawdzają właściwości dostępności. ```swift // AccessibilityAudit.swift // 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() } } ``` Automatyczny audyt w iOS 17+ wykrywa typowe problemy: brakujące etykiety, niewystarczający kontrast, zbyt małe elementy. ### 15. Jakie są najczęstsze błędy dostępności? Częste błędy obejmują brakujące etykiety, nieopisane obrazy, brak wsparcia Dynamic Type oraz interaktywne elementy bez odpowiednich traits. ```swift // CommonMistakes.swift // 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) ``` Te błędy są wykrywalne za pomocą Accessibility Inspector i naprawialne kilkoma linijkami kodu. ## Podsumowanie Dostępność iOS opiera się na trzech filarach: VoiceOver dla użytkowników niewidomych, Dynamic Type dla adaptacji tekstu i semantyczne traits dla spójnej nawigacji. Opanowanie tych pojęć dowodzi profesjonalnej wiedzy aktywnie poszukiwanej przez pracodawców. ### Lista kontrolna - Skonfigurować VoiceOver z odpowiednimi etykietami, hintami i traits - Wdrożyć Dynamic Type ze stylami tekstowymi - Dostosować układy do ekstremalnych rozmiarów dostępności - Grupować elementy dla efektywnej nawigacji - Zarządzać fokusem programowo po zmianach interfejsu - Uczynić niestandardowe kontrolki regulowanymi - Regularnie audytować przy pomocy Accessibility Inspector i automatycznych testów - Unikać typowych błędów: brakujących etykiet, nieopisanych obrazów, niewystarczającego kontrastu ### Dodatkowe źródła Oficjalna dokumentacja Apple "Human Interface Guidelines - Accessibility" pozostaje punktem odniesienia. Regularne testowanie z włączonym VoiceOver pomaga wykrywać problemy, których narzędzia automatyczne nie wychwycą. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/ios/ios-accessibility-interview-voiceover-dynamic-type