Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessibility (Voice Over) with Sprite Kit

I'm attempting to add support for Voice Over accessibility in a puzzle game which has a fixed board. However, I'm having trouble getting UIAccessibilityElements to show up.

Right now I'm overriding accessibilityElementAtIndex, accessibilityElementCount and indexOfAccessibilityElement in my SKScene.

They are returning an array of accessible elements as such:

func loadAccessibleElements()
{
    self.isAccessibilityElement = false

    let pieces = getAllPieces()

    accessibleElements.removeAll(keepCapacity: false)
    for piece in pieces
    {
        let element = UIAccessibilityElement(accessibilityContainer: self.usableView!)

        element.accessibilityFrame = piece.getAccessibilityFrame()
        element.accessibilityLabel = piece.getText()
        element.accessibilityTraits = UIAccessibilityTraitButton
        accessibleElements.append(element)
    }
}

Where piece is a subclass of SKSpriteNode and getAccessibilityFrame is defined:

func getAccessibilityFrame() -> CGRect
{
    return parentView!.convertRect(frame, toView: nil)
}

Right now one (wrongly sized) accessibility element seems to appear on the screen in the wrong place.

Could someone point me in the right direction?

Many thanks

EDIT:
I've tried a hack-ish work around by placing a UIView over the SKView with UIButton elements in the same location as the SKSpriteNodes. However, accessibility still doesn't want to work. The view is loaded as such:

func loadAccessibilityView()
{
    view.isAccessibilityElement = false
    view.accessibilityElementsHidden = false
    skView.accessibilityElementsHidden = false
    let accessibleSubview = UIView(frame: view.frame)
    accessibleSubview.userInteractionEnabled = true
    accessibleSubview.isAccessibilityElement = false
    view.addSubview(accessibleSubview)
    view.bringSubviewToFront(accessibleSubview)

    let pieces = (skView.scene! as! GameScene).getAllPieces()
    for piece in pieces
    {
        let pieceButton = UIButton(frame: piece.getAccessibilityFrame())
        pieceButton.isAccessibilityElement = true
        pieceButton.accessibilityElementsHidden = false
        pieceButton.accessibilityTraits = UIAccessibilityTraitButton
        pieceButton.setTitle(piece.getText(), forState: UIControlState.Normal)
        pieceButton.setBackgroundImage(UIImage(named: "blue-button"), forState: UIControlState.Normal)
        pieceButton.alpha = 0.2
        pieceButton.accessibilityLabel = piece.getText()
        pieceButton.accessibilityFrame = pieceButton.frame
        pieceButton.addTarget(self, action: Selector("didTap:"), forControlEvents: UIControlEvents.TouchUpInside)
        accessibleSubview.addSubview(pieceButton)
    }

    UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil)

}

The buttons are placed correctly, however accessibility just isn't working at all. Something seems to be preventing it from working.

like image 571
Mason Avatar asked Nov 01 '22 04:11

Mason


1 Answers

I've searched in vain for a description of how to implement VoiceOver in Swift using SpriteKit, so I finally figured out how to do it. Here's some working code that converts a SKNode to an accessible pushbutton when added to a SKScene class:

// Add the following code to a scene where you want to make the SKNode variable named “leave” an accessible button
// leave must already be initialized and added as a child of the scene, or a child of other SKNodes in the scene
// screenHeight must already be defined as the height of the device screen, in points

// Accessibility

private var accessibleElements: [UIAccessibilityElement] = []

private func nodeToDevicePointsFrame(node: SKNode) -> CGRect {

    // first convert from frame in SKNode to frame in SKScene's coordinates

    var sceneFrame = node.frame
    sceneFrame.origin = node.scene!.convertPoint(node.frame.origin, fromNode: node.parent!)

    // convert frame from SKScene coordinates to device points
    // sprite kit scene origin is in lower left, accessibility device screen origin is at upper left
    // assumes scene is initialized using SKSceneScaleMode.Fill using dimensions same as device points

    var deviceFrame = sceneFrame
    deviceFrame.origin.y = CGFloat(screenHeight-1) - (sceneFrame.origin.y + sceneFrame.size.height)
    return deviceFrame
}

private func initAccessibility() {
    if accessibleElements.count == 0 {
        let accessibleLeave = UIAccessibilityElement(accessibilityContainer: self.view!)
        accessibleLeave.accessibilityFrame = nodeToDevicePointsFrame(leave)
        accessibleLeave.accessibilityTraits = UIAccessibilityTraitButton
        accessibleLeave.accessibilityLabel = “leave” // the accessible name of the button
        accessibleElements.append(accessibleLeave)
    }
}
override func didMoveToView(view: SKView) {
    self.isAccessibilityElement = false
    leave.isAccessibilityElement = true
}

override func willMoveFromView(view: SKView) {
    accessibleElements = []
}

override func accessibilityElementCount() -> Int {
    initAccessibility()
    return accessibleElements.count
}

override func accessibilityElementAtIndex(index: Int) -> AnyObject? {
    initAccessibility()
    if (index < accessibleElements.count) {
        return accessibleElements[index] as AnyObject
    } else {
        return nil
    }
}

override func indexOfAccessibilityElement(element: AnyObject) -> Int {
    initAccessibility()
    return accessibleElements.indexOf(element as! UIAccessibilityElement)!
}
like image 198
DOorg Avatar answered Nov 11 '22 06:11

DOorg