Upgrade to Pro — share decks privately, control downloads, hide ads and more …

noteのiOSアプリで実装したアクセシビリティの全て #iosdc #a /a11y_wit...

fromkk
September 18, 2021

noteのiOSアプリで実装したアクセシビリティの全て #iosdc #a /a11y_with_iOS_App_on_note

iOSDC Japan 2021で登壇した資料です。

----CfP----
noteのiOSアプリはここ1年間で大きな変化を遂げました。
元々30%程度だったSwift率が90%を超え、デバイスもiPhoneの縦画面のみの対応でしたがiPadの複数ウィンドウにも対応しました。
このように目に見えるカイゼンを進めてきた私たちですが、あるきっかけで目に見えない課題があることを知りました。
アクセシビリティ機能を駆使して私たちのアプリを利用してくれている方からお問い合わせをいただいたのです。
この方の日頃の利用方法についてインタビューさせていただくことになり、今まで見えていなかった課題に気づくことができました。
そこでアクセシビリティの有識者に力を借りながら、Voice OverやDynamic Typeといったアクセシビリティの助けとなる機能にも対応をはじめました。
・Voice Overを利用するとさわれると思っているUI要素にさわれない
・わかりやすいと思っていたボタンなのにVoice Overを通すと何のボタンなのか理解ができない
このトークではこういった課題を認知して、実際に対応したことをまとめてお話します。

fromkk

September 18, 2021
Tweet

More Decks by fromkk

Other Decks in Programming

