accessibility fixes

This commit is contained in:
Pfeil, Scott Robert 2020-07-08 16:30:30 -04:00
parent 02e0d9711e
commit 2052e4ddc6
4 changed files with 160 additions and 20 deletions

View File

@ -32,6 +32,10 @@ open class BarsCarouselIndicatorModel: CarouselIndicatorModel {
// MARK: - Codec
//--------------------------------------------------
public override init() {
super.init()
}
public required init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)

View File

@ -61,6 +61,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
public init() {}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)

View File

@ -55,7 +55,7 @@ open class Carousel: View {
/// The view that we use for paging
public var pagingView: (UIView & CarouselPageControlProtocol)?
/// If the carousel should loop after scrolling past the first and final cells.
public var loop = false
@ -179,7 +179,7 @@ open class Carousel: View {
numberOfPages = newMolecules.count
molecules = newMolecules
if carouselModel?.loop ?? false && newMolecules.count > 1 {
if carouselModel?.loop ?? false && newMolecules.count > 1 && !UIAccessibility.isVoiceOverRunning {
// Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell).
loop = true
@ -277,31 +277,57 @@ open class Carousel: View {
}
}
func trackSwipeActionAnalyticsforIndex(_ index : Int){
guard let itemModel = molecules?[index],
let viewControllerObject = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return }
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: viewControllerObject, actionInformation: itemModel.toJSON(), additionalData: nil)
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
/// Sets accessibility for the cell. Only the current cell is accessible.
public func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) {
guard let cell = cell else { return }
if index == currentIndex {
cell.accessibilityElementsHidden = false
var array = cell.accessibilityElements
if let pagingView = pagingView {
if let acc = pagingView.accessibilityElements {
array?.append(contentsOf: acc)
} else {
array?.append(pagingView)
}
}
accessibilityElements = array
// set to nil to get fresh elements
accessibilityElements = nil
} else {
cell.accessibilityElementsHidden = true
}
}
func trackSwipeActionAnalyticsforIndex(_ index : Int){
guard let itemModel = molecules?[index],
let viewControllerObject = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return }
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: viewControllerObject, actionInformation: itemModel.toJSON(), additionalData: nil)
/// Accessibility element that allows for adjustable carousel.
private lazy var carouselAccessibilityElement: CarouselAccessibilityElement = {
let accessibilityElement = CarouselAccessibilityElement(accessibilityContainer: self)
accessibilityElement.accessibilityFrameInContainerSpace = collectionView.frame
return accessibilityElement
}()
private var _accessibilityElements: [Any]?
/// Returns the accessibilityElements. If nil, will return current cell and carouselAccessibilityElement
override open var accessibilityElements: [Any]? {
set {
_accessibilityElements = newValue
}
get {
// Only return custom accessibility if nil.
guard _accessibilityElements == nil else {
return _accessibilityElements
}
if let currentCell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) {
_accessibilityElements = [currentCell, carouselAccessibilityElement]
} else {
_accessibilityElements = [carouselAccessibilityElement]
}
return _accessibilityElements
}
}
}
@ -347,7 +373,6 @@ extension Carousel: UIScrollViewDelegate {
/// Go to the cell at the specified index.
func goTo(_ index: Int, animated: Bool) {
showPeaking(false)
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index)
currentIndex = index
@ -378,7 +403,7 @@ extension Carousel: UIScrollViewDelegate {
}
}
}
open func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Adjust for looping
@ -395,12 +420,15 @@ extension Carousel: UIScrollViewDelegate {
// Disable peaking when dragging.
dragging = true
guard !UIAccessibility.isVoiceOverRunning else { return }
showPeaking(false)
}
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
dragging = false
guard !UIAccessibility.isVoiceOverRunning else { return }
// This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). Math requires that we are using UICollectionViewFlowLayout.
guard (model as? CarouselModel)?.paging == true,
@ -464,3 +492,106 @@ extension Carousel: UIScrollViewDelegate {
trackSwipeActionAnalyticsforIndex(pageIndex)
}
}
// Adapted from: https://developer.apple.com/documentation/uikit/accessibility_for_ios_and_tvos/delivering_an_exceptional_accessibility_experience
/// Ensures a good accessibility experience. Adds adjustable swiping for cards.
class CarouselAccessibilityElement: UIAccessibilityElement {
/// This indicates to the user what exactly this element is supposed to be.
override var accessibilityLabel: String? {
get {
guard let containerView = accessibilityContainer as? Carousel,
let accessibilityLabel = containerView.accessibilityLabel else { return super.accessibilityLabel }
return accessibilityLabel
}
set {
super.accessibilityLabel = newValue
}
}
override var accessibilityValue: String? {
get {
// Read which card we are on.
guard let containerView = accessibilityContainer as? Carousel,
let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: containerView.currentIndex + 1)) else {
return super.accessibilityValue
}
return String(format: format, indexString, containerView.numberOfPages)
}
set {
super.accessibilityValue = newValue
}
}
// This tells VoiceOver that our element will support the increment and decrement callbacks.
/// - Tag: accessibility_traits
override var accessibilityTraits: UIAccessibilityTraits {
get {
return .adjustable
}
set {
super.accessibilityTraits = newValue
}
}
/**
A convenience for forward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
*/
func accessibilityScrollForward() -> Bool {
guard let containerView = accessibilityContainer as? Carousel else { return false }
let newIndex = containerView.currentIndex + 1
guard newIndex < containerView.numberOfPages else { return false }
containerView.goTo(newIndex, animated: false)
return true
}
/**
A convenience for backward scrolling in both `accessibilityIncrement` and `accessibilityScroll`.
It returns a `Bool` because `accessibilityScroll` needs to know if the scroll was successful.
*/
func accessibilityScrollBackward() -> Bool {
guard let containerView = accessibilityContainer as? Carousel else { return false }
let newIndex = containerView.currentIndex - 1
guard newIndex >= 0 else { return false }
containerView.goTo(newIndex, animated: false)
return true
}
/*
Overriding the following two methods allows the user to perform increment and decrement actions
(done by swiping up or down).
*/
/// - Tag: accessibility_increment_decrement
override func accessibilityIncrement() {
// This causes the picker to move forward one if the user swipes up.
_ = accessibilityScrollForward()
}
override func accessibilityDecrement() {
// This causes the picker to move back one if the user swipes down.
_ = accessibilityScrollBackward()
}
/*
This will cause the picker to move forward or backwards on when the user does a 3-finger swipe,
depending on the direction of the swipe. The return value indicates whether or not the scroll was successful,
so that VoiceOver can alert the user if it was not.
*/
override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool {
if direction == .left {
return accessibilityScrollForward()
} else if direction == .right {
return accessibilityScrollBackward()
}
return false
}
}

View File

@ -32,7 +32,8 @@ import UIKit
public var useHorizontalMargins: Bool?
public var leftPadding: CGFloat?
public var rightPadding: CGFloat?
public var accessibilityText: String?
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
self.molecules = molecules
}
@ -57,6 +58,7 @@ import UIKit
case useHorizontalMargins
case leftPadding
case rightPadding
case accessibilityText
}
//--------------------------------------------------
@ -83,6 +85,7 @@ import UIKit
useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins)
leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding)
rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
}
public func encode(to encoder: Encoder) throws {
@ -101,5 +104,6 @@ import UIKit
try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins)
try container.encodeIfPresent(leftPadding, forKey: .leftPadding)
try container.encodeIfPresent(rightPadding, forKey: .rightPadding)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
}
}