WWDC Quick Look 💓 By SwiftGGTeam
Create accessible experiences for watchOS

Create accessible experiences for watchOS

元の動画を見る

ハイライト

Apple は、watchOS 8 で大きなフォント、VoiceOver、および AssistiveTouch のサポートを拡張しました。開発者は、SwiftUI アクセシビリティ修飾子を使用して、Apple Watch アプリをビジュアルおよびモーション アクセシビリティ機能に適応させることができます。

主な内容

Apple Watchの画面は小さいです。多くの watchOS アプリは、タイトル、アイコン、ステータス、ボタンをリスト セルに詰め込みます。デフォルトのフォント サイズは正常に見えますが、ユーザーがテキスト サイズを大きくすると、タスクの説明が切り詰められ、ボタンが押し寄せられます。

この講演では、植物管理アプリを使用して問題を説明します。メインインターフェイスには、植物の名前、水やり時間、施肥時間、照明の状態、タスクを記録するためのボタンが表示されます。このインターフェイスは情報密度が高いため、watchOS のアクセシビリティに関する一般的な問題が露呈します。

最初の問題はダイナミックタイプです。固定フォント サイズはシステムのテキスト サイズによって変わりません。テキストが 1 行のみ許可されている場合、大きなフォント サイズは直接切り捨てられます。 Apple のアドバイスは具体的です。システムのテキスト スタイルを使用し、テキストの折り返しを許可し、大きなフォント サイズでは垂直レイアウトに切り替えることです。

2つ目はVoiceOver(画面読み上げ)の問題です。複雑なセルには、複数のテキスト、画像、ボタンが含まれる場合があります。 VoiceOver は「ドロップ、イメージ」などのアイコン名を 1 つずつ読み上げます。ユーザーは次のプラントに行く前に、多くの無意味な要素を聞かなければなりません。解決策は、要素を組み合わせて、タスクとボタンにシナリオ指向のラベルを提供することです。

3 番目の問題はカスタム コントロールです。音声中の散水頻度カウンターは、マイナス記号、数字、プラス記号で構成されます。視覚的なユーザーはそれを全体として理解しますが、VoiceOver ユーザーはそれを 3 つの個別の要素として聞きます。 SwiftUI が提供するaccessibilityElement()そしてaccessibilityAdjustableAction、調整可能な要素に変えることができます。

最後に、watchOS 8 には AssistiveTouch が搭載されています。ユーザーは画面に触れずに、つまむ、こぶし、または手首の動きでウォッチを操作できます。開発者は、どの要素にフォーカスできるか、カーソル ボックスが十分な大きさかどうか、カスタム アクションをアクション メニューに表示できるかどうかを確認する必要があります。

##詳細

ダイナミック タイプ: 固定フォント サイズの代わりにテキスト スタイルを使用します。

(04:48) スピーチではまず、植物の名前が大きくならないという問題が取り上げられます。その理由は、タイトルが固定フォント サイズを使用しているためです。 SwiftUI は、ユーザーのテキスト サイズ設定に基づいて自動的に拡大縮小するシステム テキスト スタイルを watchOS に提​​供します。

struct PlantView: View {
    @Binding var plant: Plant

    var body: some View {
        VStack(alignment: .leading) {
            Text(plant.name)
                .font(.title3)
            HStack() {
                PlantImage(plant: plant)
                PlantTaskList(plant: $plant)
            }
            PlantTaskButtons(plant: $plant)
        }
    }
}

キーポイント:

  • @Binding var plant: Plantビューで外部プラント データを直接読み書きできるようにします。 -VStack(alignment: .leading)タイトル、コンテンツ、ボタンを縦横に左揃えに配置します。 -Text(plant.name)植物名を表示します。 -.font(.title3)システム テキスト スタイルを使用すると、テキストはシステム テキスト サイズの変更に従います。 -PlantImagePlantTaskListそしてPlantTaskButtonsそれぞれ、画像、タスク リスト、アクション ボタンを担当します。

(05:00) タイトルを拡大縮小できるようになった後、タスクのテキストも大きなフォント サイズで切り詰められます。スピーチで示された解決策は、行数の制限を緩和し、テキストの折り返しのためのスペースを与えることです。

struct PlantTaskLabel: View {
    let task: PlantTask
    @Binding var plant: Plant

    var body: some View {
        HStack {
            Image(systemName: task.systemImageName)
                .imageScale(.small)
            Text(plant.stringForTask(task: task))
        }
        .lineLimit(3)
        .font(.caption2)
    }
}