Transcript

  1. 1

  2. 2

  3. 3

  4. ΢ΣϒΞΫηγϏϦςΟͷ4ͭͷݪଇ • ஌֮Մೳ • ৘ใٴͼϢʔβΠϯλϑΣʔείϯϙʔωϯτ͸ɺར༻ऀ͕஌֮Ͱ͖Δํ๏Ͱར༻ऀʹఏࣔՄೳͰͳ ͚Ε͹ͳΒͳ͍ɻ • ૢ࡞Մೳ • ϢʔβΠϯλϑΣʔείϯϙʔωϯτٴͼφϏήʔγϣϯ͸ૢ࡞ՄೳͰͳ͚Ε͹ͳΒͳ͍ɻ

    • ཧղՄೳ • ৘ใٴͼϢʔβΠϯλϑΣʔεͷૢ࡞͸ཧղՄೳͰͳ͚Ε͹ͳΒͳ͍ɻ • ݎ࿚ੑ • ίϯςϯπ͸ɺࢧԉٕज़ΛؚΉ༷ʑͳϢʔβΤʔδΣϯτ͕࣮֬ʹղऍͰ͖ΔΑ͏ʹे෼ʹݎ࿚Ͱͳ ͚Ε͹ͳΒͳ͍ɻ 12 https://waic.jp/docs/UNDERSTANDING-WCAG20/intro.html
  5. ίʔυΠϝʔδ lazy var containerView: UIView = { let view =

    UIView(frame: view.bounds) view.isUserInteractionEnabled = true view.addGestureRecognizer( UITapGestureRecognizer(target: self, action: #selector(tap(sender:)))) return view }() @objc private func tap(sender: UITapGestureRecognizer) { showTappedAlert() } 33
  6. ίʔυΠϝʔδ lazy var button: UIButton = { let button =

    UIButton(type: .custom) let configuration = UIImage.SymbolConfiguration( font: UIFont.systemFont(ofSize: 280)) let image = UIImage(systemName: "heart", withConfiguration: configuration) button.setImage(image, for: .normal) return button }() 38
  7. मਖ਼಺༰ 1 lazy var closeButton: UIButton = { let button

    = UIButton(type: .custom) button.setTitle("ด͡Δ", for: .normal) button.addTarget(self, action: #selector(close(sender:)), for: .touchUpInside) return button }() @objc private func close(sender: Any) { dismiss(animated: true) } 44
  8. मਖ਼಺༰ 2 private var cancellable: AnyCancellable? private func subscribeVoiceOver() {

    cancellable?.cancel() cancellable = NotificationCenter.default.publisher( for: UIAccessibility.voiceOverStatusDidChangeNotification ) .sink { [weak self] _ in self?.handleVoiceOverStatus() } } private func handleVoiceOverStatus() { closeButton.isHidden = !UIAccessibility.isVoiceOverRunning } 45
  9. ରԠ಺༰ 56 private func showErrorMessage(_ errorMessage: String) { errorLabel.text =

    errorMessage UIAccessibility.post(notification: .announcement, argument: errorMessage) } + https://developer.apple.com/documentation/uikit/uiaccessibility/notification
  10. मਖ਼಺༰ @objc private func next(sender: Any) { UIAccessibility.post(notification: .layoutChanged, argument:

    messageLabel) } + 60 https://developer.apple.com/documentation/uikit/uiaccessibility/notification
  11. ίʔυΠϝʔδ1 lazy var dateLabel: UILabel = { let label =

    UILabel() label.textColor = .label label.font = UIFont.preferredFont(forTextStyle: .headline) label.textAlignment = .center label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping return label }() 
 
 private func showNow() { dateLabel.text = dateFormatter.string(from: now) } 63
  12. ίʔυΠϝʔδ2 private lazy var dateFormatter: DateFormatter = { guard let

    dateFormat = DateFormatter.dateFormat( fromTemplate: "yyyy/MM/dd HH:mm", options: 0, locale: .current) else { fatalError("failed get date format from template") } var calendar = Calendar(identifier: .gregorian) let timeZone = TimeZone.current let locale = Locale.current calendar.locale = locale var dateFormatter = DateFormatter() dateFormatter.calendar = calendar dateFormatter.timeZone = timeZone dateFormatter.locale = locale dateFormatter.dateFormat = dateFormat return dateFormatter }() 64
  13. मਖ਼಺༰ 66 private func showNow() { dateLabel.text = dateFormatter.string(from: now)

    dateLabel.accessibilityLabel = accessibilityDateFormatter.string(from: now) } private lazy var accessibilityDateFormatter: DateFormatter = { var calendar = Calendar(identifier: .gregorian) let timeZone = TimeZone.current let locale = Locale.current calendar.locale = locale var dateFormatter = DateFormatter() dateFormatter.calendar = calendar dateFormatter.timeZone = timeZone dateFormatter.locale = locale dateFormatter.dateStyle = .long dateFormatter.timeStyle = .medium return dateFormatter }() + +
  14. मਖ਼಺༰ 70 private func setUp() { isAccessibilityElement = true }

    
 
 override var accessibilityLabel: String? { get { titleLabel.text } set {} } override var accessibilityValue: String? { get { toggleSwitch.accessibilityValue } set {} } override var accessibilityTraits: UIAccessibilityTraits { get { toggleSwitch.accessibilityTraits } set {} } override func accessibilityActivate() -> Bool { toggleSwitch.isOn.toggle() return true } SwitchAccessibleViewCell.swift
  15. ϩʔλʔػೳ • “VoiceOver ϩʔλʔΛ࢖ͬͯ VoiceOver ͷಈ࡞ΛมߋͰ͖· ͢ɻVoiceOver ͷԻྔ΍࿩͢଎͞Λมߋͨ͠Γɺը໘্Ͱ߲໨ؒ ΛҠಈͨ͠Γ͢Δ͜ͱ͕Ͱ͖ɺͦͷଞͷૢ࡞΋ՄೳͰ͢ɻ” 


    https://support.apple.com/ja-jp/HT204783 • 1ຊࢦͷ্ԼεϫΠϓͰར༻Մೳ • ը໘্Λ2ຊࢦͰճ͢Α͏ʹಈ͔͢͜ͱͰઃఆΛมߋ • ΧελϜػೳΛ࡞੒͢Δ͜ͱ΋Մೳ 
 https://developer.apple.com/documentation/uikit/ uiaccessibilitycustomrotor 73
  16. ίʔυΠϝʔδ 74 private func makeHeadlineLabel(with title: String) -> UILabel {

    let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .headline) label.textColor = UIColor.label label.text = title label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping return label }
  17. मਖ਼಺༰ 76 private func makeHeadlineLabel(with title: String) -> UILabel {

    let label = UILabel() label.font = UIFont.preferredFont(forTextStyle: .headline) label.textColor = UIColor.label label.text = title label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping label.accessibilityTraits = [.header] return label } +
  18. 78 static var none: UIAccessibilityTraits The accessibility element has no

    traits. static var button: UIAccessibilityTraits The accessibility element behaves like a button. static var link: UIAccessibilityTraits The accessibility element behaves like a link. static var image: UIAccessibilityTraits The accessibility element behaves like an image. static var searchField: UIAccessibilityTraits The accessibility element behaves like a search field. static var keyboardKey: UIAccessibilityTraits The accessibility element behaves like a keyboard key. static var staticText: UIAccessibilityTraits The accessibility element behaves like static text that can't change. static var header: UIAccessibilityTraits The accessibility element is a header that divides content into sections, such as the title of a navigation bar. static var tabBar: UIAccessibilityTraits The accessibility element behaves like a tab bar. static var summaryElement: UIAccessibilityTraits The accessibility element provides summary information when the app starts. static var selected: UIAccessibilityTraits The accessibility element is currently in a selected state. static var notEnabled: UIAccessibilityTraits The accessibility element isn't in an enabled state and doesn't respond to user interaction. static var adjustable: UIAccessibilityTraits The accessibility element allows continuous adjustment through a range of values. static var allowsDirectInteraction: UIAccessibilityTraits The accessibility element allows direct touch interaction for VoiceOver users. static var updatesFrequently: UIAccessibilityTraits The accessibility element frequently updates its label or value. static var causesPageTurn: UIAccessibilityTraits The accessibility element causes an automatic page turn when VoiceOver finishes reading the text within it. static var playsSound: UIAccessibilityTraits The accessibility element plays its own sound when the user activates it. static var startsMediaSession: UIAccessibilityTraits The accessibility element starts a media session when the user activates it. accessibilityTraits https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits
  19. 89