Skip to content

Commit 4eb1125

Browse files
authored
Merge pull request #21 from GoodRequest/feature/inputFields-accessibility
Input Fields Accessibility
2 parents 6cf58a1 + 29d694b commit 4eb1125

File tree

3 files changed

+156
-8
lines changed

3 files changed

+156
-8
lines changed

GoodSwiftUI-Sample/GoodSwiftUI-Sample/Screens/InputFieldSampleView.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ extension InputFieldSampleView {
106106
private var nameInputField: some View {
107107
// Text field
108108
InputField(text: $name, title: "Name", placeholder: "Jožko", hint: "Text is limited to 10 characters")
109+
// Override of default accessibility label
110+
.setAccessibilityLabel("Fill the name")
111+
// Accessibility identifier for UI tests
112+
.setAccessibilityIdentifier("nameTextField")
109113

110114
// "Continue" keyboard action button
111115
.inputFieldTraits(returnKeyType: .continue)
@@ -123,9 +127,6 @@ extension InputFieldSampleView {
123127
// Focus state binding to advance focus from keyboard action button (Continue)
124128
.bindFocusState($focusState, to: .name)
125129
.disabled(!nameEnabled)
126-
127-
// Accessibility identifier for UI tests
128-
.accessibilityIdentifier("nameTextField")
129130
}
130131

131132
private var pinCodeInputField: some View {
@@ -143,6 +144,12 @@ extension InputFieldSampleView {
143144
numpadReturnKeyTitle: "Done",
144145
isSecureTextEntry: true
145146
)
147+
148+
// Setting accessibility labels for eye Button (secureField)
149+
.setEyeButtonAccessibilityLabel(showLabel: "Show PIN", hideLabel: "Hide PIN")
150+
151+
// Accessibility identifier for UI tests
152+
.setAccessibilityIdentifier("pinTextField")
146153

147154
// Custom validation criteria closure
148155
.validationCriteria {
@@ -162,9 +169,6 @@ extension InputFieldSampleView {
162169
.validityGroup($validityGroup)
163170
.bindFocusState($focusState, to: .pin)
164171
.disabled(!passwordEnabled)
165-
166-
// Accessibility identifier for UI tests
167-
.accessibilityIdentifier("pinTextField")
168172
}
169173

170174
private var percentFormattedInputField: some View {
@@ -195,6 +199,8 @@ extension InputFieldSampleView {
195199
hint: nil,
196200
leftView: {
197201
Text("+421 \(password)")
202+
.accessibilityLabel("Predvoľba")
203+
.accessibilityValue("+421 \(password)")
198204
},
199205
rightView: {
200206
Button {
@@ -207,8 +213,8 @@ extension InputFieldSampleView {
207213
}
208214
}
209215
)
216+
.setAccessibilityIdentifier("customViewsInputField")
210217
.alert("Right button alert", isPresented: $showsRightAlert, actions: {})
211-
.accessibilityIdentifier("customViewsInputField")
212218
}
213219

214220
private var validityGroups: some View {

Sources/GRInputField/SwiftUI/InputField.swift

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public struct InputField<LeftView: View, RightView: View>: UIViewRepresentable {
4242
private var resignAction: MainClosure?
4343
private var editingChangedAction: MainClosure?
4444

45+
private var accessibilityIdentifier: String?
46+
private var accessibilityLabel: String?
47+
private var accessibilityValue: String?
48+
private var accessibilityHint: String?
49+
private var showPasswordAccessibilityLabel: String?
50+
private var hidePasswordAccessibilityLabel: String?
51+
4552
// MARK: - Initialization
4653

4754
public init(
@@ -61,6 +68,11 @@ public struct InputField<LeftView: View, RightView: View>: UIViewRepresentable {
6168
self.criteria = { Validator(criteria: [Criterion.alwaysValid]) }
6269
self.leftView = leftView
6370
self.rightView = rightView
71+
72+
self.accessibilityIdentifier = self.title
73+
self.accessibilityLabel = self.title
74+
self.accessibilityValue = self.text
75+
self.accessibilityHint = self.hint
6476
}
6577

6678
@available(iOS 15.0, *)
@@ -240,6 +252,7 @@ public struct InputField<LeftView: View, RightView: View>: UIViewRepresentable {
240252
}
241253

242254
updateValidationState(uiView: uiView, context: context)
255+
updateAccessibility(uiView)
243256
}
244257

245258
public static func dismantleUIView(_ uiView: ValidableInputFieldView, coordinator: Coordinator) {
@@ -355,7 +368,18 @@ public struct InputField<LeftView: View, RightView: View>: UIViewRepresentable {
355368
// (set)self.text = store formatted value back to storage
356369
self.text = formatted
357370
}
358-
371+
372+
// MARK: - Accessibility
373+
374+
func updateAccessibility(_ uiView: ValidableInputFieldView) {
375+
uiView.accessibilityIdentifier = accessibilityIdentifier
376+
uiView.accessibilityLabel = accessibilityLabel
377+
uiView.accessibilityHint = accessibilityHint
378+
uiView.accessibilityValue = accessibilityValue
379+
uiView.showPasswordAccessibilityLabel = showPasswordAccessibilityLabel
380+
uiView.hidePasswordAccessibilityLabel = hidePasswordAccessibilityLabel
381+
}
382+
359383
}
360384

361385
// MARK: - Public modifiers
@@ -436,6 +460,43 @@ public extension InputField {
436460

437461
}
438462

463+
// MARK: - Accessibility
464+
465+
public extension InputField {
466+
467+
func setAccessibilityIdentifier(_ identifier: String) -> Self {
468+
var modifiedSelf = self
469+
modifiedSelf.accessibilityIdentifier = identifier
470+
return modifiedSelf
471+
}
472+
473+
func setAccessibilityLabel(_ label: String) -> Self {
474+
var modifiedSelf = self
475+
modifiedSelf.accessibilityLabel = label
476+
return modifiedSelf
477+
}
478+
479+
func setAccessibilityHint(_ hint: String) -> Self {
480+
var modifiedSelf = self
481+
modifiedSelf.accessibilityHint = hint
482+
return modifiedSelf
483+
}
484+
485+
func setAccessibilityValue(_ value: String) -> Self {
486+
var modifiedSelf = self
487+
modifiedSelf.accessibilityValue = value
488+
return modifiedSelf
489+
}
490+
491+
func setEyeButtonAccessibilityLabel(showLabel: String, hideLabel: String) -> Self {
492+
var modifiedSelf = self
493+
modifiedSelf.showPasswordAccessibilityLabel = showLabel
494+
modifiedSelf.hidePasswordAccessibilityLabel = hideLabel
495+
return modifiedSelf
496+
}
497+
498+
}
499+
439500
// MARK: - View focus extension
440501

441502
@available(iOS 15.0, *)

Sources/GRInputField/UIKit/InputFieldView.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,15 @@ public class InputFieldView: UIView {
8585

8686
private let titleLabel = UILabel().then {
8787
$0.adjustsFontForContentSizeCategory = true
88+
$0.isAccessibilityElement = false
8889
}
8990

9091
private let contentView = UIView()
9192

9293
private let leftContainerImageView = UIImageView().then {
9394
$0.contentMode = .scaleAspectFit
9495
$0.setContentHuggingPriority(.defaultHigh, for: .horizontal)
96+
$0.isAccessibilityElement = false
9597
}
9698

9799
private let textField = UITextField().then {
@@ -116,6 +118,7 @@ public class InputFieldView: UIView {
116118
private let hintLabel = UILabel().then {
117119
$0.adjustsFontForContentSizeCategory = true
118120
$0.numberOfLines = 2
121+
$0.isAccessibilityElement = false
119122
}
120123

121124
// MARK: - Variables
@@ -131,6 +134,7 @@ public class InputFieldView: UIView {
131134
set {
132135
textField.text = newValue
133136
editingChangedSubject.send(newValue)
137+
accessibilityValue = newValue
134138
}
135139
}
136140

@@ -163,6 +167,61 @@ public class InputFieldView: UIView {
163167

164168
private let editingChangedSubject = PassthroughSubject<String, Never>()
165169
private(set) public lazy var editingChangedPublisher = editingChangedSubject.eraseToAnyPublisher()
170+
171+
// MARK: - Accessibility
172+
173+
public override var accessibilityIdentifier: String? {
174+
get {
175+
textField.accessibilityIdentifier
176+
} set {
177+
textField.accessibilityIdentifier = newValue
178+
}
179+
}
180+
181+
public override var accessibilityLabel: String? {
182+
get {
183+
textField.accessibilityLabel
184+
} set {
185+
textField.accessibilityLabel = newValue
186+
}
187+
}
188+
189+
public override var accessibilityHint: String? {
190+
get {
191+
textField.accessibilityHint
192+
} set {
193+
textField.accessibilityHint = newValue
194+
}
195+
}
196+
197+
public override var accessibilityValue: String? {
198+
get {
199+
if isSecureTextEntry {
200+
return textField.isSecureTextEntry ? textField.accessibilityValue : text
201+
} else {
202+
return textField.accessibilityValue
203+
}
204+
} set {
205+
guard !isSecureTextEntry else { return }
206+
207+
textField.accessibilityValue = newValue
208+
}
209+
}
210+
211+
public var showPasswordAccessibilityLabel: String? {
212+
didSet {
213+
guard textField.isSecureTextEntry else { return }
214+
215+
eyeButton.accessibilityLabel = showPasswordAccessibilityLabel
216+
}
217+
}
218+
public var hidePasswordAccessibilityLabel: String? {
219+
didSet {
220+
guard !textField.isSecureTextEntry else { return }
221+
222+
eyeButton.accessibilityLabel = hidePasswordAccessibilityLabel
223+
}
224+
}
166225

167226
// MARK: - Initializer
168227

@@ -189,6 +248,9 @@ private extension InputFieldView {
189248
addSubviews()
190249
setupConstraints()
191250
setupActionHandlers()
251+
252+
// Accessiblity will be handled by the UITextField itself
253+
self.isAccessibilityElement = false
192254
}
193255

194256
func setupAppearance() {
@@ -236,6 +298,8 @@ private extension InputFieldView {
236298

237299
hintLabel.textColor = standardAppearance.enabled?.hintColor
238300
hintLabel.text = hint
301+
hintLabel.isAccessibilityElement = false
302+
accessibilityHint = hint
239303

240304
textField.attributedPlaceholder = NSAttributedString(
241305
string: textField.placeholder ?? C.emptyString,
@@ -252,6 +316,8 @@ private extension InputFieldView {
252316

253317
hintLabel.textColor = standardAppearance.disabled?.hintColor
254318
hintLabel.text = hint
319+
hintLabel.isAccessibilityElement = false
320+
accessibilityHint = hint
255321

256322
textField.attributedPlaceholder = NSAttributedString(
257323
string: textField.placeholder ?? C.emptyString,
@@ -267,6 +333,8 @@ private extension InputFieldView {
267333

268334
hintLabel.textColor = standardAppearance.selected?.hintColor
269335
hintLabel.text = hint
336+
hintLabel.isAccessibilityElement = false
337+
accessibilityHint = hint
270338

271339
textField.attributedPlaceholder = NSAttributedString(
272340
string: textField.placeholder ?? C.emptyString,
@@ -282,6 +350,10 @@ private extension InputFieldView {
282350

283351
hintLabel.textColor = standardAppearance.failed?.hintColor
284352
hintLabel.text = (hint == nil) ? (nil) : (message ?? hint)
353+
354+
hintLabel.isAccessibilityElement = true
355+
accessibilityHint = (hint == nil) ? (nil) : (message ?? hint)
356+
UIAccessibility.post(notification: .layoutChanged, argument: hintLabel)
285357

286358
textField.attributedPlaceholder = NSAttributedString(
287359
string: textField.placeholder ?? C.emptyString,
@@ -399,6 +471,12 @@ private extension InputFieldView {
399471
eyeButton.isHidden = false
400472
eyeButton.setImage(eyeImage, for: .normal)
401473
textField.isSecureTextEntry = isSecure
474+
eyeButton.accessibilityLabel = isSecure
475+
? showPasswordAccessibilityLabel
476+
: hidePasswordAccessibilityLabel
477+
eyeButton.accessibilityIdentifier = isSecure
478+
? "show"
479+
: "hide"
402480
}
403481

404482
func trimWhitespaceIfAllowed() {
@@ -593,6 +671,9 @@ public extension InputFieldView {
593671

594672
/// Text
595673
textField.text = model.text
674+
accessibilityValue = textField.text
675+
accessibilityHint = hint
676+
accessibilityLabel = titleLabel.text
596677
}
597678

598679
func fail(with errorMessage: String?) {

0 commit comments

Comments
 (0)