キーポイント:

  • let task: PlantTask水やりや肥料など、現在のタスクの種類を保存します。 -Image(systemName: task.systemImageName)タスクに対応する SF シンボル アイコンを使用します。 -.imageScale(.small)アイコンがテキストスペースを占有しないようにするには、アイコンを縮小します。 -Text(plant.stringForTask(task: task))タスクのステータステキストを表示します。 -.lineLimit(3)大きなフォント サイズでの切り捨てを減らすために、最大 3 行を許可します。 -.font(.caption2)ダイナミック タイプは、システム テキスト スタイルを使用して引き続きサポートされます。

大きなフォントサイズのレイアウト: サイズカテゴリーに応じて構造を切り替える

(05:48) 改行を許可した後でも、インターフェイスが混雑している可能性があります。音声選択の読み取りsizeCategory、フォント サイズがしきい値に達すると、垂直レイアウトに切り替わります。

struct PlantContainerView: View {
    @Environment(\.sizeCategory) var sizeCategory
    @Binding var plant: Plant

    var body: some View {
        if sizeCategory < .extraExtraLarge {
            PlantViewHorizontal(plant: $plant)
        } else {
            PlantViewVertical(plant: $plant)
        }
    }
}

キーポイント:

  • @Environment(\.sizeCategory)システムのテキスト サイズ設定を読み取ります。 -@Binding var plant: Plant同じプラント データを異なるレイアウトに渡します。 -if sizeCategory < .extraExtraLarge現在のフォント サイズが依然として水平レイアウトに適しているかどうかを判断します。 -PlantViewHorizontal(plant: $plant)デフォルトおよび小さいフォント サイズをコンパクトなレイアウトで表示します。 -PlantViewVertical(plant: $plant)フォント サイズを大きくすると、ラベルとボタンの垂直方向のスペースが広がります。

VoiceOver: アクセス可能な要素の数を減らす

(08:56) VoiceOver をオンにすると、元のインターフェイスが植物名、画像名、タスク テキスト、アイコン名、ボタンを読み上げます。ユーザーが通過するには要素が多すぎます。演説で指摘されたのは、NavigationLink子要素のアクセシビリティ情報を自動的にマージして、植物セルをアクセシブルな要素にすることができます。

struct PlantCellView: View {
    @EnvironmentObject var plantData: PlantData
    var plant: Plant

    var plantIndex: Int {
        plantData.plants.firstIndex(where: { $0.id == plant.id })!
    }

    var body: some View {
        NavigationLink(destination: PlantEditView(plant: plant).environmentObject(plantData)) {
            PlantContainerView(plant: $plantData.plants[plantIndex])
                .padding()
        }
    }
}

キーポイント:

  • @EnvironmentObject var plantData: PlantData環境から植物のリストを読み取ります。 -plantIndex合格id配列内の現在のプラントの位置を見つけます。 -NavigationLinkセル全体を編集インターフェイスからアクセスできるようにします。 -PlantEditView(plant: plant).environmentObject(plantData)編集ページを共有データに接続します。 -PlantContainerView(plant: $plantData.plants[plantIndex])配列内の現在のプラントをバインドします。 -.padding()セルコンテンツの空白スペースを拡張すると、タッチエリアもより快適になります。

VoiceOver タグ: アイコン名をタスク セマンティクスに変更します

(09:38) SF シンボルはデフォルトの読みを提供します。たとえば、水滴アイコンは「ドロップ」と読み取られます。植物の世話のシナリオでは、ユーザーは「5 日後に水やり」を聞く必要があります。タスクテキストに音声が追加されましたaccessibilityLabel

struct PlantTaskLabel: View {
    let task: PlantTask
    @Binding var plant: Plant

    var body: some View {
        HStack {
            Image(systemName: task.systemImageName)
                .imageScale(.small)
            Text(plant.stringForTask(task: task))
                .accessibilityLabel(plant.accessibilityStringForTask(task: task))
        }
        .lineLimit(3)
        .font(.caption2)
    }
}

キーポイント:

  • Text(plant.stringForTask(task: task))短いテキストを画面に表示したままにしておきます。 -.accessibilityLabel(...)VoiceOver が読み上げる専用のテキストを提供します。 -plant.accessibilityStringForTask(task: task)水やり、肥料、照明などのタスク タイプに基づいて完全なセマンティクスを生成します。
  • ビジュアル テキストと音声テキストが分離された後も、短いコピーを小さな画面に表示でき、VoiceOver で完全なコンテキストを読み上げることができます。

(10:03) ボタンにはセマンティック ラベルも必要です。読み取り専用の「ドロップフィルボタン」には動作上の意味はありません。この講義では、ボタンのラベルが「丸太に水やり」などのアクションの説明に変更されます。

struct PlantButton: View {
    let task: PlantTask
    let action: () -> Void
    @State private var isTapped: Bool = false

