From 8640aedb396dd76e0daaf169e811f6a1685d92f2 Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Thu, 11 Jul 2024 16:09:12 +0200 Subject: [PATCH 1/9] As pat of IOS-10347 we need to set sheet's title label accessibility trait to header --- Sources/Mistica/Components/Sheet/View/SheetViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Mistica/Components/Sheet/View/SheetViewController.swift b/Sources/Mistica/Components/Sheet/View/SheetViewController.swift index cd596be3d..9b67e3f3c 100644 --- a/Sources/Mistica/Components/Sheet/View/SheetViewController.swift +++ b/Sources/Mistica/Components/Sheet/View/SheetViewController.swift @@ -35,6 +35,7 @@ public class SheetViewController: UIViewController { label.textAlignment = .left label.textColor = .textPrimary label.font = .textPreset5() + label.accessibilityTraits = .header return label } return nil From 166b98d84bde66748b48a1e1eac6a6be5ded6d1b Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Fri, 9 Aug 2024 13:17:45 +0200 Subject: [PATCH 2/9] Added new protocol for accessible text views Added full cell accessbility config struct First approach for improving cells accessibility --- .../Internals/CellCenterSectionView.swift | 2 +- .../Lists/ListCellContentView.swift | 33 ++++++++++++- .../Components/Lists/ListTableViewCell.swift | 49 +++++++++++++++++++ Sources/Mistica/Components/Tag/TagView.swift | 8 +++ .../Accessibility/AccessibleTextualView.swift | 13 +++++ .../FullCellAccessibilityConfig.swift | 19 +++++++ 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 Sources/MisticaCommon/Utils/Accessibility/AccessibleTextualView.swift create mode 100644 Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift diff --git a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift index ba32e90d4..857ca94af 100644 --- a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift +++ b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift @@ -9,7 +9,7 @@ import UIKit class CellCenterSectionView: UIStackView { - var headlineView: UIView? { + var headlineView: AccessibleTextualView? { didSet { oldValue?.removeFromSuperview() diff --git a/Sources/Mistica/Components/Lists/ListCellContentView.swift b/Sources/Mistica/Components/Lists/ListCellContentView.swift index afae5a794..8823ce35c 100644 --- a/Sources/Mistica/Components/Lists/ListCellContentView.swift +++ b/Sources/Mistica/Components/Lists/ListCellContentView.swift @@ -10,11 +10,30 @@ import UIKit protocol ListCellContentTableViewDelegate { func cellStyleChanged() + func accessibilityChanged() } // MARK: ListCellContentView open class ListCellContentView: UIView { + // MARK: Accessibility properties + + var defaultAccessibilityLabel: String { + let titleText = titleAccessibilityLabel ?? titleAttributedText?.string ?? title + let subtitleText = subtitleAccessibilityLabel ?? subtitleAttributedText?.string ?? subtitle + let detailText = detailAccessibilityLabel ?? detailTextAttributedText?.string ?? detailText + let headlineText = headlineView?.accessibleText + + var accessibilityComponents: [String?] = [ + titleText, + headlineText, + subtitleText, + detailText + ] + + return accessibilityComponents.compactMap { $0 }.joined(separator: ", ") + } + // MARK: View Styles public enum ViewStyles { @@ -82,6 +101,7 @@ open class ListCellContentView: UIView { set { centerSection.titleLabel.text = newValue updateAssetAligment() + updateAccessibility() } } @@ -100,6 +120,7 @@ open class ListCellContentView: UIView { } set { centerSection.titleLabel.attributedText = newValue + updateAccessibility() } } @@ -117,6 +138,7 @@ open class ListCellContentView: UIView { centerSection.subtitleLabel.text = newValue centerSection.didSetTextToSubtitleLabel() updateAssetView() + updateAccessibility() } } @@ -136,6 +158,7 @@ open class ListCellContentView: UIView { set { centerSection.subtitleLabel.attributedText = newValue centerSection.didSetTextToSubtitleLabel() + updateAccessibility() } } @@ -147,6 +170,7 @@ open class ListCellContentView: UIView { centerSection.detailLabel.text = newValue centerSection.didSetTexToDetailText() updateAssetView() + updateAccessibility() } } @@ -166,16 +190,18 @@ open class ListCellContentView: UIView { set { centerSection.detailLabel.attributedText = newValue centerSection.didSetTexToDetailText() + updateAccessibility() } } - public var headlineView: UIView? { + public var headlineView: AccessibleTextualView? { get { centerSection.headlineView } set { centerSection.headlineView = newValue updateAssetView() + updateAccessibility() } } @@ -333,6 +359,7 @@ private extension ListCellContentView { func commonInit() { layoutViews() updateCellStyle() + accessibilityElements = [headlineView, centerSection.titleLabel, centerSection.subtitleLabel, centerSection.detailLabel].compactMap { $0 } } func layoutViews() { @@ -385,4 +412,8 @@ private extension ListCellContentView { leftSection.topAlignment() } } + + func updateAccessibility() { + tableViewDelegate?.accessibilityChanged() + } } diff --git a/Sources/Mistica/Components/Lists/ListTableViewCell.swift b/Sources/Mistica/Components/Lists/ListTableViewCell.swift index 574a42b07..605931524 100644 --- a/Sources/Mistica/Components/Lists/ListTableViewCell.swift +++ b/Sources/Mistica/Components/Lists/ListTableViewCell.swift @@ -9,7 +9,14 @@ import Foundation import UIKit +public enum ListCellAccessibilityType { + case navigation + case action + case informative(accessibilityLabel: String?) +} + open class ListTableViewCell: UITableViewCell { + public var listCellAccessibilityType: ListCellAccessibilityType = .navigation public var listCellContentView = ListCellContentView() private lazy var cellSeparatorView = SeparatorView(axis: .horizontal) @@ -21,6 +28,24 @@ open class ListTableViewCell: UITableViewCell { } } + // MARK: Accessibility properties + + public var fullCellAccessibilityConfig: FullCellAccessibilityConfig? = nil { + didSet { + if let fullCellAccessibilityConfig { + isAccessibilityElement = true + accessibilityLabel = fullCellAccessibilityConfig.accessibilityLabel + } else { + isAccessibilityElement = false + accessibilityLabel = nil + } + } + } + + public var defaultAccessibilityLabel: String { + listCellContentView.defaultAccessibilityLabel + } + // MARK: Initializers override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -77,6 +102,7 @@ open class ListTableViewCell: UITableViewCell { listCellContentView.tableViewDelegate = self layoutViews() updateCellStyle() + fullCellAccessibilityConfig = FullCellAccessibilityConfig(accessibilityLabel: listCellContentView.defaultAccessibilityLabel) } func layoutViews() { @@ -95,15 +121,38 @@ open class ListTableViewCell: UITableViewCell { func updateCellStyle() { backgroundColor = .background } + + override public func accessibilityActivate() -> Bool { + guard let activationAction = fullCellAccessibilityConfig?.activationAction else { return false } + + activationAction() + return true + } +} + +// MARK: Accessibility + +extension ListTableViewCell { + public func setDefaultFullCellAccessibilityConfig(activationAction: (() -> Void)? = nil) { + fullCellAccessibilityConfig = FullCellAccessibilityConfig(accessibilityLabel: defaultAccessibilityLabel, activationAction: activationAction) + } } +// MARK: ListCellContentTableViewDelegate + extension ListTableViewCell: ListCellContentTableViewDelegate { public func cellStyleChanged() { listCellContentView.directionalLayoutMargins = listCellContentView.cellStyle.contentViewLayoutMargins cellSeparatorView.isHidden = listCellContentView.cellStyle.cellSeparatorIsHidden } + + func accessibilityChanged() { + fullCellAccessibilityConfig?.accessibilityLabel = listCellContentView.defaultAccessibilityLabel + } } +// MARK: Private methods + private extension ListTableViewCell { var highlightedView: UIView { switch listCellContentView.cellStyle { diff --git a/Sources/Mistica/Components/Tag/TagView.swift b/Sources/Mistica/Components/Tag/TagView.swift index d3d488449..4c4e12886 100644 --- a/Sources/Mistica/Components/Tag/TagView.swift +++ b/Sources/Mistica/Components/Tag/TagView.swift @@ -161,6 +161,14 @@ public class TagView: UIView { } } +// MARK: + +extension TagView: AccessibleTextualView { + public var accessibleText: String? { + return text + } +} + // MARK: Private methods private extension TagView { diff --git a/Sources/MisticaCommon/Utils/Accessibility/AccessibleTextualView.swift b/Sources/MisticaCommon/Utils/Accessibility/AccessibleTextualView.swift new file mode 100644 index 000000000..297bc8102 --- /dev/null +++ b/Sources/MisticaCommon/Utils/Accessibility/AccessibleTextualView.swift @@ -0,0 +1,13 @@ +// +// AccessibleTextualView.swift +// +// Made with ❤️ by Novum +// +// Copyright © Telefonica. All rights reserved. +// + +import UIKit + +public protocol AccessibleTextualView: UIView { + var accessibleText: String? { get } +} diff --git a/Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift b/Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift new file mode 100644 index 000000000..c6e5da66c --- /dev/null +++ b/Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift @@ -0,0 +1,19 @@ +// +// FullCellAccessibilityConfig.swift +// +// Made with ❤️ by Novum +// +// Copyright © Telefonica. All rights reserved. +// + +import Foundation + +public struct FullCellAccessibilityConfig { + public var accessibilityLabel: String + public let activationAction: (() -> Void)? + + public init(accessibilityLabel: String, activationAction: (() -> Void)? = nil) { + self.accessibilityLabel = accessibilityLabel + self.activationAction = activationAction + } +} From 97734118471e9145efc75688765568cb1d64fd14 Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Tue, 13 Aug 2024 12:28:37 +0200 Subject: [PATCH 3/9] Added accessibility type to lists catalog to be able to test it Update ListCellContent accessibility (via delegate) when headlineView is updated Fixed some typos Deleted no longer used FullCellAccessibilityConfig and created accessibility type Created separate files for list cell accessibility data --- .../UICatalogListsViewController.swift | 36 ++++++++++++ .../Internals/CellCenterSectionView.swift | 8 +++ .../Lists/ListCellContentView.swift | 25 +++++++-- .../Components/Lists/ListTableViewCell.swift | 55 +++++++++---------- .../FullCellAccessibilityConfig.swift | 19 ------- ...AccessibilityListCellInteractiveData.swift | 21 +++++++ .../ListCells/AccessibilityListCellType.swift | 17 ++++++ 7 files changed, 127 insertions(+), 54 deletions(-) delete mode 100644 Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift create mode 100644 Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift create mode 100644 Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift diff --git a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift index 046289690..6c74bffe4 100644 --- a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift +++ b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift @@ -18,6 +18,7 @@ private enum Section: Int, CaseIterable { case headline case assetStyle case controlType + case accessibilityType case show } @@ -77,6 +78,16 @@ class UICatalogListsViewController: UITableViewController { return cell }() + private lazy var accessibilityTypeCell: UISegmentedControlTableViewCell = { + let cell = UISegmentedControlTableViewCell(reuseIdentifier: "accessibilityTypeCell") + cell.segmentedControl.insertSegment(withTitle: "Default", at: 0, animated: false) + cell.segmentedControl.insertSegment(withTitle: "Informative", at: 1, animated: false) + cell.segmentedControl.insertSegment(withTitle: "Custom informative", at: 2, animated: false) + cell.segmentedControl.insertSegment(withTitle: "Interactive", at: 3, animated: false) + cell.segmentedControl.selectedSegmentIndex = 0 + return cell + }() + private lazy var showListCell: UITableViewCell = { let cell = UITableViewCell(style: .default, reuseIdentifier: "showListCell") cell.textLabel?.textColor = .textLink @@ -92,6 +103,7 @@ class UICatalogListsViewController: UITableViewController { [headlineCell], [assetStyleCell], [controlCell], + [accessibilityTypeCell], [showListCell] ] @@ -186,6 +198,26 @@ extension UICatalogListsViewController { break } + switch accessibilityTypeCell.segmentedControl.selectedSegmentIndex { + case 0: + sampleVC.accessibilityType = .default + case 1: + sampleVC.accessibilityType = .informative + case 2: + sampleVC.accessibilityType = .customInformative("Custom informative label") + case 3: + let accessibilityInteractiveData = AccessibilityListCellInteractiveData { [weak self] in + let alertController = UIAlertController(title: nil, message: "Custom action", preferredStyle: .alert) + let alertAction = UIAlertAction(title: "Accept", style: .cancel) + alertController.addAction(alertAction) + + self?.present(alertController, animated: true) + } + sampleVC.accessibilityType = .interactive(accessibilityInteractiveData) + default: + break + } + show(sampleVC, sender: self) tableView.deselectRow(animated: true) view.endEditing(true) @@ -209,6 +241,8 @@ private extension Section { return "Asset Style" case .controlType: return "Control Type" + case .accessibilityType: + return "Accessibility Type" case .show: return nil } @@ -231,6 +265,7 @@ private class UICatalogListSampleViewController: UIViewController, UITableViewDa var assetType: ListCellContentView.CellAssetType! var customControl = CustomControl.none var cellLayoutStyle: ListCellContentView.CellStyle! + var accessibilityType: AccessibilityListCellType! let numberOfRows = 30 @@ -283,6 +318,7 @@ private class UICatalogListSampleViewController: UIViewController, UITableViewDa } cell.isCellSeparatorHidden = indexPath.row == (numberOfRows - 1) + cell.accessibilityType = accessibilityType return cell } diff --git a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift index 857ca94af..142ac5257 100644 --- a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift +++ b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift @@ -8,6 +8,10 @@ import UIKit +protocol ListCellContentViewDelegate { + func accessibilityChanged() +} + class CellCenterSectionView: UIStackView { var headlineView: AccessibleTextualView? { didSet { @@ -17,6 +21,8 @@ class CellCenterSectionView: UIStackView { insertArrangedSubview(view, at: 0) updateSpacing() } + + listCellContentViewDelegate?.accessibilityChanged() } } @@ -24,6 +30,8 @@ class CellCenterSectionView: UIStackView { lazy var subtitleLabel = IntrinsictHeightLabel() lazy var detailLabel = IntrinsictHeightLabel() + var listCellContentViewDelegate: ListCellContentViewDelegate? + var titleTextColor: UIColor = .textPrimary { didSet { titleLabel.textColor = titleTextColor diff --git a/Sources/Mistica/Components/Lists/ListCellContentView.swift b/Sources/Mistica/Components/Lists/ListCellContentView.swift index 8823ce35c..f0be4dfad 100644 --- a/Sources/Mistica/Components/Lists/ListCellContentView.swift +++ b/Sources/Mistica/Components/Lists/ListCellContentView.swift @@ -24,7 +24,7 @@ open class ListCellContentView: UIView { let detailText = detailAccessibilityLabel ?? detailTextAttributedText?.string ?? detailText let headlineText = headlineView?.accessibleText - var accessibilityComponents: [String?] = [ + let accessibilityComponents: [String?] = [ titleText, headlineText, subtitleText, @@ -77,7 +77,7 @@ open class ListCellContentView: UIView { // MARK: SubViews - /// View used in `ListCellStyle.boxed` style for show a rounded border arround the content + /// View used in `ListCellStyle.boxed` style for show a rounded border around the content lazy var cellBorderView = UIView() private lazy var cellContentView = UIStackView() var tableViewDelegate: ListCellContentTableViewDelegate? @@ -100,7 +100,7 @@ open class ListCellContentView: UIView { } set { centerSection.titleLabel.text = newValue - updateAssetAligment() + updateAssetAlignment() updateAccessibility() } } @@ -353,13 +353,22 @@ public extension ListCellContentView { } } +// MARK: ListCellContentViewDelegate + +extension ListCellContentView: ListCellContentViewDelegate { + func accessibilityChanged() { + updateAccessibilityElements() + } +} + // MARK: Private private extension ListCellContentView { func commonInit() { + centerSection.listCellContentViewDelegate = self layoutViews() updateCellStyle() - accessibilityElements = [headlineView, centerSection.titleLabel, centerSection.subtitleLabel, centerSection.detailLabel].compactMap { $0 } + updateAccessibilityElements() } func layoutViews() { @@ -396,7 +405,7 @@ private extension ListCellContentView { return } - updateAssetAligment() + updateAssetAlignment() leftSection.assetType = assetType @@ -405,7 +414,7 @@ private extension ListCellContentView { } } - func updateAssetAligment() { + func updateAssetAlignment() { if centerSection.headlineView == nil, !centerSection.hasSubtitleText, !centerSection.hasDetailText { leftSection.centerAlignment() } else { @@ -416,4 +425,8 @@ private extension ListCellContentView { func updateAccessibility() { tableViewDelegate?.accessibilityChanged() } + + func updateAccessibilityElements() { + accessibilityElements = [centerSection.titleLabel, headlineView as Any, centerSection.subtitleLabel, centerSection.detailLabel].compactMap { $0 } + } } diff --git a/Sources/Mistica/Components/Lists/ListTableViewCell.swift b/Sources/Mistica/Components/Lists/ListTableViewCell.swift index 605931524..dd474175e 100644 --- a/Sources/Mistica/Components/Lists/ListTableViewCell.swift +++ b/Sources/Mistica/Components/Lists/ListTableViewCell.swift @@ -9,16 +9,10 @@ import Foundation import UIKit -public enum ListCellAccessibilityType { - case navigation - case action - case informative(accessibilityLabel: String?) -} - open class ListTableViewCell: UITableViewCell { - public var listCellAccessibilityType: ListCellAccessibilityType = .navigation public var listCellContentView = ListCellContentView() private lazy var cellSeparatorView = SeparatorView(axis: .horizontal) + private var accessibilityActivationAction: (() -> Void)? public var isCellSeparatorHidden: Bool = true { didSet { @@ -30,20 +24,29 @@ open class ListTableViewCell: UITableViewCell { // MARK: Accessibility properties - public var fullCellAccessibilityConfig: FullCellAccessibilityConfig? = nil { + public var defaultAccessibilityLabel: String { + listCellContentView.defaultAccessibilityLabel + } + + public var accessibilityType: AccessibilityListCellType = .default { didSet { - if let fullCellAccessibilityConfig { - isAccessibilityElement = true - accessibilityLabel = fullCellAccessibilityConfig.accessibilityLabel - } else { - isAccessibilityElement = false - accessibilityLabel = nil - } + accessibilityTypeUpdated() } } - public var defaultAccessibilityLabel: String { - listCellContentView.defaultAccessibilityLabel + func accessibilityTypeUpdated() { + switch accessibilityType { + case .interactive(let accessibilityInteractiveData): + isAccessibilityElement = true + accessibilityLabel = accessibilityInteractiveData.label ?? defaultAccessibilityLabel + accessibilityActivationAction = accessibilityInteractiveData.action + case .informative: + isAccessibilityElement = false + accessibilityLabel = nil + case .customInformative(let accessibilityText): + isAccessibilityElement = true + accessibilityLabel = accessibilityText + } } // MARK: Initializers @@ -102,7 +105,6 @@ open class ListTableViewCell: UITableViewCell { listCellContentView.tableViewDelegate = self layoutViews() updateCellStyle() - fullCellAccessibilityConfig = FullCellAccessibilityConfig(accessibilityLabel: listCellContentView.defaultAccessibilityLabel) } func layoutViews() { @@ -123,21 +125,16 @@ open class ListTableViewCell: UITableViewCell { } override public func accessibilityActivate() -> Bool { - guard let activationAction = fullCellAccessibilityConfig?.activationAction else { return false } + guard case let .interactive(accessibilityInteractiveData) = accessibilityType, + let action = accessibilityInteractiveData.action else { + return false + } - activationAction() + action() return true } } -// MARK: Accessibility - -extension ListTableViewCell { - public func setDefaultFullCellAccessibilityConfig(activationAction: (() -> Void)? = nil) { - fullCellAccessibilityConfig = FullCellAccessibilityConfig(accessibilityLabel: defaultAccessibilityLabel, activationAction: activationAction) - } -} - // MARK: ListCellContentTableViewDelegate extension ListTableViewCell: ListCellContentTableViewDelegate { @@ -147,7 +144,7 @@ extension ListTableViewCell: ListCellContentTableViewDelegate { } func accessibilityChanged() { - fullCellAccessibilityConfig?.accessibilityLabel = listCellContentView.defaultAccessibilityLabel + accessibilityTypeUpdated() } } diff --git a/Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift b/Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift deleted file mode 100644 index c6e5da66c..000000000 --- a/Sources/MisticaCommon/Utils/Accessibility/FullCellAccessibilityConfig.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// FullCellAccessibilityConfig.swift -// -// Made with ❤️ by Novum -// -// Copyright © Telefonica. All rights reserved. -// - -import Foundation - -public struct FullCellAccessibilityConfig { - public var accessibilityLabel: String - public let activationAction: (() -> Void)? - - public init(accessibilityLabel: String, activationAction: (() -> Void)? = nil) { - self.accessibilityLabel = accessibilityLabel - self.activationAction = activationAction - } -} diff --git a/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift new file mode 100644 index 000000000..b5e9408e8 --- /dev/null +++ b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift @@ -0,0 +1,21 @@ +// +// AccessibilityListCellInteractiveData.swift +// +// Made with ❤️ by Novum +// +// Copyright © Telefonica. All rights reserved. +// + +import Foundation + +public struct AccessibilityListCellInteractiveData { + public let label: String? + public let action: (() -> Void)? + + public init(label: String? = nil, action: (() -> Void)?) { + self.label = label + self.action = action + } + + public static var `default`: AccessibilityListCellInteractiveData = .init(action: nil) +} diff --git a/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift new file mode 100644 index 000000000..74a2ef28d --- /dev/null +++ b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift @@ -0,0 +1,17 @@ +// +// AccessibilityListCellType.swift +// +// Made with ❤️ by Novum +// +// Copyright © Telefonica. All rights reserved. +// + +import Foundation + +public enum AccessibilityListCellType { + case interactive(AccessibilityListCellInteractiveData) + case informative + case customInformative(String) + + public static var `default`: AccessibilityListCellType = .interactive(.default) +} From e94239ce64d56e7fd03551ca04b179eb0f75019c Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Mon, 2 Sep 2024 12:01:15 +0200 Subject: [PATCH 4/9] Moved method to right place --- .../Components/Lists/ListTableViewCell.swift | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/Mistica/Components/Lists/ListTableViewCell.swift b/Sources/Mistica/Components/Lists/ListTableViewCell.swift index dd474175e..a89efe645 100644 --- a/Sources/Mistica/Components/Lists/ListTableViewCell.swift +++ b/Sources/Mistica/Components/Lists/ListTableViewCell.swift @@ -34,21 +34,6 @@ open class ListTableViewCell: UITableViewCell { } } - func accessibilityTypeUpdated() { - switch accessibilityType { - case .interactive(let accessibilityInteractiveData): - isAccessibilityElement = true - accessibilityLabel = accessibilityInteractiveData.label ?? defaultAccessibilityLabel - accessibilityActivationAction = accessibilityInteractiveData.action - case .informative: - isAccessibilityElement = false - accessibilityLabel = nil - case .customInformative(let accessibilityText): - isAccessibilityElement = true - accessibilityLabel = accessibilityText - } - } - // MARK: Initializers override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { @@ -159,4 +144,19 @@ private extension ListTableViewCell { return listCellContentView.cellBorderView } } + + func accessibilityTypeUpdated() { + switch accessibilityType { + case .interactive(let accessibilityInteractiveData): + isAccessibilityElement = true + accessibilityLabel = accessibilityInteractiveData.label ?? defaultAccessibilityLabel + accessibilityActivationAction = accessibilityInteractiveData.action + case .informative: + isAccessibilityElement = false + accessibilityLabel = nil + case .customInformative(let accessibilityText): + isAccessibilityElement = true + accessibilityLabel = accessibilityText + } + } } From a8c79eab17cfcec7cd792ffceb55d6dd3c86a9df Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Fri, 6 Sep 2024 11:51:04 +0200 Subject: [PATCH 5/9] Added double interaction accessibility type Added comments to help understanding this new feature --- .../UICatalogListsViewController.swift | 3 ++ .../Internals/CellCenterSectionView.swift | 11 ++++++ .../Lists/ListCellContentView.swift | 34 ++++++++++++++++++- .../Components/Lists/ListTableViewCell.swift | 24 +++++++++++-- .../ListCells/AccessibilityListCellType.swift | 1 + 5 files changed, 70 insertions(+), 3 deletions(-) diff --git a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift index 6c74bffe4..329323c7f 100644 --- a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift +++ b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift @@ -84,6 +84,7 @@ class UICatalogListsViewController: UITableViewController { cell.segmentedControl.insertSegment(withTitle: "Informative", at: 1, animated: false) cell.segmentedControl.insertSegment(withTitle: "Custom informative", at: 2, animated: false) cell.segmentedControl.insertSegment(withTitle: "Interactive", at: 3, animated: false) + cell.segmentedControl.insertSegment(withTitle: "Double interaction", at: 4, animated: false) cell.segmentedControl.selectedSegmentIndex = 0 return cell }() @@ -214,6 +215,8 @@ extension UICatalogListsViewController { self?.present(alertController, animated: true) } sampleVC.accessibilityType = .interactive(accessibilityInteractiveData) + case 4: + sampleVC.accessibilityType = .doubleInteraction(.default) default: break } diff --git a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift index 142ac5257..513b7fb75 100644 --- a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift +++ b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift @@ -13,6 +13,8 @@ protocol ListCellContentViewDelegate { } class CellCenterSectionView: UIStackView { + var accessibilityActivationAction: (() -> Void)? + var headlineView: AccessibleTextualView? { didSet { oldValue?.removeFromSuperview() @@ -78,6 +80,15 @@ class CellCenterSectionView: UIStackView { } } + override public func accessibilityActivate() -> Bool { + guard let accessibilityActivationAction else { + return false + } + + accessibilityActivationAction() + return true + } + func didSetTextToSubtitleLabel() { if !hasSubtitleText { subtitleLabel.removeFromSuperview() diff --git a/Sources/Mistica/Components/Lists/ListCellContentView.swift b/Sources/Mistica/Components/Lists/ListCellContentView.swift index f0be4dfad..537bf6116 100644 --- a/Sources/Mistica/Components/Lists/ListCellContentView.swift +++ b/Sources/Mistica/Components/Lists/ListCellContentView.swift @@ -18,6 +18,27 @@ protocol ListCellContentTableViewDelegate { open class ListCellContentView: UIView { // MARK: Accessibility properties + var accessibilityType: AccessibilityListCellType = .default { + didSet { + if case .doubleInteraction(let accessibilityInteractiveData) = accessibilityType { + // If double interaction accessibility, make centerSection accessible to be focusable (isAccessibilityElement = true) + centerSection.isAccessibilityElement = true + // Set center section label to the provided one (or the default one if not provided) + centerSection.accessibilityLabel = accessibilityInteractiveData.label ?? defaultAccessibilityLabel + // Set accessibility activation action to be executed on center section double tap + centerSection.accessibilityActivationAction = accessibilityInteractiveData.action + } else { + // If any other accessibility type, it's managed in the superview (ListTableViewCell) + centerSection.isAccessibilityElement = false + centerSection.accessibilityLabel = nil + centerSection.accessibilityActivationAction = nil + } + updateAccessibilityElements() + } + } + + // Default accessibilityLabel using the order specified in the Figma spec: + // https://www.figma.com/design/Be8QB9onmHunKCCAkIBAVr/%F0%9F%94%B8-Lists-Specs?node-id=0-1&node-type=CANVAS&t=jgG9X5qKokaMwJjm-0 var defaultAccessibilityLabel: String { let titleText = titleAccessibilityLabel ?? titleAttributedText?.string ?? title let subtitleText = subtitleAccessibilityLabel ?? subtitleAttributedText?.string ?? subtitle @@ -427,6 +448,17 @@ private extension ListCellContentView { } func updateAccessibilityElements() { - accessibilityElements = [centerSection.titleLabel, headlineView as Any, centerSection.subtitleLabel, centerSection.detailLabel].compactMap { $0 } + switch accessibilityType { + case .informative: + // Set accessibility order following Figma spec: + // https://www.figma.com/design/Be8QB9onmHunKCCAkIBAVr/%F0%9F%94%B8-Lists-Specs?node-id=0-1&node-type=CANVAS&t=jgG9X5qKokaMwJjm-0 + accessibilityElements = [centerSection.titleLabel, headlineView as Any, centerSection.subtitleLabel, centerSection.detailLabel, controlView as Any].compactMap { $0 } + case .doubleInteraction: + // If double interaction, just two elements: center section and right section + accessibilityElements = [centerSection, controlView as Any].compactMap { $0 } + case .interactive, .customInformative: + accessibilityElements = [] + } + } } diff --git a/Sources/Mistica/Components/Lists/ListTableViewCell.swift b/Sources/Mistica/Components/Lists/ListTableViewCell.swift index a89efe645..10fc6ecda 100644 --- a/Sources/Mistica/Components/Lists/ListTableViewCell.swift +++ b/Sources/Mistica/Components/Lists/ListTableViewCell.swift @@ -12,7 +12,6 @@ import UIKit open class ListTableViewCell: UITableViewCell { public var listCellContentView = ListCellContentView() private lazy var cellSeparatorView = SeparatorView(axis: .horizontal) - private var accessibilityActivationAction: (() -> Void)? public var isCellSeparatorHidden: Bool = true { didSet { @@ -28,6 +27,16 @@ open class ListTableViewCell: UITableViewCell { listCellContentView.defaultAccessibilityLabel } + /// Cell accessibility type. + /// - Possible values: + /// - .interactive: Interactive cell (e.g: navigates when tap). Whole cell will be focused. Parameter: AccessibilityListCellInteractiveData + /// - AccessibilityListCellInteractiveData.label: Optional label to be read instead of default one + /// - AccessibilityListCellInteractiveData.action: Custom action associated to double tap + /// - .doubleInteraction: Double interaction: (e.g: navigates when tap on cell and a custom button in the controlView). Two elements focused: center view and control view. Parameter: AccessibilityListCellInteractiveData + /// - AccessibilityListCellInteractiveData.label: Optional label to be read instead of default one for main content (center view) + /// - AccessibilityListCellInteractiveData.action: Custom action associated to double tap on main content (center view) + /// - case informative: Informative cell. Each cell element will be focused/read individually + /// - case customInformative(String): Whole cell will be focused/read using the string parameter as accessibility label public var accessibilityType: AccessibilityListCellType = .default { didSet { accessibilityTypeUpdated() @@ -146,16 +155,27 @@ private extension ListTableViewCell { } func accessibilityTypeUpdated() { + listCellContentView.accessibilityType = accessibilityType + switch accessibilityType { case .interactive(let accessibilityInteractiveData): + // Whole cell has to be focused as one block => isAccessibilityElement = true isAccessibilityElement = true + // Set accessibility label to the provided one or the default one if not provided accessibilityLabel = accessibilityInteractiveData.label ?? defaultAccessibilityLabel - accessibilityActivationAction = accessibilityInteractiveData.action + case .doubleInteraction: + // Cell has to have two focusable blocks: center section (main data) and right section (extra element like a button) => isAccessibilityElement = false + // and delegate focus management to subviews + isAccessibilityElement = false + accessibilityLabel = nil case .informative: + // All the cell elements should be focused individually => isAccessibilityElement = false isAccessibilityElement = false accessibilityLabel = nil case .customInformative(let accessibilityText): + // Whole cell has to be focused as one block => isAccessibilityElement = true isAccessibilityElement = true + // Set accessibility label to the provided custom one accessibilityLabel = accessibilityText } } diff --git a/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift index 74a2ef28d..10162570f 100644 --- a/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift +++ b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellType.swift @@ -10,6 +10,7 @@ import Foundation public enum AccessibilityListCellType { case interactive(AccessibilityListCellInteractiveData) + case doubleInteraction(AccessibilityListCellInteractiveData) case informative case customInformative(String) From e6703fadde8291a1bb9d523fad0ea6835536de19 Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Fri, 6 Sep 2024 13:13:58 +0200 Subject: [PATCH 6/9] Updated comment --- Sources/Mistica/Components/Lists/ListTableViewCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Mistica/Components/Lists/ListTableViewCell.swift b/Sources/Mistica/Components/Lists/ListTableViewCell.swift index 10fc6ecda..69149e657 100644 --- a/Sources/Mistica/Components/Lists/ListTableViewCell.swift +++ b/Sources/Mistica/Components/Lists/ListTableViewCell.swift @@ -31,7 +31,7 @@ open class ListTableViewCell: UITableViewCell { /// - Possible values: /// - .interactive: Interactive cell (e.g: navigates when tap). Whole cell will be focused. Parameter: AccessibilityListCellInteractiveData /// - AccessibilityListCellInteractiveData.label: Optional label to be read instead of default one - /// - AccessibilityListCellInteractiveData.action: Custom action associated to double tap + /// - AccessibilityListCellInteractiveData.action: Custom action associated to double tap (e.g: toggle switch) /// - .doubleInteraction: Double interaction: (e.g: navigates when tap on cell and a custom button in the controlView). Two elements focused: center view and control view. Parameter: AccessibilityListCellInteractiveData /// - AccessibilityListCellInteractiveData.label: Optional label to be read instead of default one for main content (center view) /// - AccessibilityListCellInteractiveData.action: Custom action associated to double tap on main content (center view) From ed72b55886d89b5fe1f19aff134b54c8187e176e Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Fri, 6 Sep 2024 14:42:05 +0200 Subject: [PATCH 7/9] Swiftformat --- Sources/Mistica/Components/Lists/ListCellContentView.swift | 1 - Sources/Mistica/Components/Lists/ListTableViewCell.swift | 2 +- Sources/Mistica/Components/Tag/TagView.swift | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Mistica/Components/Lists/ListCellContentView.swift b/Sources/Mistica/Components/Lists/ListCellContentView.swift index 11a813efc..41c94a6f6 100644 --- a/Sources/Mistica/Components/Lists/ListCellContentView.swift +++ b/Sources/Mistica/Components/Lists/ListCellContentView.swift @@ -459,6 +459,5 @@ private extension ListCellContentView { case .interactive, .customInformative: accessibilityElements = [] } - } } diff --git a/Sources/Mistica/Components/Lists/ListTableViewCell.swift b/Sources/Mistica/Components/Lists/ListTableViewCell.swift index 69149e657..f94a83aa2 100644 --- a/Sources/Mistica/Components/Lists/ListTableViewCell.swift +++ b/Sources/Mistica/Components/Lists/ListTableViewCell.swift @@ -120,7 +120,7 @@ open class ListTableViewCell: UITableViewCell { override public func accessibilityActivate() -> Bool { guard case let .interactive(accessibilityInteractiveData) = accessibilityType, - let action = accessibilityInteractiveData.action else { + let action = accessibilityInteractiveData.action else { return false } diff --git a/Sources/Mistica/Components/Tag/TagView.swift b/Sources/Mistica/Components/Tag/TagView.swift index 4c4e12886..8d7473da9 100644 --- a/Sources/Mistica/Components/Tag/TagView.swift +++ b/Sources/Mistica/Components/Tag/TagView.swift @@ -165,7 +165,7 @@ public class TagView: UIView { extension TagView: AccessibleTextualView { public var accessibleText: String? { - return text + text } } From e683233d765ab4a0408377e36a6ae2f5a4ff88c2 Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Tue, 10 Sep 2024 10:44:31 +0200 Subject: [PATCH 8/9] Added custom label for interactive cell in catalog --- .../Mistica/Components/UICatalogListsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift index 329323c7f..a278e6e69 100644 --- a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift +++ b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift @@ -207,7 +207,7 @@ extension UICatalogListsViewController { case 2: sampleVC.accessibilityType = .customInformative("Custom informative label") case 3: - let accessibilityInteractiveData = AccessibilityListCellInteractiveData { [weak self] in + let accessibilityInteractiveData = AccessibilityListCellInteractiveData(label: "Custom action cell. Tap to execute custom action") { [weak self] in let alertController = UIAlertController(title: nil, message: "Custom action", preferredStyle: .alert) let alertAction = UIAlertAction(title: "Accept", style: .cancel) alertController.addAction(alertAction) From 9f3dabe546d2a362fcbac8c769d4acb38cbb8cc5 Mon Sep 17 00:00:00 2001 From: David Hidalgo Date: Tue, 10 Sep 2024 17:04:04 +0200 Subject: [PATCH 9/9] Fixed PR comments Present interactive cell alert in tabbarcontroller to avoid warning --- .../Mistica/Components/UICatalogListsViewController.swift | 2 +- .../Components/Lists/Internals/CellCenterSectionView.swift | 4 ++-- Sources/Mistica/Components/Lists/ListTableViewCell.swift | 6 +++--- .../ListCells/AccessibilityListCellInteractiveData.swift | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift index a278e6e69..bda8515af 100644 --- a/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift +++ b/MisticaCatalog/Source/Catalog/Mistica/Components/UICatalogListsViewController.swift @@ -212,7 +212,7 @@ extension UICatalogListsViewController { let alertAction = UIAlertAction(title: "Accept", style: .cancel) alertController.addAction(alertAction) - self?.present(alertController, animated: true) + self?.tabBarController?.present(alertController, animated: true) } sampleVC.accessibilityType = .interactive(accessibilityInteractiveData) case 4: diff --git a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift index 513b7fb75..c3c56c51d 100644 --- a/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift +++ b/Sources/Mistica/Components/Lists/Internals/CellCenterSectionView.swift @@ -8,7 +8,7 @@ import UIKit -protocol ListCellContentViewDelegate { +protocol ListCellContentViewDelegate: AnyObject { func accessibilityChanged() } @@ -32,7 +32,7 @@ class CellCenterSectionView: UIStackView { lazy var subtitleLabel = IntrinsictHeightLabel() lazy var detailLabel = IntrinsictHeightLabel() - var listCellContentViewDelegate: ListCellContentViewDelegate? + weak var listCellContentViewDelegate: ListCellContentViewDelegate? var titleTextColor: UIColor = .textPrimary { didSet { diff --git a/Sources/Mistica/Components/Lists/ListTableViewCell.swift b/Sources/Mistica/Components/Lists/ListTableViewCell.swift index f94a83aa2..808dcde1c 100644 --- a/Sources/Mistica/Components/Lists/ListTableViewCell.swift +++ b/Sources/Mistica/Components/Lists/ListTableViewCell.swift @@ -157,9 +157,11 @@ private extension ListTableViewCell { func accessibilityTypeUpdated() { listCellContentView.accessibilityType = accessibilityType + // When `isAccessibilityElement = true` then the whole cell is focused as one block + // and when `isAccessibilityElement = false` then all the cell elements would be focused individually + // or just the elements specified in the accessibilityElements property if defined switch accessibilityType { case .interactive(let accessibilityInteractiveData): - // Whole cell has to be focused as one block => isAccessibilityElement = true isAccessibilityElement = true // Set accessibility label to the provided one or the default one if not provided accessibilityLabel = accessibilityInteractiveData.label ?? defaultAccessibilityLabel @@ -169,11 +171,9 @@ private extension ListTableViewCell { isAccessibilityElement = false accessibilityLabel = nil case .informative: - // All the cell elements should be focused individually => isAccessibilityElement = false isAccessibilityElement = false accessibilityLabel = nil case .customInformative(let accessibilityText): - // Whole cell has to be focused as one block => isAccessibilityElement = true isAccessibilityElement = true // Set accessibility label to the provided custom one accessibilityLabel = accessibilityText diff --git a/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift index b5e9408e8..511cdcc31 100644 --- a/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift +++ b/Sources/MisticaCommon/Utils/Accessibility/ListCells/AccessibilityListCellInteractiveData.swift @@ -12,10 +12,10 @@ public struct AccessibilityListCellInteractiveData { public let label: String? public let action: (() -> Void)? - public init(label: String? = nil, action: (() -> Void)?) { + public init(label: String? = nil, action: (() -> Void)? = nil) { self.label = label self.action = action } - public static var `default`: AccessibilityListCellInteractiveData = .init(action: nil) + public static var `default`: AccessibilityListCellInteractiveData = .init() }