Accessibility (Voice Over) with Sprite Kit

随声附和 提交于 2019-12-21 16:55:16

问题


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.


回答1:


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)!
}



回答2:


Accessibility frames are defined in the fixed physical screen coordinates, not UIView coordinates, and transforming between them is kind of tricky.

The device origin is the lower left of the screen, with X up, when the device is in landscape right mode.

It's a pain converting, I've no idea why Apple did it that way.



来源:https://stackoverflow.com/questions/30191912/accessibility-voice-over-with-sprite-kit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!