    var body: some View {
        Button(action: {
            self.isTapped.toggle()
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.isTapped.toggle()
            }
            action()
        }) {
            Image(systemName: task.systemImageFillName)
                .foregroundColor(task.color)
                .scaleEffect(isTapped ? 1.5 : 1)
                .animation(nil, value: 0)
                .rotationEffect(.degrees(isTapped ? 360 : 0))
                .animation(.spring(), value: 0)
                .imageScale(.large)
        }
        .buttonStyle(BorderedButtonStyle())
        .accessibilityLabel("Log \(task.name)")
    }
}

キーポイント:

  • let action: () -> Voidボタンがトリガーされた後にビジネス操作を受け取ります。 -@State private var isTapped一時的なクリック アニメーションの状態を保存します。 -Button(action:)まずアニメーションの状態を切り替えてから、受信したアニメーションを実行します。action()
  • DispatchQueue.main.asyncAfter0.5 秒後にアニメーション状態を再開します。 -Image(systemName: task.systemImageFillName)タスクに対応する塗りつぶしアイコンを表示します。 -.scaleEffectそして.rotationEffectクリックフィードバックを担当します。 -.buttonStyle(BorderedButtonStyle())境界ボタンのスタイルを使用します。 -.accessibilityLabel("Log \(task.name)")これがタスクを記録するボタンであることを VoiceOver に伝えます。

カスタム コントロール: 調整可能なアクションを使用して加算と減算を表現します。

(11:07) 水やり頻度カウンターは 3 つの視覚要素で構成されます。 VoiceOver ユーザーには、「削除、ボタン。8 つ。追加、ボタン」と聞こえます。 Speech はそれを要素にカプセル化し、上へのスワイプと下へのスワイプを使用して増分と減分を実行します。

struct PlantTaskFrequency: View {
    let task: PlantTask
    @Binding var plant: Plant
    let increment: () -> Void
    let decrement: () -> Void

    var value: Int {
        switch task {
        case .water:
            return plant.wateringFrequency
        case .fertilize:
            return plant.fertilizingFrequency
        default:
            return 0
        }
    }

    var body: some View {
        Section(header: Text("\(task.name) frequency in days"), content: {
            CustomCounter(value: value, increment: increment, decrement: decrement)
                .accessibilityElement()
                .accessibilityAdjustableAction { direction in
                    switch direction {
                    case .increment:
                        increment()
                    case .decrement:
                        decrement()
                    default:
                        break
                    }
                }
                .accessibilityLabel("\(task.name) frequency")
                .accessibilityValue("\(value) days")
        })
    }
}

キーポイント:

  • incrementそしてdecrement外部からの増減操作です。 -valueタスクの種類に基づいて、水やりまたは施肥の頻度を読み取ります。 -CustomCounter(value:increment:decrement:)引き続きビジュアルインターフェイスを担当します。 -.accessibilityElement()子要素を無視して、新しい高レベルのアクセシビリティ要素を作成します。 -.accessibilityAdjustableActionVoiceOver の増減ジェスチャをビジネス メソッドにマッピングします。 -.accessibilityLabel("\(task.name) frequency")コントロール名を指定します。 -.accessibilityValue("\(value) days")現在の値を提供します。値が変更されると再度読み取られます。

AssistiveTouch: 静的コンテンツにフォーカスできるようにします

(19:50) AssistiveTouch は対話可能な要素のみに焦点を当てます。ボタンやスイッチなど、NavigationLinkデフォルトでフォーカス可能。普通TextまたはLabelデフォルトではフォーカスされていません。スピーチ自体の飲料情報領域をクリックして詳細を開くことはできますが、内部の静的テキストは自動的に AssistiveTouch フォーカスになりません。

struct FreeDrinkView: View {
    @State var didCancel = false
    @State var didAccept = false
    @State var showDetail = false

    var body: some View {
        VStack(spacing:10) {
            FreeDrinkTitleView()

            FreeDrinkInfoView()
                .accessibilityRespondsToUserInteraction(true)

            HStack {
                CancelButton(buttonTapped: $didCancel)
                AcceptButton(buttonTapped: $didAccept)
            }
        }
        .onTapGesture {
            showDetail.toggle()
        }
        .sheet(isPresented: $showDetail, onDismiss: dismiss) {
            DrinkDetailModalView()
        }
    }
}

キーポイント:

  • @State var showDetail詳細ポップアップ ウィンドウを表示するかどうかを制御します。 -FreeDrinkInfoView()ドリンク情報を表示します。 -.accessibilityRespondsToUserInteraction(true)ユーザー操作に応答するために、この静的領域を宣言します。 -HStack中の [キャンセル] ボタンと [同意する] ボタンは、AssistiveTouch によってすでにフォーカスされています。 -.onTapGesture全体を任せてくださいVStackクリックすると詳細が開きます。 -.sheet(isPresented:onDismiss:)詳細インターフェイスを表示します。

