NSCollectionViewItem and support for accessibility

360 Views Asked by At

I'm using a NSCollectionView to layout custom views horizontally. Each NSCollectionViewItem hosts a custom NSView that acts like a checkbox. These custom child views support accessibility on their own (by overriding the required accessibility methods, such as accessibilityRole, accessibilityLabel and so on).

I cannot, however, seem to be able to promote these custom views to become the primary UI view so that they're accessible directly. When I inspect using Accessibility Inspector, each collection view item appears to be a Group and there seems to be no way to automate pressing of a button, for example, since the custom view isn't visible to it.

I tried implementing NSAccessibilityButton on the collection view item, however I don't know what to return for accessibilityParent() - it seems by default NSCollectionViewItems return the parent collection view section, but that's in-accessible without perhaps subclassing NSCollectionView and its layout.

The following did not help either:

  public override var accessibilityFocusedUIElement: Any? {
    return self.customView
  }

This never gets called. The question is, how does one add support for accessibility when using a NSCollectionView / NSCollectionViewItem? I simply wish to surface one of its child views so it becomes accessible to automator or apps such as keyboard maestro.

1

There are 1 best solutions below

0
On

Not sure if this is a workaround or the only possible solution, but this is how I got it to work:

  1. Create a new Accessibility Proxy class that can forward accessibility related lookups
  2. Set this class as the view of a NSCollectionViewItem in `loadView()
  3. Forward calls to an "accessible" child-view

Here's my proxy class:

import Foundation
import Cocoa

/// A wrapper view that acts as a proxy for re-directing accessibility-related lookups.
/// This can be useful in a `NSCollectionViewItem`, for example, to re-direct requests to a child view you'd like to suface as the main, accessible view.
public class BKProxyAccessibleButton: NSView {
  public var isAccessibilityElementProxy: (()->Bool)? = nil
  public var accessibilityFocusedUIElementProxy: (()->Any?)? = nil
  public var accessibilityPerformPressProxy: (()->Bool)? = nil
  public var accessibilityValueProxy: (()->NSNumber?)? = nil
  public var accessibilityHelpProxy: (()->String?)? = nil
  public var accessibilityLabelProxy: (()->String?)? = nil
}

// MARK:- Accessibility
extension BKProxyAccessibleButton: NSAccessibilityButton {
  open override func accessibilityRole() -> NSAccessibility.Role? {
    return .button
  }
  
  open override func accessibilityHelp() -> String? {
    return accessibilityHelpProxy?()
  }
  
  open override func accessibilityLabel() -> String? {
    return accessibilityLabelProxy?()
  }

  open override func accessibilityTitle() -> String? {
    return accessibilityLabelProxy?()
  }
  
  open override func isAccessibilityElement() -> Bool {
    return isAccessibilityElementProxy?() ?? false
  }
  
  open override var accessibilityFocusedUIElement: Any? {
    return accessibilityFocusedUIElementProxy?()
  }
  
  open override func accessibilityPerformPress() -> Bool {
    return accessibilityPerformPressProxy?() ?? false
  }
  
  @objc public override func accessibilityValue() -> Any? {
    return accessibilityValueProxy?()
  }
}