From 024194e7e56a9c8a6bff0ebb75a9d6d9003febbb Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Tue, 12 Mar 2024 18:24:48 +0530 Subject: [PATCH] Updated accessibility, add addressed review comments --- VDS.xcodeproj/project.pbxproj | 4 + VDS/Components/Pagination/Pagination.swift | 82 ++++++++++++------- .../Pagination/PaginationCellItem.swift | 1 + .../Pagination/PaginationCollectionView.swift | 66 +++++++++++++++ 4 files changed, 122 insertions(+), 31 deletions(-) create mode 100644 VDS/Components/Pagination/PaginationCollectionView.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 0b263712..38a79f1e 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */; }; + 71ACE89C2BA0451200FB6ADC /* PaginationCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89B2BA0451200FB6ADC /* PaginationCollectionView.swift */; }; 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B23C2C2B91FA690027F7D9 /* Pagination.swift */; }; 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */; }; 71BFA70A2B7F70E6000DCE33 /* DropShadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */; }; @@ -192,6 +193,7 @@ 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; sourceTree = ""; }; + 71ACE89B2BA0451200FB6ADC /* PaginationCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationCollectionView.swift; sourceTree = ""; }; 71B23C2C2B91FA690027F7D9 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = ""; }; 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = PaginationChangeLog.txt; sourceTree = ""; }; 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropShadowable.swift; sourceTree = ""; }; @@ -404,6 +406,7 @@ isa = PBXGroup; children = ( 71B23C2C2B91FA690027F7D9 /* Pagination.swift */, + 71ACE89B2BA0451200FB6ADC /* PaginationCollectionView.swift */, 71FC86D92B96F44C00700965 /* PaginationButton.swift */, 71FC86DB2B96F4C800700965 /* PaginationCellItem.swift */, 71FC86E32B9841AC00700965 /* PaginationFlowLayout.swift */, @@ -1056,6 +1059,7 @@ EAC925842911C63100091998 /* Colorable.swift in Sources */, EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */, EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */, + 71ACE89C2BA0451200FB6ADC /* PaginationCollectionView.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */, EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */, diff --git a/VDS/Components/Pagination/Pagination.swift b/VDS/Components/Pagination/Pagination.swift index 1f00b949..754935b7 100644 --- a/VDS/Components/Pagination/Pagination.swift +++ b/VDS/Components/Pagination/Pagination.swift @@ -21,24 +21,16 @@ open class Pagination: View { ///Selected page index private var _selectedPageIndex: Int = 0 ///Custom flow layout defined for the Pagination - private let flowLayout = PaginationFlowLayout() + private var flowLayout: PaginationFlowLayout { + guard let flowLayout = collectionContainerView.collectionView.collectionViewLayout as? PaginationFlowLayout else { fatalError("Flow layout should be PaginationFlowLayout class") } + return flowLayout + } ///A root view for the pagination private let containerView: View = View().with { $0.translatesAutoresizingMaskIntoConstraints = false } - ///Collectionview to render pagination indexes - private lazy var collectionView: UICollectionView = { - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) - collectionView.isScrollEnabled = false - collectionView.translatesAutoresizingMaskIntoConstraints = false - collectionView.delegate = self - collectionView.dataSource = self - collectionView.showsHorizontalScrollIndicator = false - collectionView.showsVerticalScrollIndicator = false - collectionView.register(PaginationCellItem.self, forCellWithReuseIdentifier: PaginationCellItem.identifier) - collectionView.backgroundColor = .clear - return collectionView - }() + ///Container view to hold collectionview to render pagination indexes + private let collectionContainerView = PaginationCollectionView() //-------------------------------------------------- // MARK: - Public Properties @@ -86,24 +78,32 @@ open class Pagination: View { super.initialSetup() addSubview(containerView) - containerView.pinToSuperView() - containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 288).activate() + containerView + .pinTop() + .pinBottom() + containerView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor).activate() + trailingAnchor.constraint(greaterThanOrEqualTo: containerView.trailingAnchor).activate() + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + containerView.widthAnchor.constraint(equalToConstant: 288).activate() containerView.heightAnchor.constraint(equalToConstant: 44).activate() containerView.addSubview(previousButton) - containerView.addSubview(collectionView) + containerView.addSubview(collectionContainerView) containerView.addSubview(nextButton) previousButton .pinTop() .pinBottom() .pinLeading() - previousButton.trailingAnchor.constraint(greaterThanOrEqualTo: collectionView.leadingAnchor).activate() - collectionView.heightAnchor.constraint(equalToConstant: VDSLayout.Spacing.space4X.value).activate() - collectionView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() - collectionView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - collectionView.trailingAnchor.constraint(greaterThanOrEqualTo: nextButton.leadingAnchor).activate() - collectionViewWidthAnchor = collectionView.widthAnchor.constraint(equalToConstant: 92) + + previousButton.trailingAnchor.constraint(greaterThanOrEqualTo: collectionContainerView.leadingAnchor).activate() + collectionContainerView.heightAnchor.constraint(equalToConstant: VDSLayout.Spacing.space4X.value).activate() + collectionContainerView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() + collectionContainerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + collectionContainerView.trailingAnchor.constraint(greaterThanOrEqualTo: nextButton.leadingAnchor).activate() + collectionViewWidthAnchor = collectionContainerView.widthAnchor.constraint(equalToConstant: 92) collectionViewWidthAnchor?.activate() + collectionContainerView.collectionView.delegate = self + collectionContainerView.collectionView.dataSource = self nextButton .pinTop() @@ -117,8 +117,24 @@ open class Pagination: View { flowLayout.$collectionViewWidth .receive(on: RunLoop.main) .sink { [weak self] value in - self?.collectionViewWidthAnchor?.constant = value - }.store(in: &subscribers) + self?.collectionViewWidthAnchor?.constant = value //As cell width is dynamic i.e cell may contain 2 or 3 or 4 charcters. Make sure that all the visible cells are displayed. + }.store(in: &subscribers) + collectionContainerView.onAccessibilityIncrement = { [weak self] in + guard let self else { return } + self.selectedPage = max(0, self.selectedPage + 1) + } + collectionContainerView.onAccessibilityDecrement = { [weak self] in + guard let self else { return } + self.selectedPage = max(0, self.selectedPage - 1) + } + } + + ///Updating the accessiblity values i.e elements, label, value other items for the component. + open override func updateAccessibility() { + super.updateAccessibility() + accessibilityElements = [previousButton, collectionContainerView, nextButton] + collectionContainerView.accessibilityLabel = "Pagination containing \(total) pages" + collectionContainerView.accessibilityValue = "Page \(selectedPage) of \(total) selected" } /// Used to make changes to the View based off a change events or from local properties. @@ -126,7 +142,7 @@ open class Pagination: View { super.updateView() nextButton.surface = surface previousButton.surface = surface - collectionView.reloadData() + collectionContainerView.collectionView.reloadData() } //-------------------------------------------------- @@ -137,21 +153,25 @@ open class Pagination: View { let isNextAction = sender == nextButton _selectedPageIndex = if isNextAction { _selectedPageIndex + 1 } else { _selectedPageIndex - 1 } updateSelection() + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in + guard let self else { return } + UIAccessibility.post(notification: .announcement, argument: "Page \(self.selectedPage) of \(self.total) selected") + } } ///Refreshing the UI based on the selected page private func updateSelection() { guard _selectedPageIndex < total else { return } //Need to make selected page as second element so scrolling previous index of the selected page to left - collectionView.scrollToItem(at: IndexPath(row: max(_selectedPageIndex - 1, 0), section: 0), at: .left, animated: false) + collectionContainerView.collectionView.scrollToItem(at: IndexPath(row: max(_selectedPageIndex - 1, 0), section: 0), at: .left, animated: false) previousButton.isHidden = _selectedPageIndex == 0 nextButton.isHidden = _selectedPageIndex == total - 1 - collectionView.reloadData() + collectionContainerView.collectionView.reloadData() verifyIfMaxDigitChanged() } ///Identifying if there is any change in the digits of upcoming page - func verifyIfMaxDigitChanged() { + private func verifyIfMaxDigitChanged() { let upperLimitPage = _selectedPageIndex + flowLayout.maxNumberOfColumns let upperLimitDigits = upperLimitPage.digitCount //future value digits switch (flowLayout.numberOfColumns, upperLimitDigits) { @@ -163,9 +183,9 @@ open class Pagination: View { if upperLimitDigits != flowLayout.upperLimitDigits { flowLayout.upperLimitDigits = upperLimitDigits flowLayout.invalidateLayout() - collectionView.reloadData() + collectionContainerView.collectionView.reloadData() //Need to make selected page as second element so scrolling previous index of the selected page to left - collectionView.scrollToItem(at: IndexPath(row: max(_selectedPageIndex - 1, 0), section: 0), at: .left, animated: false) + collectionContainerView.collectionView.scrollToItem(at: IndexPath(row: max(_selectedPageIndex - 1, 0), section: 0), at: .left, animated: false) } } } diff --git a/VDS/Components/Pagination/PaginationCellItem.swift b/VDS/Components/Pagination/PaginationCellItem.swift index 68fa7271..68f51142 100644 --- a/VDS/Components/Pagination/PaginationCellItem.swift +++ b/VDS/Components/Pagination/PaginationCellItem.swift @@ -23,6 +23,7 @@ final class PaginationCellItem: UICollectionViewCell { private var indexLabel: Label = Label().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.textAlignment = .center + $0.isAccessibilityElement = false $0.numberOfLines = 1 } diff --git a/VDS/Components/Pagination/PaginationCollectionView.swift b/VDS/Components/Pagination/PaginationCollectionView.swift new file mode 100644 index 00000000..a41c65ef --- /dev/null +++ b/VDS/Components/Pagination/PaginationCollectionView.swift @@ -0,0 +1,66 @@ +// +// PaginationCollectionView.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 12/03/24. +// + +import UIKit + +///PaginationCollectionView is a container view that holds collectionview for displaying page indexes +final class PaginationCollectionView: View { + + //-------------------------------------------------- + // MARK: - Internal Properties + //-------------------------------------------------- + ///Notifies when accessibility increment is happend when user swipes up + var onAccessibilityIncrement: (() -> Void)? + ///Notifies when accessibility decrement is happend when user swipes down + var onAccessibilityDecrement: (() -> Void)? + ///Collectionview to render pagination indexes + lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.isScrollEnabled = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false + collectionView.isAccessibilityElement = true + collectionView.register(PaginationCellItem.self, forCellWithReuseIdentifier: PaginationCellItem.identifier) + collectionView.backgroundColor = .clear + return collectionView + }() + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + ///Custom flow layout defined for the Pagination + private let flowLayout = PaginationFlowLayout() + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + ///Accessibilty traits for the Pagination view + override var accessibilityTraits: UIAccessibilityTraits { + get { [.adjustable] } + set { } + } + + ///Accessibilty increment + override func accessibilityIncrement() { + onAccessibilityIncrement?() + } + + ///Accessibilty decrement + override func accessibilityDecrement() { + onAccessibilityDecrement?() + } + + /// Executed on initialization for this View. + override func setup() { + super.setup() + addSubview(collectionView) + collectionView.pinToSuperView() + isAccessibilityElement = true + accessibilityElements = [collectionView] + } +}