LoginSignup
12
4

More than 1 year has passed since last update.

アクセシビリティに配慮したモバイルアプリの作り方(開発視点)

Last updated at Posted at 2022-12-03

初めに

アクセシビリティは開発で無視されがちですが、やり方がわかれば、アクセシビリティに配慮するのはそこまで難しいことではありません。

この記事ではアクセシビリティに配慮したアプリの作り方を例を交えながら説明します。

アクセシビリティに関して

アクセシビリティの定義

この記事で言うアクセシビリティに配慮するというのは、スクリーンリーダー(TalkBack / VoiceOver)による読み上げと操作に対応することを指します。

正確に言えば、認識しやすい色の使い方や小さすぎない文字サイズなどもアクセシビリティに含まれますが、デザインに関わる部分はここでは取り上げません。

アクセシビリティの基本原則

1. 説明の文字がないかつ意味を持つ要素に対しては説明ラベルを指定する

説明の文字がないですが、その要素自体は意味を持つ場合は説明ラベル

  • Android: contentDescription
  • iOS: label / accessibilityLabel

を指定すべきです。

注意する必要があるのは、要素自体が意味を持たない場合は指定すべきではありません。全ての画像が見えないと仮定し、UI を全部文字にするのを想像するとわかりやすいかもしれません。

また、説明の内容も要素自体の説明ではなく要素の意味の説明です。例:「送信ボタン」ではなく「送信」。
(関連記事: お前らはまだ img タグの alt 属性の付け方を間違っている)

Example

  • UI 中の画像に対しては説明ラベルを指定すべき
ラベル名 送信 コメント数 10
- 1_1 1_2
  • UI 中の画像に対しては説明ラベルを指定すべきではない
状況 隣に説明の文字が既にある 画像自体は意味を持たない
- 1_3 1_4

Code Example

  • Jetpack Compose
.kt
Button(onClick = {}) {
    Icon(painter = Icons.Default.Send, contentDescription = "送信")
}

Button(onClick = {}) {
    Icon(painter = Icons.Default.Send, contentDescription = null)
    Text("送信")
}
  • SwiftUI
.swift
Button(action: {}) {
    Image("ic_send", label: Text("送信"))
}

Button(action: {}) {
    HStack {
        Image(decorative: "ic_send")
        Text("送信")
    }
}

2. 自前で文字を描画する場合はアクセシビリティプロパティとしてテキストを指定する

標準のテキストウィジェットを使う場合は自動でテキストがアクセシビリティプロパティに設定されますが、自前で Canvas などに文字を描画する場合は手動で指定する必要があります。

Code Example

  • Jetpack Compose
.kt
Canvas(
    modifier = Modifier
        .size(100.dp)
        .semantics { text = AnnotatedString("テキスト") }
) {
    drawText("テキスト")
}
  • SwiftUI
.swift
Canvas { context, size in
    context.draw("テキスト")
}
.frame(width: 100, height: 100)
.accessibilityLabel(Text("テキスト"))

3. 操作できるコントロールはその状態とできる操作も指定する

トグル(スイッチ)、チェックボックス、タブバー、スライダーなど、状態と操作を持つコントロールに対しては現在の状態と押すときの操作を指定すべきです。

標準のウィジェットを使う場合は自動で指定されますが、カスタムウィジェットを作る場合は手動で指定する必要があります。

  • Jetpack Compose: semantics や selectable、toggleable などの Modifier
  • UIKit: accessibilityValue / accessibilityTraits
  • SwiftUI: accessibility から始まる Modifier

Code Example

  • Jetpack Compose
.kt
Column(
    horizontalAlignment = Alignment.CenterHorizontally,
    modifier = Modifier.selectable(
        selected = index == selectedIndex,
        role = Role.RadioButton,
        onClick = { selectedIndex = index }
    )
) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .size(30.dp)
            .background(color = Color.Blue, shape = CircleShape)
    ) {
        if (selected) {
            Icon(Icons.Default.Check, contentDescription = null, tint = Color.White)
        }
    }
    Text("ブルー")
}

この例を TalkBack で読み上げさせると、
「未選択、ブルー、ラジオボタン、切り替えるにはダブルタップします」
になります。

Modifier.selectable ではなく Modifier.clickable を使うと、
「ブルー、ダブルタップできるメニューです」
になり、選択状態と押すときの操作の説明が失われます。

  • SwiftUI
.swift
VStack {
    ZStack {
        Circle()
            .fill(Color.blue)
            .frame(width: 30, height: 30)
        if index == selectedIndex {
            Image(decorative: "ic_check")
        }
    }
    Text("ブルー")
}
.onTapGesture {
    selectedIndex = index
}
.accessibilityAddTraits(index == selectedIndex ? [.isSelected, .isButton] : .isButton)

4. 場合によってはグルーピングが必要

例えばスイッチと文字ラベル両方ある場合はそれら一つのグループとして、全体をクリックできるリストアイテムも中に個々の要素があっても一つのグループとして扱うべきです。

クリックできるアイテムの中の要素は自動でグルーピングされますが、それ以外のアイテムのグルーピングが必要な場合は手動で設定する必要があります。

Code Example 1

  • Jetpack Compose
.kt
Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
    Icon(Icons.Default.Comment, contentDescription = "コメント数")
    Text("10")
}
  • SwiftUI
.swift
HStack {
    Image("ic_comment", label: Text("コメント数"))
    Text("10")
}
.accessibilityElement(children: .combine)

グルーピングすることで、スクリーンリーダーは個々の要素ではなく全体にフォーカスして読み上げます。

Before After
7e761850-2bed-1627-bdf4-9880fda17b3c.gif a29d550e-48aa-da88-b20a-49e1ffc53afc.png

ただし、全体として扱っても、個々の要素の読み上げには間があるため、複数のテキストを繋げて読ませたい場合は、子要素のアクセシビリティプロパティをクリアして設定し直す必要があります。

Code Example 2

  • Jetpack Compose
.kt
Row(modifier = Modifier.clearAndSetSemantics {
    text = AnnotatedString("あなたは1番です。")
}) {
    Text("あなたは")
    Text("1", style = MaterialTheme.typography.h4)
    Text("番です。")
}
  • SwiftUI
.swift
HStack {
    Text("あなたは")
    Text("1")
        .font(.title)
    Text("番です。")
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(Text("あなたは1番です。"))

また、原則 3 で話したトグル(スイッチ)など状態と操作を持つコントロールをラベルとしてのテキストと組み合わせる場合もグルーピングが必要です。

Code Example 3

SwitchonCheckedChangenull 渡すことで Switch 自体のアクセシビリティを無効にし、代わりに Modifier.toggleableRow 全体を Switch として認識させる。Modifier.toggleable はクリック検知をしているのでグルーピングも含めています。

  • Jetpack Compose
.kt
Row(
    verticalAlignment = Alignment.CenterVertically,
    modifier = Modifier.toggleable(
        value = checked,
        role = Role.Switch,
        onValueChange = { checked = it }
    )
) {
    Text("通知", modifier = Modifier.weight(1f))
    Switch(checked = checked, onCheckedChange = null)
}

SwiftUI では TextToggle の子要素にすることで自動でグルーピングされるため省略します。

おわりに

UI を作る時に、アクセシビリティを念頭に置き、適切なラベルを指定するだけでもアプリのアクセシビリティを大きく改善できるかもしれません。

イメージが難しければ、ぜひ一度スクリーンリーダーをオンにして遊んでみてください!

12
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4