2026年のiOSアクセシビリティ面接質問: VoiceOverとDynamic Type
iOS面接に向け、VoiceOver、Dynamic Type、セマンティックtraits、監査などアクセシビリティの重要質問を解説します。

アクセシビリティはiOS開発で欠かせないスキルになりました。採用担当者はVoiceOver、Dynamic Type、アクセシビリティAPIの習熟度を体系的に評価しています。本記事では技術面接で成功するための主要な概念をまとめます。
世界には10億人以上の障がいを持つ人がいます。Appleは厳格なアクセシビリティ基準を課しており、多くの企業はアクセシブルでないアプリの公開を拒みます。このスキルはシニア候補者を他から差別化します。
iOSアクセシビリティの基礎
1. iOSの主なアクセシビリティツールは?
iOSはさまざまな障がいに向けた包括的なアクセシビリティツール群を提供します。VoiceOverは視覚に障がいを持つユーザーへ画面読み上げを行います。Dynamic Typeは文字サイズを調整します。Switch Controlは外部スイッチでの操作を可能にします。Voice Controlは完全な音声操作を提供します。
// 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といったプロパティを公開します。スクリーンリーダーはユーザーがジェスチャで操作する間、これらの情報を音声で合成します。
// 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は変化しうる現在の状態を表現します。スライダーやスイッチのような動的なコントロールではこの区別が重要です。
// 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がないボタンはボタンとしてアナウンスされません。
// 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はユーザーがシステムの文字サイズを調整できる機能です。意味的なテキストスタイルと柔軟な制約を使い、画面が自動的に適応するよう実装します。
// 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倍ほどに拡大できます。インターフェースは別のレイアウトで対応すべきです: 横並びを縦並びに変更し、装飾要素を非表示にし、余白を調整します。
// 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)から始まります。これらのケースを最初から見越して設計することが必要です。
iOSの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
7. 画像をアクセシブルにする方法は?
画像は役割により扱いが異なります。情報を伝える画像は説明が必要、装飾画像は無視するべき、操作可能な画像にはアクションのlabelが必要です。
// 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回のアナウンスで完全な文脈を提供できます。
// 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ユーザーは縦方向のスワイプで利用します。
// 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の変化(モーダルの表示、コンテンツの読み込み、画面遷移)後、フォーカスを制御することは重要です。フォーカスはユーザーを必要な情報へ導くべきです。
// 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へ伝えます。
iOSの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
調整可能な要素と複雑なコントロール
11. カスタムスライダーをアクセシブルにするには?
カスタムスライダーは.adjustableのtraitを実装し、増減のジェスチャに応答します。VoiceOverは縦スワイプで値を変更します。
// 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と文脈に応じたアナウンスを組み合わせます。
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を関連付け、エラーを明確に示し、フィールド間をスムーズに移動できるようにします。
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で実体験を検証し、ユニットテストでアクセシビリティのプロパティを確認します。
// 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のない操作要素などです。
// 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を有効にした定期的な動作確認は自動ツールでは見つけられない問題の発見に役立ちます。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
タグ
共有
関連記事

Swift における Combine vs async/await:段階的な移行パターン
Swift で Combine から async/await への移行を進めるための完全ガイド:段階的な戦略、ブリッジングパターン、iOS コードベースにおけるパラダイムの共存。

Swift Macros:メタプログラミングの実践例
Swift Macros の完全ガイドです。freestanding と attached マクロの作成、swift-syntax を用いた AST 操作、ボイラープレートを排除する実践的なコード例をまとめています。

StoreKit 2面接対策:サブスクリプション管理とレシート検証
StoreKit 2、サブスクリプション管理、レシート検証、アプリ内課金実装に関するiOS面接の質問を、実用的なSwiftコード例とともにマスターしましょう。