# 2026年のiOSアクセシビリティ面接質問: VoiceOverとDynamic Type > iOS面接に向け、VoiceOver、Dynamic Type、セマンティックtraits、監査などアクセシビリティの重要質問を解説します。 - Published: 2026-03-12 - Updated: 2026-05-01 - Author: SharpSkill - Tags: ios, accessibility, voiceover, dynamic-type, swift - Reading time: 14 min --- アクセシビリティはiOS開発で欠かせないスキルになりました。採用担当者はVoiceOver、Dynamic Type、アクセシビリティAPIの習熟度を体系的に評価しています。本記事では技術面接で成功するための主要な概念をまとめます。 > **なぜアクセシビリティが面接で重要か** > > 世界には10億人以上の障がいを持つ人がいます。Appleは厳格なアクセシビリティ基準を課しており、多くの企業はアクセシブルでないアプリの公開を拒みます。このスキルはシニア候補者を他から差別化します。 ## iOSアクセシビリティの基礎 ### 1. iOSの主なアクセシビリティツールは? iOSはさまざまな障がいに向けた包括的なアクセシビリティツール群を提供します。VoiceOverは視覚に障がいを持つユーザーへ画面読み上げを行います。Dynamic Typeは文字サイズを調整します。Switch Controlは外部スイッチでの操作を可能にします。Voice Controlは完全な音声操作を提供します。 ```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() } ``` これらのAPIにより、ユーザー設定や有効な支援技術に応じて画面を動的に最適化できます。 ### 2. VoiceOverは技術的にどう動くのか? VoiceOverはインターフェースを巡回してアクセシブルな要素を識別します。各要素はlabel、value、traits、actionsといったプロパティを公開します。スクリーンリーダーはユーザーがジェスチャで操作する間、これらの情報を音声で合成します。 ```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") } } ``` アクセシビリティの階層は視覚的な階層と異なる場合があり、複雑なレイアウトでも論理的なナビゲーションを可能にします。 ### 3. accessibilityLabelとaccessibilityValueの違いは? `accessibilityLabel`は要素を恒常的に識別し、`accessibilityValue`は変化しうる現在の状態を表現します。スライダーやスイッチのような動的なコントロールではこの区別が重要です。 ```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は常にlabelに続いてvalueを読み上げ、ユーザーは要素自体と現状の両方を把握できます。 ### 4. accessibilityTraitsとその重要性を説明してください traitsはVoiceOverに要素の種類と振る舞いを伝えます。読み上げ方や利用可能なアクションを左右します。`.button`のtraitがないボタンはボタンとしてアナウンスされません。 ```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により支援技術はユーザーへ一貫した予測可能な体験を提供できます。 ## Dynamic Typeと適応的な文字サイズ ### 5. Dynamic Typeを正しく実装するには? Dynamic Typeはユーザーがシステムの文字サイズを調整できる機能です。意味的なテキストスタイルと柔軟な制約を使い、画面が自動的に適応するよう実装します。 ```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) } } ``` `adjustsFontForContentSizeCategory`を有効にしないとユーザーが設定を変更してもlabelは初期サイズのままです。 ### 6. 極端なアクセシビリティサイズへの対応は? アクセシビリティサイズ(AX1からAX5)は文字を3倍ほどに拡大できます。インターフェースは別のレイアウトで対応すべきです: 横並びを縦並びに変更し、装飾要素を非表示にし、余白を調整します。 ```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 } } } ``` アクセシビリティカテゴリは`.accessibilityMedium`(AX1)から始まります。これらのケースを最初から見越して設計することが必要です。 ### 7. 画像をアクセシブルにする方法は? 画像は役割により扱いが異なります。情報を伝える画像は説明が必要、装飾画像は無視するべき、操作可能な画像にはアクションのlabelが必要です。 ```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 { } } } ``` 説明の不適切な画像や非表示にされていない装飾画像はVoiceOverの体験を大きく損ないます。 ## グルーピングと高度なナビゲーション ### 8. VoiceOver向けに要素をグループ化するには? グループ化は複数の視覚要素を1つのアクセシブル要素にまとめます。これによりナビゲーションが速くなり、1回のアナウンスで完全な文脈を提供できます。 ```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 { } } } ``` グループ化しない場合、VoiceOverはTextごとに止まりナビゲーションが大幅に遅くなります。 ### 9. カスタムのアクセシビリティアクションを実装するには? カスタムアクションは視覚的なUIを煩雑にせずアクセシブルな機能を追加します。VoiceOverユーザーは縦方向のスワイプで利用します。 ```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 } } ``` カスタムアクションはVoiceOverで発見しづらいスワイプジェスチャの代わりに有効です。 ### 10. VoiceOverのフォーカスをプログラムで制御するには? UIの変化(モーダルの表示、コンテンツの読み込み、画面遷移)後、フォーカスを制御することは重要です。フォーカスはユーザーを必要な情報へ導くべきです。 ```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" ) } } ``` `.screenChanged`と`.layoutChanged`の通知はUIの重大な変更をVoiceOverへ伝えます。 ## 調整可能な要素と複雑なコントロール ### 11. カスタムスライダーをアクセシブルにするには? カスタムスライダーは`.adjustable`のtraitを実装し、増減のジェスチャに応答します。VoiceOverは縦スワイプで値を変更します。 ```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) } } ``` `.adjustable`がないと、カスタムスライダーは星を一つずつタップさせることになります。 ### 12. アクセシブルなカルーセルの作り方は? カルーセルはアクセシビリティ上の課題があります。非線形のナビゲーション、画面外のコンテンツ、複数の状態などです。`.adjustable`のtraitと文脈に応じたアナウンスを組み合わせます。 ```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) } } ``` この方法であれば、標準のスワイプジェスチャに依存せずにカルーセルを操作できます。 ### 13. フォームのアクセシビリティはどう扱う? アクセシブルなフォームでは各フィールドにlabelを関連付け、エラーを明確に示し、フィールド間をスムーズに移動できるようにします。 ```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" } } ``` アクセシビリティのhintは視覚UIを汚さずにエラーや指示を伝えます。 ## アクセシビリティ監査とテスト ### 14. iOSアプリのアクセシビリティを監査する方法は? 監査は自動ツールと手動テストの組み合わせです。XcodeのAccessibility Inspectorで要素を分析し、VoiceOverで実体験を検証し、ユニットテストでアクセシビリティのプロパティを確認します。 ```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() } } ``` iOS 17以降の自動監査はlabelの欠落、コントラスト不足、要素の小ささなど一般的な問題を検出します。 ### 15. もっとも多いアクセシビリティの誤りは? 頻発する誤りはlabelの欠落、未記述の画像、Dynamic Type非対応、適切なtraitのない操作要素などです。 ```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) ``` これらの誤りはAccessibility Inspectorで検出でき、わずか数行で修正可能です。 ## まとめ iOSのアクセシビリティは、視覚障がいのあるユーザー向けのVoiceOver、テキスト適応のためのDynamic Type、一貫したナビゲーションのための意味的traitsという3つの柱で支えられます。これらを習得することは、企業が積極的に求める専門性の証となります。 ### チェックリスト - VoiceOverに適切なlabel、hint、traitsを設定する - 意味的なテキストスタイルでDynamic Typeを実装する - 極端なアクセシビリティサイズに合わせたレイアウトを用意する - 要素をグループ化して効率的なナビゲーションを実現する - UI変更後はフォーカスをプログラムで制御する - カスタムコントロールを調整可能にする - Accessibility Inspectorと自動テストで定期的に監査する - 頻発する誤りを避ける: labelの欠落、未記述の画像、コントラスト不足 ### 補足リソース Appleの公式ドキュメント"Human Interface Guidelines - Accessibility"は引き続き重要な参考資料です。VoiceOverを有効にした定期的な動作確認は自動ツールでは見つけられない問題の発見に役立ちます。 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/ios/ios-accessibility-interview-voiceover-dynamic-type