Adding Accessibility to iOS App

  1. Make cell accessible by using the text of titleLabel. Otherwise, the touch area would be defaulted to the label’s size and too small.
    • Set accessibilityLabel in model binding function
class CommonContentCell: UICollectionViewCell {
   override func awakeFromNib() {
        ...
        isAccessibilityElement = true
    }

    override func prepareForReuse() {
        ...
        titleLabel.text = nil
        ...
         accessibilityLabel = titleLabel.text
     }

    func bind(model: Model) {
        ...
        accessibilityLabel = titleLabel.text
    }
}
    • Set in layoutSubviews when the cell is used in many different files and there’s no binding function.
- (void)layoutSubviews {
     ...

     self.accessibilityLabel = [NSString stringWithFormat:@"%@ %@", self.textLabel.text, self.detailTextLabel.text?self.detailTextLabel.text:@""];
 }

2. Set button’s accessibility label if the button’s image name is too long

self.notificationButton.accessibilityLabel = NSLocalizedString(@"notice", "");

3. Make video player view accessible

  1. Add show controls button to make controls accessible by double tapping the screen
- (void)setupAccessibility {
    ...
    
    if (UIAccessibilityIsVoiceOverRunning()) {
        self.showControlsButton = [self addShowControlsButton];
        [self.showControlsButton addTarget:self action:@selector(togglePlayControlView) forControlEvents:UIControlEventTouchUpInside];
        [self.showControlsButton setHidden:YES];
    }
}
- (void)togglePlayControlViewIsShow:(BOOL)isShow withStatusBar:(BOOL)withStatusbar {
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.showControlsButton != nil) {
            [self.showControlsButton setHidden:isShow];
            if (!isShow) {
                UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.showControlsButton);
            }
        }
}

extension PlayerViewController {
    @objc func addShowControlsButton() -> UIButton {
        let button = UIButton(frame: .zero)
        button.accessibilityLabel = "show controls"
        
        button.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(button)
        NSLayoutConstraint.activate([
            button.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            button.leadingAnchor.constraint(equalTo: view.leadingAnchor)
        ])
        return button
    }
}

2. Use Apple’s default subtitles. You cannot make your custom subtitles accessible; even if you create accessibility notifications to announce subtitles, the sound of the accessibility notifications would stop when the video makes sound.

- (void)setShowingSubtitles:(NSMutableArray *)showingSubtitles {
    if (self.player == nil) {
        _showingSubtitles = showingSubtitles;
        return;
    }
    
    [self selectAVAssetLegibleMediaWithPlayer:self.player languageCode:self.subtitlesManager.currentSubtitleTracksStr completion:^(BOOL shows) {
        if (!shows) {
            _showingSubtitles = showingSubtitles;
            [self.player disableSubtitles];
        }
    }];
}

- (void)toggleSubtitleWithToast: {
    if (_hiddenSubtitles) {
        [UIView animateWithDuration:0.25 animations:^{
            ...
            [self selectAVAssetLegibleMediaWithPlayer:self.player languageCode:self.subtitlesManager.currentSubtitleTracksStr completion:nil];
        } completion:^(BOOL finished) { }];
    } else {
        ...
        [UIView animateWithDuration:0.25 animations:^{
            ...
            [self selectAVAssetLegibleMediaWithPlayer:self.player languageCode:nil completion:nil];
        } completion:^(BOOL finished) { }];
    }
    _hiddenSubtitles = !_hiddenSubtitles;
}

    
extension PlayerViewController {
    @objc func selectAVAssetLegibleMedia(player: CLPlayer, languageCode: String?, completion: ((Bool) -> Void)?) {
        guard UIAccessibility.isVoiceOverRunning,
            let asset = player.avPlayer?.currentItem?.asset else {
            completion?(false)
            return
        }
        
        if let group = asset.mediaSelectionGroup(forMediaCharacteristic: .legible) {
            guard let languageCode = languageCode else {
                player.avPlayer?.currentItem?.select(nil, in: group)
                completion?(false)
                return
            }
            
            for option in group.options {
                if let title = option.value(forKey: "title") as? String {
                    if title.lowercased().hasPrefix(languageCode) {
                        player.avPlayer?.currentItem?.select(option, in: group)
                        completion?(true)
                        return
                    }
                }
            }
        }
        completion?(false)
    }
}

4. Set accessibility labels of UILabels which have only text attachment. I tried other ways using attributes of NSAttributedString, but the attempts did no good. Setting IPANotification doesn’t work if you want it to have text of numbers. And IPANotification cannot utilize internationalization.

5. Make custom UIControl accessible

@implementation RatingControl

- (id)init {
    [self setupAccessibility];

    ....    
}

- (void)setRating:(CGFloat)rating
{
    ...
     [self updateAccessiblityValue];
 }
@end

extension RatingControl {
     func updateAccessiblityValue() {
         accessibilityValue = "\(rating)"
     }

     func setupAccessibility() {
         isAccessibilityElement = true
         accessibilityTraits = .adjustable

         accessibilityLabel = "contents.name.star_rating".localized
         updateAccessiblityValue()
     }

     open override func accessibilityIncrement() {
         super.accessibilityIncrement()
         guard rating  0 else {
             return
         }

         rating -= RatingControl.isHalfRatingAvailable() ? 0.5 : 1
     }
 }