Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessibility on Cards. #86

Merged
merged 24 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Mistica/Source/Components/Cards/DataCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class DataCard: UIView {
static let largeIconSize = CGFloat(24)
}

private lazy var cardAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
private let iconContainerView = UIView()
private var iconImageView = DataCardAsset()
private let cardBaseView = CardBase()
Expand All @@ -83,6 +84,18 @@ public class DataCard: UIView {
}
}

override public var accessibilityElements: [Any]? {
get {
// We must set the frame and be sure it is already calculated.
cardAccessibilityElement.accessibilityFrameInContainerSpace = bounds
return [
cardAccessibilityElement,
cardBaseView
].compactMap { $0 }
}
set {}
}

override public init(frame: CGRect) {
super.init(frame: frame)
commomInit()
Expand Down Expand Up @@ -122,6 +135,15 @@ public extension DataCard {
var linkButton: Button {
cardBaseView.buttonsView.linkButton
}

override var accessibilityTraits: UIAccessibilityTraits {
get {
cardAccessibilityElement.accessibilityTraits
}
set {
cardAccessibilityElement.accessibilityTraits = newValue
}
}
}

// MARK: Private
Expand Down Expand Up @@ -199,6 +221,13 @@ private extension DataCard {
case .primaryAndLink(let primaryButton, let linkButton):
cardBaseView.configureButtons(primaryButton: primaryButton, linkButton: linkButton)
}

cardAccessibilityElement.accessibilityLabel = [
configuration.headline,
configuration.title,
configuration.subtitle,
configuration.descriptionTitle
].compactMap { $0 }.joined(separator: " ")
}
}

Expand Down
31 changes: 31 additions & 0 deletions Mistica/Source/Components/Cards/HighlightedCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class HighlightedCard: UIView {
case secondary
}

private lazy var cardAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
private lazy var verticalStackView = UIStackView()
private lazy var horizontalStackView = UIStackView()

Expand Down Expand Up @@ -140,6 +141,20 @@ public class HighlightedCard: UIView {
}
}

override public var accessibilityElements: [Any]? {
get {
updateAccessibilityLabel()
// We must set the frame and be sure it is already calculated.
cardAccessibilityElement.accessibilityFrameInContainerSpace = bounds
return [
cardAccessibilityElement,
actionButton.isHidden ? nil : actionButton,
closeButton.isHidden ? nil : closeButton
].compactMap { $0 }
}
set {}
}

public init(title: String? = nil,
subtitle: String? = nil,
rightImage: UIImage? = nil,
Expand Down Expand Up @@ -292,6 +307,15 @@ public extension HighlightedCard {
backgroundImageView.accessibilityIdentifier = newValue
}
}

override var accessibilityTraits: UIAccessibilityTraits {
get {
cardAccessibilityElement.accessibilityTraits
}
set {
cardAccessibilityElement.accessibilityTraits = newValue
}
}
}

// MARK: Private
Expand Down Expand Up @@ -443,4 +467,11 @@ private extension HighlightedCard {
@objc func actionButtonTapped() {
actionButtonCallback?()
}

func updateAccessibilityLabel() {
cardAccessibilityElement.accessibilityLabel = [
titleAccessibilityLabel,
subtitleAccessibilityLabel
].compactMap { $0 }.joined(separator: " ")
}
}
12 changes: 11 additions & 1 deletion Mistica/Source/Components/Cards/Internals/CardBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,17 @@ extension CardBase {
contentView.descriptionTitle = newValue
}
}


override var accessibilityElements: [Any]? {
get {
[
buttonsView,
fragmentView as Any
].compactMap { $0 }
}
set { }
}

