Reputation: 17530
I have a UITableViewCell
containing a view which has some child accessibility elements. The view is quite long, longer that a single screen.
When user flicks left and right to navigate through the child elements, the selected element is out of screen sometimes. When he double taps later he cannot trigger the action for such an element.
Why does it not work? How to make it work correctly?
Why is the selected element out of screen sometimes? How to make it in screen?
Here is the sample code.
Upvotes: 1
Views: 460
Reputation: 5671
Why does it not work? How to make it work correctly?
With VoiceOver OFF ❌, the tap
function prints the element once you touch it AND if it's visible because a tap gesture only happens on screen.
With VoiceOver ON ✅, you should think the same way even if the screen reader is able to read out the previous labels that are out of screen: when you double-tap with one finger for an element out of screen, the gesture can't be handled with a GestureRecognizer
.
A solution could be the use of the accessibilityActivate method that's triggered when a double-tap occurs on a a11y element.
I suggest to create a label class in which you implement this function:
class myLabel:UILabel {
init(frame: CGRect, index: Int) {
super.init(frame: frame)
text = "\(index)"
isAccessibilityElement = true
isUserInteractionEnabled = true
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func accessibilityActivate() -> Bool {
print(text!)
return true
}
@objc func tap(sender: UITapGestureRecognizer) {
guard sender.state == .recognized else { return }
print(sender.view!)
}
}
The LongView
class has the following code to define its behavior:
class LongView: UIView {
override var isAccessibilityElement: Bool {
get { return false }
set { }
}
override var accessibilityElementsHidden: Bool {
get { return false }
set { }
}
override init(frame: CGRect) {
super.init(frame: frame)
let screenWidth = UIScreen.main.bounds.width
var x: CGFloat = 20
var y: CGFloat = 20
let width: CGFloat = 200
let height: CGFloat = 40
let spacing: CGFloat = 20
for index in 0..<20 {
let label = myLabel(frame: CGRect(x: x, y: y, width: width, height: height),
index: index)
addSubview(label)
x += width + spacing
if x + width > screenWidth {
y += height + spacing
x = spacing
}
}
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override var intrinsicContentSize: CGSize {
CGSize(width: UIScreen.main.bounds.width,
height: subviews.map { $0.frame.maxY }.max()! + 20)
}
}
And the table view cell class is as follows:
class Cell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let longView = LongView()
longView.accessibilityElementsHidden = false
contentView.addSubview(longView)
longView.translatesAutoresizingMaskIntoConstraints = false
longView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
longView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
longView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
longView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
Now, you can notice that:
GestureRecognizer
function will be triggered when VoiceOver is OFF.I'm not sure that all the added lines are necessary to reach your goal but this is the rationale to solve your UIAccessibilityElement focusing issue within a long view inside a UITableViewCell. 🥳🎉🎊
Upvotes: 1