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 // MARK: - Codec
//-------------------------------------------------- //--------------------------------------------------
public override init() {
super.init()
}
public required init(from decoder: Decoder) throws { public required init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) let typeContainer = try decoder.container(keyedBy: CodingKeys.self)

View File

@ -61,6 +61,7 @@ open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelPro
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Codec // MARK: - Codec
//-------------------------------------------------- //--------------------------------------------------
public init() {}
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) 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 /// The view that we use for paging
public var pagingView: (UIView & CarouselPageControlProtocol)? public var pagingView: (UIView & CarouselPageControlProtocol)?
/// If the carousel should loop after scrolling past the first and final cells. /// If the carousel should loop after scrolling past the first and final cells.
public var loop = false public var loop = false
@ -179,7 +179,7 @@ open class Carousel: View {
numberOfPages = newMolecules.count numberOfPages = newMolecules.count
molecules = newMolecules 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). // 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 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) { public func setAccessiblity(_ cell: UICollectionViewCell?, index: Int) {
guard let cell = cell else { return } guard let cell = cell else { return }
if index == currentIndex { if index == currentIndex {
cell.accessibilityElementsHidden = false cell.accessibilityElementsHidden = false
var array = cell.accessibilityElements
if let pagingView = pagingView { // set to nil to get fresh elements
if let acc = pagingView.accessibilityElements { accessibilityElements = nil
array?.append(contentsOf: acc)
} else {
array?.append(pagingView)
}
}
accessibilityElements = array
} else { } else {
cell.accessibilityElementsHidden = true cell.accessibilityElementsHidden = true
} }
} }
func trackSwipeActionAnalyticsforIndex(_ index : Int){ /// Accessibility element that allows for adjustable carousel.
guard let itemModel = molecules?[index], private lazy var carouselAccessibilityElement: CarouselAccessibilityElement = {
let viewControllerObject = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol else { return } let accessibilityElement = CarouselAccessibilityElement(accessibilityContainer: self)
MVMCoreUILoggingHandler.shared()?.defaultLogAction(forController: viewControllerObject, actionInformation: itemModel.toJSON(), additionalData: nil) 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. /// Go to the cell at the specified index.
func goTo(_ index: Int, animated: Bool) { func goTo(_ index: Int, animated: Bool) {
showPeaking(false) showPeaking(false)
setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index) setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index)
currentIndex = index currentIndex = index
@ -378,7 +403,7 @@ extension Carousel: UIScrollViewDelegate {
} }
} }
} }
open func scrollViewDidScroll(_ scrollView: UIScrollView) { open func scrollViewDidScroll(_ scrollView: UIScrollView) {
// Adjust for looping // Adjust for looping
@ -395,12 +420,15 @@ extension Carousel: UIScrollViewDelegate {
// Disable peaking when dragging. // Disable peaking when dragging.
dragging = true dragging = true
guard !UIAccessibility.isVoiceOverRunning else { return }
showPeaking(false) showPeaking(false)
} }
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
dragging = false 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. // 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, guard (model as? CarouselModel)?.paging == true,
@ -464,3 +492,106 @@ extension Carousel: UIScrollViewDelegate {
trackSwipeActionAnalyticsforIndex(pageIndex) 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 useHorizontalMargins: Bool?
public var leftPadding: CGFloat? public var leftPadding: CGFloat?
public var rightPadding: CGFloat? public var rightPadding: CGFloat?
public var accessibilityText: String?
public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) { public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) {
self.molecules = molecules self.molecules = molecules
} }
@ -57,6 +58,7 @@ import UIKit
case useHorizontalMargins case useHorizontalMargins
case leftPadding case leftPadding
case rightPadding case rightPadding
case accessibilityText
} }
//-------------------------------------------------- //--------------------------------------------------
@ -83,6 +85,7 @@ import UIKit
useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins) useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins)
leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding) leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding)
rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding) rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -101,5 +104,6 @@ import UIKit
try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins) try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins)
try container.encodeIfPresent(leftPadding, forKey: .leftPadding) try container.encodeIfPresent(leftPadding, forKey: .leftPadding)
try container.encodeIfPresent(rightPadding, forKey: .rightPadding) try container.encodeIfPresent(rightPadding, forKey: .rightPadding)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
} }
} }