func configureButtons(primaryButton: CardButton?, linkButton: CardLinkButton?) {
buttonsView.configureButtons(primaryButton: primaryButton, linkButton: linkButton)

Expand Down
10 changes: 10 additions & 0 deletions Mistica/Source/Components/Cards/Internals/CardButtonsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ class CardButtons: UIStackView {
private var primaryActionHandler: (() -> Void)?
private var linkActionHandler: (() -> Void)?

override var accessibilityElements: [Any]? {
get {
[
primaryButton.superview == nil ? nil : primaryButton,
linkButton.superview == nil ? nil : linkButton
].compactMap { $0 }
}
set {}
}

override public init(frame: CGRect) {
super.init(frame: frame)
commomInit()
Expand Down
29 changes: 29 additions & 0 deletions Mistica/Source/Components/Cards/MediaCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class MediaCard: UIView {
static let spacingAfterRichMediaView = CGFloat(8)
}

private lazy var cardAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self)
private var richMediaContainerView = UIView()
private let baseCardView = CardBase()

Expand All @@ -56,6 +57,18 @@ public class MediaCard: UIView {
baseCardView.fragmentView = fragmentView
}
}

override public var accessibilityElements: [Any]? {
get {
cardAccessibilityElement.accessibilityFrameInContainerSpace = bounds
return [
cardAccessibilityElement,
fragmentView as Any,
Copy link
Contributor

@jmbrocal jmbrocal Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this cast?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid the Xcode warning about your implicitly casting from N to Any.

baseCardView.buttonsView
].compactMap { $0 }
}
set {}
}

public var contentConfiguration: MediaCardConfiguration? {
didSet {
Expand Down Expand Up @@ -93,6 +106,15 @@ public extension MediaCard {
var linkButton: Button {
baseCardView.buttonsView.linkButton
}

override var accessibilityTraits: UIAccessibilityTraits {
get {
cardAccessibilityElement.accessibilityTraits
}
set {
cardAccessibilityElement.accessibilityTraits = newValue
}
}
}

// MARK: Private
Expand Down Expand Up @@ -160,6 +182,13 @@ private extension MediaCard {
baseCardView.descriptionTitle = configuration.descriptionTitle

baseCardView.configureButtons(primaryButton: configuration.button, linkButton: configuration.link)

cardAccessibilityElement.accessibilityLabel = [
baseCardView.headline,
baseCardView.title,
baseCardView.subtitle,
baseCardView.descriptionTitle
].compactMap { $0 }.joined(separator: " ")
}
}

Expand Down
57 changes: 57 additions & 0 deletions Mistica/Source/Components/Cards/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [HighlightedCard](#highlightedcard)
* [Right Image](#right-image)
* [How to use a HighlightedCard](#how-to-use-a-highlightedcard)
* [Accessibility](#accessibility)

## DataCard

Expand Down Expand Up @@ -112,3 +113,59 @@ highlightedCard.showCloseButton = true
```

When using with autolayout, **HighlightedCard** has no intrinsic size for the width but it has an specific intrinsic size for the height.

# Accessibility

Cards are ready for VoiceOver.
They will be an unique element and if has buttons, them will be also focusable with VoiceOver.

VoiceOver will read the following components (in this particular order):
- headline
- title
- subtitle
- description

## Extra content Accessibility

If extra view is provided to the cell, extra view is reponsible about his accessibility and will be a focusable element for voice over inside the card like the buttons.
To provide a good Accessibility Experience watch [this video](https://developer.apple.com/videos/play/wwdc2018/230/) is highly recommended.

This is an example extra view class that wil be an unique element for VoiceOver and will read Title and Text.

```swift
class ExtraView: UIView {
private let titleLabel = UILabel()
private let textLabel = UILabel()

// Initializers...

func configure(title: String, text: String) {
titleLabel.text = title
textLabel.text = text

// Update the accessibility label with the
// content to be readed by VoiceOver
accessibilityLabel = "\(title) \(text)"
}

private func commonInit() {
// Setup code...

// Make this view as an accesible element to
// be focusable by VoiceOver.
isAccessibleElement = true
}
}

card.fragmentView = extraView
```


## Cards as Cells (UITableViewCell/UICollectionViewCell)

To use the cards as cells, as selectable items, set the `accessibilityTraits` of the Card to `[.button]` is recommended.
And double tap with the focused card will launch `didSelect` on table/collection delegate.

## UITapGesture

Same as working with cells, Cards can be tappables with `UITapGestureRecognizer`, setting the `accessibilityTrait` to `[.button]` is also recommended.