From 2d9d1efee4d234906809527b7a6d4254cbbde764 Mon Sep 17 00:00:00 2001 From: Ivan Koshkin Date: Fri, 16 Dec 2022 19:52:49 +0100 Subject: [PATCH] Easy accessibility configuration for views was added AccessibilitySetting --- Sources/CoreUI/UI/UI+Accessibility.swift | 44 ++++++++++++++ Sources/CoreUI/UI/UI+Button.swift | 24 +++++--- Sources/CoreUI/UI/UI+ImageView.swift | 3 + Sources/CoreUI/UI/UI+Label.swift | 6 ++ Sources/CoreUI/UI/UI+Stack.swift | 13 +++- Sources/CoreUI/UI/UI+TextView.swift | 3 + Sources/CoreUI/UI/UI+View.swift | 40 ++++++++++-- Sources/CoreUI/UI/UIView+accessibility.swift | 13 ++++ Tests/CoreUITests/CoreUITests.swift | 64 ++++++++++++++++++++ 9 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 Sources/CoreUI/UI/UI+Accessibility.swift create mode 100644 Sources/CoreUI/UI/UIView+accessibility.swift diff --git a/Sources/CoreUI/UI/UI+Accessibility.swift b/Sources/CoreUI/UI/UI+Accessibility.swift new file mode 100644 index 0000000..cf6ee54 --- /dev/null +++ b/Sources/CoreUI/UI/UI+Accessibility.swift @@ -0,0 +1,44 @@ +import UIKit + +public extension UI { + enum AccessibilitySetting { + case isElement(Bool) + case identifier(String) + case label(String) + case hint(String) + case value(String) + case traits(UIAccessibilityTraits) + case containerType(UIAccessibilityContainerType) + case elements([Any]) + case elementsHidden(Bool) + case shouldGroupChildren(Bool) + } +} + +internal extension UI.AccessibilitySetting { + func apply(to view: UIView) { + switch self { + case .isElement(let isElement): + view.isAccessibilityElement = isElement + case .identifier(let identifier): + view.accessibilityIdentifier = identifier + case .label(let label): + view.accessibilityLabel = label + case .hint(let hint): + view.accessibilityHint = hint + case .value(let value): + view.accessibilityValue = value + case .traits(let traits): + view.accessibilityTraits = traits + case .containerType(let type): + view.accessibilityContainerType = type + case .elementsHidden(let isHidden): + view.accessibilityElementsHidden = isHidden + case .elements(let elements): + view.accessibilityElements = elements + case .shouldGroupChildren(let shouldGroup): + view.shouldGroupAccessibilityChildren = shouldGroup + } + } +} + diff --git a/Sources/CoreUI/UI/UI+Button.swift b/Sources/CoreUI/UI/UI+Button.swift index 5a1cde1..f44b946 100644 --- a/Sources/CoreUI/UI/UI+Button.swift +++ b/Sources/CoreUI/UI/UI+Button.swift @@ -15,6 +15,7 @@ public extension UI { isSelected: Bool = false, width: CGFloat? = nil, height: CGFloat? = nil, + accessibility additionalAccessibilitySettings: [AccessibilitySetting] = [], apply: ((UIButton) -> Void)? = nil ) -> UIButton { let button = View(backgroundColor: backgroundColor, width: width, height: height) as UIButton @@ -26,6 +27,7 @@ public extension UI { if let title = title { button.setTitle(title, for: .normal) + button.accessibility(.label(title)) } if let image = image { @@ -45,7 +47,10 @@ public extension UI { } button.isSelected = isSelected - + + button.accessibility(.isElement(true), .shouldGroupChildren(true), .traits(.button)) + button.accessibility(additionalAccessibilitySettings) + apply?(button) return button @@ -59,12 +64,13 @@ public extension UI { isSelected: Bool = false, width: CGFloat? = nil, height: CGFloat? = nil, + accessibility additionalAccessibilitySettings: [AccessibilitySetting] = [], apply: ((UIButton) -> Void)? = nil ) -> UIButton { let button = View(backgroundColor: backgroundColor, width: width, height: height) as UIButton button.setAttributedTitle(attributedTitle, for: .normal) - + if let image = image { button.setImage(image, for: .normal) } @@ -79,16 +85,14 @@ public extension UI { button.isSelected = isSelected - apply?(button) - - button.isAccessibilityElement = true - button.shouldGroupAccessibilityChildren = true - button.accessibilityTraits = .button - button.accessibilityLabel = attributedTitle.string - button.titleLabel?.adjustsFontForContentSizeCategory = true button.adjustsImageSizeForAccessibilityContentSizeCategory = true - + + button.accessibility(.isElement(true), .shouldGroupChildren(true), .traits(.button), .label(attributedTitle.string)) + button.accessibility(additionalAccessibilitySettings) + + apply?(button) + return button } diff --git a/Sources/CoreUI/UI/UI+ImageView.swift b/Sources/CoreUI/UI/UI+ImageView.swift index 23cfa05..c80d64f 100644 --- a/Sources/CoreUI/UI/UI+ImageView.swift +++ b/Sources/CoreUI/UI/UI+ImageView.swift @@ -13,6 +13,7 @@ public extension UI { cornerRadius: CGFloat? = nil, contentMode: UIView.ContentMode = .scaleAspectFill, clipsToBounds: Bool = true, + accessibility accessibilitySettings: [AccessibilitySetting] = [], apply: ((UIImageView) -> Void)? = nil ) -> UIImageView { let imageView = UIImageView() @@ -53,6 +54,8 @@ public extension UI { imageView.layer.cornerRadius = cornerRadius } + imageView.accessibility(accessibilitySettings) + var constraints: [NSLayoutConstraint] = [] if let width = width { diff --git a/Sources/CoreUI/UI/UI+Label.swift b/Sources/CoreUI/UI/UI+Label.swift index dcb5566..a280bad 100644 --- a/Sources/CoreUI/UI/UI+Label.swift +++ b/Sources/CoreUI/UI/UI+Label.swift @@ -11,6 +11,7 @@ public extension UI { lineBreakMode: NSLineBreakMode = .byTruncatingTail, minWidth: CGFloat? = nil, minHeight: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], apply: ((UILabel) -> Void)? = nil ) -> UILabel { let label = UILabel() @@ -26,6 +27,8 @@ public extension UI { label.text = text } + label.accessibility(accessibilitySettings) + var constraints: [NSLayoutConstraint] = [] if let width = minWidth { @@ -50,6 +53,7 @@ public extension UI { lineBreakMode: NSLineBreakMode = .byTruncatingTail, minWidth: CGFloat? = nil, minHeight: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], apply: ((UILabel) -> Void)? = nil ) -> UILabel { let label = UILabel() @@ -60,6 +64,8 @@ public extension UI { label.adjustsFontForContentSizeCategory = true label.attributedText = attributedText + + label.accessibility(accessibilitySettings) var constraints: [NSLayoutConstraint] = [] diff --git a/Sources/CoreUI/UI/UI+Stack.swift b/Sources/CoreUI/UI/UI+Stack.swift index 4530638..78ab517 100644 --- a/Sources/CoreUI/UI/UI+Stack.swift +++ b/Sources/CoreUI/UI/UI+Stack.swift @@ -3,13 +3,14 @@ import UIKit // swiftlint:disable identifier_name type_body_length // swiftlint:disable:next type_name public extension UI { - + private static func Stack(axis: NSLayoutConstraint.Axis, spacing: CGFloat, distribution: UIStackView.Distribution, alignment: UIStackView.Alignment, backgroundColor: UIColor?, isInteractionEnabled: Bool?, + accessibility accessibilitySettings: [AccessibilitySetting], _ subviews: [UIView?] = [], apply: ((UIStackView) -> Void)? = nil ) -> UIStackView { @@ -25,6 +26,8 @@ public extension UI { stack.isUserInteractionEnabled = isInteractionEnabled } + stack.accessibility(accessibilitySettings) + if #available(iOS 14.0, *) { if let backgroundColor = backgroundColor { stack.backgroundColor = backgroundColor @@ -42,6 +45,7 @@ public extension UI { alignment: UIStackView.Alignment = .fill, backgroundColor: UIColor? = nil, isInteractionEnabled: Bool? = nil, + accessibility: [AccessibilitySetting] = [], _ subviews: [UIView?] = [], apply: ((UIStackView) -> Void)? = nil ) -> UIStackView { @@ -51,6 +55,7 @@ public extension UI { alignment: alignment, backgroundColor: backgroundColor, isInteractionEnabled: isInteractionEnabled, + accessibility: accessibility, subviews, apply: apply ) @@ -62,6 +67,7 @@ public extension UI { alignment: UIStackView.Alignment = .fill, backgroundColor: UIColor? = nil, isInteractionEnabled: Bool? = nil, + accessibility: [AccessibilitySetting] = [], _ subviews: UIView?..., apply: ((UIStackView) -> Void)? = nil ) -> UIStackView { @@ -70,6 +76,7 @@ public extension UI { alignment: alignment, backgroundColor: backgroundColor, isInteractionEnabled: isInteractionEnabled, + accessibility: accessibility, subviews, apply: apply ) @@ -81,6 +88,7 @@ public extension UI { alignment: UIStackView.Alignment = .fill, backgroundColor: UIColor? = nil, isInteractionEnabled: Bool? = nil, + accessibility: [AccessibilitySetting] = [], _ subviews: [UIView?] = [], apply: ((UIStackView) -> Void)? = nil ) -> UIStackView { @@ -90,6 +98,7 @@ public extension UI { alignment: alignment, backgroundColor: backgroundColor, isInteractionEnabled: isInteractionEnabled, + accessibility: accessibility, subviews, apply: apply ) @@ -101,6 +110,7 @@ public extension UI { alignment: UIStackView.Alignment = .fill, backgroundColor: UIColor? = nil, isInteractionEnabled: Bool? = nil, + accessibility: [AccessibilitySetting] = [], _ subviews: UIView?..., apply: ((UIStackView) -> Void)? = nil ) -> UIStackView { @@ -109,6 +119,7 @@ public extension UI { alignment: alignment, backgroundColor: backgroundColor, isInteractionEnabled: isInteractionEnabled, + accessibility: accessibility, subviews, apply: apply ) diff --git a/Sources/CoreUI/UI/UI+TextView.swift b/Sources/CoreUI/UI/UI+TextView.swift index 1fd946d..f7354b8 100644 --- a/Sources/CoreUI/UI/UI+TextView.swift +++ b/Sources/CoreUI/UI/UI+TextView.swift @@ -9,6 +9,7 @@ public extension UI { font: UIFont? = nil, textColor: UIColor? = nil, linkTextAttributes: [NSAttributedString.Key: Any]? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], apply: ((UITextView) -> Void)? = nil ) -> UITextView { let textView = UITextView() @@ -42,6 +43,8 @@ public extension UI { if let textColor = textColor { textView.textColor = textColor } + + textView.accessibility(accessibilitySettings) apply?(textView) diff --git a/Sources/CoreUI/UI/UI+View.swift b/Sources/CoreUI/UI/UI+View.swift index f8b0159..67353c3 100644 --- a/Sources/CoreUI/UI/UI+View.swift +++ b/Sources/CoreUI/UI/UI+View.swift @@ -10,6 +10,7 @@ public extension UI { minWidth: CGFloat? = nil, minHeight: CGFloat? = nil, cornerRadius: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], _ subviews: [UIView?], apply: ((ViewType) -> Void)? = nil ) -> ViewType { @@ -29,6 +30,8 @@ public extension UI { view.addSubview(subview) } + view.accessibility(accessibilitySettings) + var constraints: [NSLayoutConstraint] = [] if let width = width { @@ -61,6 +64,7 @@ public extension UI { minWidth: CGFloat? = nil, minHeight: CGFloat? = nil, cornerRadius: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], _ subviews: UIView?..., apply: ((ViewType) -> Void)? = nil ) -> ViewType { @@ -70,6 +74,7 @@ public extension UI { minWidth: minWidth, minHeight: minHeight, cornerRadius: cornerRadius, + accessibility: accessibilitySettings, subviews, apply: apply) } @@ -82,11 +87,20 @@ public extension UI { height: CGFloat? = nil, minHeight: CGFloat? = nil, cornerRadius: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], _ subview: UIView, _ anchors: LayoutAnchor..., apply: ((ViewType) -> Void)? = nil ) -> ViewType { - View(backgroundColor: backgroundColor, width: width, height: height, minHeight: minHeight, cornerRadius: cornerRadius, subview, anchors, apply: apply) + View(backgroundColor: backgroundColor, + width: width, + height: height, + minHeight: minHeight, + cornerRadius: cornerRadius, + accessibility: accessibilitySettings, + subview, + anchors, + apply: apply) } static func View(backgroundColor: UIColor? = nil, @@ -94,11 +108,17 @@ public extension UI { height: CGFloat? = nil, minHeight: CGFloat? = nil, cornerRadius: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], _ subview: UIView, _ anchors: [LayoutAnchor], apply: ((ViewType) -> Void)? = nil ) -> ViewType { - let view = View(backgroundColor: backgroundColor, width: width, height: height, minHeight: minHeight, cornerRadius: cornerRadius) as ViewType + let view = View(backgroundColor: backgroundColor, + width: width, + height: height, + minHeight: minHeight, + cornerRadius: cornerRadius, + accessibility: accessibilitySettings) as ViewType view.addSubview(subview, anchors) apply?(view) return view @@ -115,19 +135,31 @@ public extension UI { static func Control(backgroundColor: UIColor? = nil, width: CGFloat? = nil, height: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], _ subviews: [UIView], apply: ((UIControl) -> Void)? = nil ) -> UIControl { - View(backgroundColor: backgroundColor, width: width, height: height, subviews, apply: apply) + View(backgroundColor: backgroundColor, + width: width, + height: height, + accessibility: accessibilitySettings, + subviews, + apply: apply) } static func Control(backgroundColor: UIColor? = nil, width: CGFloat? = nil, height: CGFloat? = nil, + accessibility accessibilitySettings: [AccessibilitySetting] = [], _ subviews: UIView..., apply: ((UIControl) -> Void)? = nil ) -> UIControl { - View(backgroundColor: backgroundColor, width: width, height: height, subviews, apply: apply) + View(backgroundColor: backgroundColor, + width: width, + height: height, + accessibility: accessibilitySettings, + subviews, + apply: apply) } } diff --git a/Sources/CoreUI/UI/UIView+accessibility.swift b/Sources/CoreUI/UI/UIView+accessibility.swift new file mode 100644 index 0000000..cd8dfe4 --- /dev/null +++ b/Sources/CoreUI/UI/UIView+accessibility.swift @@ -0,0 +1,13 @@ +import UIKit + +public extension UIView { + func accessibility(_ settings: [UI.AccessibilitySetting]) { + for setting in settings { + setting.apply(to: self) + } + } + + func accessibility(_ settings: UI.AccessibilitySetting...) { + accessibility(settings) + } +} diff --git a/Tests/CoreUITests/CoreUITests.swift b/Tests/CoreUITests/CoreUITests.swift index bff1213..3918212 100644 --- a/Tests/CoreUITests/CoreUITests.swift +++ b/Tests/CoreUITests/CoreUITests.swift @@ -19,4 +19,68 @@ final class CoreUITests: XCTestCase { ).isKind(of: UIStackView.self) ) } + + func testAccessibility() { + let accID = "Identifier" + let accLabel = "Label" + let accHint = "Hint" + let accValue = "Value" + + let accessibilitySettings: [UI.AccessibilitySetting] = [ + .isElement(true), + .identifier(accID), + .label(accLabel), + .hint(accHint), + .value(accValue), + .traits(.button), + .containerType(.list), + .elements([]), + .elementsHidden(true), + .shouldGroupChildren(true), + ] + + let v = UI.View() + + XCTAssertEqual(v.isAccessibilityElement, false) + XCTAssertNil(v.accessibilityIdentifier) + XCTAssertNil(v.accessibilityLabel) + XCTAssertNil(v.accessibilityHint) + XCTAssertNil(v.accessibilityValue) + XCTAssertEqual(v.accessibilityTraits, UIAccessibilityTraits.none) + XCTAssertNil(v.accessibilityElements) + XCTAssertEqual(v.accessibilityElementsHidden, false) + XCTAssertEqual(v.shouldGroupAccessibilityChildren, false) + + v.accessibility(accessibilitySettings) + + XCTAssertEqual(v.isAccessibilityElement, true) + XCTAssertEqual(v.accessibilityIdentifier, accID) + XCTAssertEqual(v.accessibilityLabel, accLabel) + XCTAssertEqual(v.accessibilityHint, accHint) + XCTAssertEqual(v.accessibilityValue, accValue) + XCTAssertEqual(v.accessibilityTraits, UIAccessibilityTraits.button) + XCTAssertNotNil(v.accessibilityElements) + XCTAssertEqual(v.accessibilityElementsHidden, true) + XCTAssertEqual(v.shouldGroupAccessibilityChildren, true) + + + } + + func testPreconfiguredAccessiblityForButton() { + let buttonText = "Button" + + let b = UI.Button(buttonText) + + XCTAssertEqual(b.isAccessibilityElement, true) + XCTAssertEqual(b.shouldGroupAccessibilityChildren, true) + XCTAssertEqual(b.accessibilityTraits, .button) + XCTAssertEqual(b.accessibilityLabel, buttonText) + + let v = UI.View() + + XCTAssertEqual(v.isAccessibilityElement, false) + XCTAssertEqual(v.shouldGroupAccessibilityChildren, false) + XCTAssertEqual(v.accessibilityTraits, .none) + XCTAssertNil(v.accessibilityLabel) + } }