From 94fdebab5732dc68977ce7f2d647fb5c40b172aa Mon Sep 17 00:00:00 2001 From: Alasdair Baxter Date: Wed, 1 Dec 2021 11:23:22 +0000 Subject: [PATCH] Add the ability to supply an accessibilityValue to ValueField through TextEditor --- CHANGELOG.md | 4 ++++ Form/TextEditor.swift | 19 +++++++++++++++++++ Form/ValueEditor.swift | 19 ++++++++++++++++++- Form/ValueField.swift | 8 ++++---- FormTests/ValueEditorTests.swift | 18 ++++++++++++++++++ FormTests/ValueFieldTests.swift | 19 +++++++++++++++++++ Package.resolved | 4 ++-- 7 files changed, 84 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c1109c..29cd9d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 3.3.0 + +- Added (backwards compatible) support for supplying an `accessibilityValue` to `ValueField` through `TextEditor`. + # 3.2.0 - Xcode 13.0 compatibility diff --git a/Form/TextEditor.swift b/Form/TextEditor.swift index d7a2ebd..e98683e 100644 --- a/Form/TextEditor.swift +++ b/Form/TextEditor.swift @@ -26,6 +26,13 @@ public protocol TextEditor { /// The current formatted text of value and the index into text where insertions happen, useful for placing cursors etc. var textAndInsertionIndex: (text: String, index: String.Index) { get } + /// The current accessibility value of the edited text. + /// + /// Depending on the kind of value being edited, and the UI surrounding it, + /// it might be desirable to have a slightly different string representation + /// exposed to an assistive technology. Defaults to the value of `text`. + var accessibilityValue: String { get } + mutating func insertCharacter(_ char: Character) mutating func deleteBackward() @@ -39,6 +46,10 @@ public extension TextEditor { return textAndInsertionIndex.text } + var accessibilityValue: String { + return text + } + var insertionIndex: String.Index { return textAndInsertionIndex.index } @@ -84,6 +95,10 @@ public class AnyTextEditor: TextEditor { set { fatalError() } } + public var accessibilityValue: String { + fatalError() + } + public var defaultValue: Value { fatalError() } @@ -172,6 +187,10 @@ private final class _AnyTextEditor: AnyTextEditor: TextEditor { private let isValidCharacter: ((Character) -> Bool) private let valueToText: (Value) -> String + private let valueToAccessibilityValue: (Value) -> String private let _textAndInsertionIndex: (Value) -> (String, String.Index) private let textToValue: (String) -> Value? private let minCharacters: Int @@ -25,16 +26,28 @@ public struct ValueEditor: TextEditor { /// Parameters: /// - defaultValue: The value to use when resetting the editor. /// - valueToText: How to convert a `Value` to the editable text representation part of the value. + /// - valueToAccessibilityValue: How to convert a `Value` into a string representation for the editor's + /// `accessibilityValue`. Defaults to the text returned by `textAndInsertionIndex`. /// - textToValue: How to convert the editable text representation part of value back to a `Value`. /// - isValidCharacter: Whether a character is a valid input to build a value. /// - minCharacters: Min characters of the editable text representation of value, defaults to zero /// - maxCharacters: Max characters of the editable text representation of value, defaults to `.max` /// - textAndInsertionIndex: Format a value for display (adding potential prefix, postfix or other formatting) /// and the index for insertions, useful for placing cursors etc. - public init(value: Value, defaultValue: Value, valueToText: @escaping (Value) -> String, textToValue: @escaping (String) -> Value?, isValidCharacter: @escaping ((Character) -> Bool), minCharacters: Int = 0, maxCharacters: Int = .max, textAndInsertionIndex: @escaping (Value) -> (String, String.Index)) { + public init( + value: Value, + defaultValue: Value, + valueToText: @escaping (Value) -> String, + valueToAccessibilityValue: ((Value) -> String)? = nil, + textToValue: @escaping (String) -> Value?, + isValidCharacter: @escaping ((Character) -> Bool), + minCharacters: Int = 0, maxCharacters: Int = .max, + textAndInsertionIndex: @escaping (Value) -> (String, String.Index) + ) { self.value = value self.defaultValue = defaultValue self.valueToText = valueToText + self.valueToAccessibilityValue = valueToAccessibilityValue ?? { textAndInsertionIndex($0).0 } self.textToValue = textToValue _textAndInsertionIndex = textAndInsertionIndex self.isValidCharacter = isValidCharacter @@ -42,6 +55,10 @@ public struct ValueEditor: TextEditor { self.maxCharacters = maxCharacters } + public var accessibilityValue: String { + valueToAccessibilityValue(value) + } + public var textAndInsertionIndex: (text: String, index: String.Index) { return _textAndInsertionIndex(value) } diff --git a/Form/ValueField.swift b/Form/ValueField.swift index b8dd99c..0d72372 100644 --- a/Form/ValueField.swift +++ b/Form/ValueField.swift @@ -169,11 +169,11 @@ public final class ValueField: UIControl, UIKeyInput { } public override var accessibilityValue: String? { - get { return label.text } - set { - guard let text = newValue else { return } - label.value = text + get { + editor.accessibilityValue } + //swiftlint:disable:next unused_setter_value + set { /* accessibilityValue is always read from the editor. */ } } public override var inputView: UIView? { diff --git a/FormTests/ValueEditorTests.swift b/FormTests/ValueEditorTests.swift index 676bc92..5c4ae9f 100644 --- a/FormTests/ValueEditorTests.swift +++ b/FormTests/ValueEditorTests.swift @@ -87,4 +87,22 @@ class ValueEditorTests: XCTestCase { test(editor, "1234567890", "12345", 0) test(editor, "123456R3", "3", 0) } + + func testComputesCorrectAccessibilityValue() { + let defaultEditor = ValueEditor(value: "test") + let customEditor = ValueEditor( + value: "test", + defaultValue: "", + valueToText: { $0 }, + valueToAccessibilityValue: { $0 + " modified" }, + textToValue: { $0 }, + isValidCharacter: { _ in true }, + minCharacters: 0, + maxCharacters: .max, + textAndInsertionIndex: { ($0, $0.endIndex) } + ) + + XCTAssertEqual(defaultEditor.accessibilityValue, defaultEditor.text) + XCTAssertEqual(customEditor.accessibilityValue, "test modified") + } } diff --git a/FormTests/ValueFieldTests.swift b/FormTests/ValueFieldTests.swift index 3cb2e04..d49a51c 100644 --- a/FormTests/ValueFieldTests.swift +++ b/FormTests/ValueFieldTests.swift @@ -153,4 +153,23 @@ class ValueFieldTests: XCTestCase { wait(for: [fieldBecameFirstResponder], timeout: 1) } + + func testUsesEditorAccessibilityValue() { + let expectedAccessibilityValue = "accessibility value" + let editor = ValueEditor( + value: "", + defaultValue: "", + valueToText: { $0 }, + valueToAccessibilityValue: { _ in expectedAccessibilityValue }, + textToValue: { $0 }, + isValidCharacter: { _ in true }, + minCharacters: 0, + maxCharacters: .max, + textAndInsertionIndex: { ($0, $0.endIndex) } + ) + + let field = ValueField(value: "", editor: editor) + + XCTAssertEqual(field.accessibilityValue, expectedAccessibilityValue) + } } diff --git a/Package.resolved b/Package.resolved index 0e58142..dfe0701 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/izettle/Flow.git", "state": { "branch": null, - "revision": "0563b5bc4b3a5a4869f9d7ff498a8de5031e3dc5", - "version": "1.8.3" + "revision": "3bc56e13c29cfbcc77cdae6193bbad697ee95799", + "version": "1.10.0" } } ]