AssistiveTouch カーソル ボックス: contentShape をクリック可能な領域を決定します

(21:12) AssistiveTouch カーソル ボックスは、要素のクリック可能な領域と一致しています。小さな省略記号ボタンには小さなカーソル ボックスが表示され、コンテンツが切り取られる可能性があります。スピーチ用contentShapeクリック可能な領域を拡大します。

struct DrinkView: View {
    var currentDrink:DrinkInfo

    var body: some View {
        HStack(alignment: .firstTextBaseline) {
            DrinkInfoView(drink:currentDrink)

            Spacer()

            NavigationLink(destination: EditView()) {
                Image(systemName: "ellipsis")
                    .symbolVariant(.circle)
            }
            .contentShape(Circle().scale(1.5))
        }
    }
}

キーポイント:

  • HStack(alignment: .firstTextBaseline)ドリンク情報と編集エントリを同じ行に配置します。 -Spacer()右側の編集ボタンを押します。 -NavigationLink(destination: EditView())編集インターフェイスに入る操作を提供します。 -Image(systemName: "ellipsis")省略記号アイコンを表示します。 -.symbolVariant(.circle)ラウンド型を使用します。 -.contentShape(Circle().scale(1.5))クリック可能領域を 1.5 倍に拡大した円に変更すると、AssistiveTouch のカーソル ボックスも大きくなります。

AssistiveTouch アクション メニュー: accessibilityAction を再利用する

(22:48) AssistiveTouch のアクション メニューには、システム アクションと要素のカスタム アクセシビリティ アクションが表示されます。 VoiceOver 用にすでに追加されていますaccessibilityActionAssistiveTouch メニューに自動的に表示されます。

PlantContainerView(plant: plant)
    .padding()
    .accessibilityElement(children: .combine)
    .accessibilityAction {
        // Edit action
    } label: {
        Label("Edit", systemImage: "ellipsis.circle")
    }

キーポイント:

  • PlantContainerView(plant: plant)植物細胞を示しています。 -.padding()独房内に接触できる余地を残しておきます。 -.accessibilityElement(children: .combine)子要素を 1 つのアクセス可能な要素に結合します。 -.accessibilityActionカスタムアクションを追加します。 -Label("Edit", systemImage: "ellipsis.circle")アクション メニューのテキストとアイコンを指定します。
  • AssistiveTouch がこの要素に焦点を当てると、Editアクションメニューの先頭に表示されます。

重要なポイント

  1. 対処法: Apple Watch To-Do アプリに大きなフォント レイアウトを追加します。 価値がある理由: watchOS 8 は、より大きなアクセス可能なテキスト サイズをサポートしており、固定レイアウトによりコンテンツを簡単に切り詰めることができます。 開始方法: ピンを入れる.font(.system(size:))に変更します.font(.headline).font(.caption2)テキストスタイルを待ちます。読む@Environment(\.sizeCategory)、大きなフォント サイズでは縦方向のレイアウトに切り替わります。

  2. 対処法: フィットネス アプリや薬のリマインダー アプリの VoiceOver タグを書き換えます。 実行する価値がある理由: アイコンのデフォルトのラベルは通常、形状を説明するものであり、ユーザーはタスクの意味を聞く必要があります。 開始方法: ステータス テキストとボタンを追加する.accessibilityLabel(...)、「カプセル」などのアイコン名を「午後9時に薬を飲む」などのビジネスコピーライティングに変更します。

  3. 対処方法: カスタム ステッパーを VoiceOver 調整可能なコントロールに変更します。 価値がある理由: コントロールにプラス記号、数値、マイナス記号を使用すると、VoiceOver のナビゲーションが遅くなる可能性があります。 開始方法: カスタム コントロールで使用します.accessibilityElement()、次に使用します.accessibilityAdjustableAction対処する.incrementそして.decrement

  4. 対処方法: watchOS アプリの AssistiveTouch フォーカス順序を確認します。 実行する価値がある理由: AssistiveTouch ユーザーはアクションを完了するためにフォーカスの移動とアクション メニューに依存しているため、小さなボタンや隠れたインタラクションが邪魔になることがあります。 開始方法: AssistiveTouch を開いてページごとにテストします。クリックに反応する静的領域を追加します.accessibilityRespondsToUserInteraction(true)、小さなボタンに使用します.contentShape(...)クリック可能な領域を拡大します。

  5. やるべきこと: リスト セルの二次操作を AssistiveTouch アクション メニューに追加します。 価値がある理由: Apple Watch の画面は小さいため、セルに複数のボタンを配置するとナビゲーションの負担が増加します。 開始方法: セルのコンテンツを使用します.accessibilityElement(children: .combine)マージして再利用する.accessibilityAction編集、完了、削除などのアクションを追加します。

関連セッション

コメント

GitHub Issues · utterances