Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2/2 TextInput accessibilityErrorMessage (VoiceOver, iOS) #35908

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Libraries/Components/TextInput/RCTTextInputViewConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ const RCTTextInputViewConfig = {
allowFontScaling: true,
fontStyle: true,
textTransform: true,
accessibilityErrorMessage: true,
accessibilityInvalid: true,
textAlign: true,
fontFamily: true,
lineHeight: true,
Expand Down
1 change: 1 addition & 0 deletions Libraries/Text/TextInput/RCTBaseTextInputViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ @implementation RCTBaseTextInputViewManager {
RCT_REMAP_VIEW_PROPERTY(autoCorrect, backedTextInputView.autocorrectionType, UITextAutocorrectionType)
RCT_REMAP_VIEW_PROPERTY(contextMenuHidden, backedTextInputView.contextMenuHidden, BOOL)
RCT_REMAP_VIEW_PROPERTY(editable, backedTextInputView.editable, BOOL)
RCT_REMAP_VIEW_PROPERTY(accessibilityErrorMessage, backedTextInputView.accessibilityErrorMessage, NSString)
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, backedTextInputView.enablesReturnKeyAutomatically, BOOL)
RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, backedTextInputView.keyboardAppearance, UIKeyboardAppearance)
RCT_REMAP_VIEW_PROPERTY(placeholder, backedTextInputView.placeholder, NSString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ @implementation RCTTextInputComponentView {
*/
BOOL _comingFromJS;
BOOL _didMoveToWindow;

/*
* A flag that triggers the accessibilityElement.accessibilityValue update and VoiceOver announcement
* to avoid duplicated announcements of accessibilityErrorMessage more info https://bit.ly/3yfUXD8
*/
BOOL _triggerAccessibilityAnnouncement;
BOOL _skipNextAccessibilityAnnouncement;
}

#pragma mark - UIView overrides
Expand All @@ -71,6 +78,8 @@ - (instancetype)initWithFrame:(CGRect)frame
_ignoreNextTextInputCall = NO;
_comingFromJS = NO;
_didMoveToWindow = NO;
_triggerAccessibilityAnnouncement = NO;
_skipNextAccessibilityAnnouncement = NO;
[self addSubview:_backedTextInputView];
}

Expand Down Expand Up @@ -133,6 +142,15 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
_backedTextInputView.editable = newTextInputProps.traits.editable;
}

NSString *newAccessibilityErrorMessage = RCTNSStringFromString(newTextInputProps.accessibilityErrorMessage);
if (newTextInputProps.text != oldTextInputProps.text && [newAccessibilityErrorMessage length] == 0) {
_backedTextInputView.accessibilityValue = RCTNSStringFromString(newTextInputProps.text);
}

if (newTextInputProps.accessibilityErrorMessage != oldTextInputProps.accessibilityErrorMessage) {
[self _setAccessibilityValueWithError:newAccessibilityErrorMessage text:RCTNSStringFromString(newTextInputProps.text)];
}

if (newTextInputProps.traits.enablesReturnKeyAutomatically !=
oldTextInputProps.traits.enablesReturnKeyAutomatically) {
_backedTextInputView.enablesReturnKeyAutomatically = newTextInputProps.traits.enablesReturnKeyAutomatically;
Expand Down Expand Up @@ -236,6 +254,15 @@ - (void)updateState:(State::Shared const &)state oldState:(State::Shared const &
}
}

- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
[super finalizeUpdates:updateMask];
if (_triggerAccessibilityAnnouncement) {
[self announceForAccessibility:_backedTextInputView.accessibilityValue];
_triggerAccessibilityAnnouncement = NO;
}
}

- (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics
{
Expand Down Expand Up @@ -594,6 +621,16 @@ - (void)_setAttributedString:(NSAttributedString *)attributedString
UITextRange *selectedRange = _backedTextInputView.selectedTextRange;
NSInteger oldTextLength = _backedTextInputView.attributedText.string.length;
_backedTextInputView.attributedText = attributedString;

if (_triggerAccessibilityAnnouncement && [_backedTextInputView.accessibilityValue length] != 0) {
[self announceForAccessibility:_backedTextInputView.accessibilityValue];
_triggerAccessibilityAnnouncement = NO;
_skipNextAccessibilityAnnouncement = YES;
} else if (_skipNextAccessibilityAnnouncement) {
NSString *lastChar = [attributedString.string substringFromIndex:[attributedString.string length] - 1];
[self announceForAccessibility:lastChar];
_skipNextAccessibilityAnnouncement = NO;
}
if (selectedRange.empty) {
// Maintaining a cursor position relative to the end of the old text.
NSInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
Expand All @@ -619,6 +656,17 @@ - (void)_setMultiline:(BOOL)multiline
[self addSubview:_backedTextInputView];
}

- (void)_setAccessibilityValueWithError:(NSString *)error text:(NSString *)text
{
if ([error length] != 0) {
_triggerAccessibilityAnnouncement = YES;
_backedTextInputView.accessibilityValue = [NSString stringWithFormat: @"%@ %@", text, error];
} else {
_backedTextInputView.accessibilityValue = text;
_triggerAccessibilityAnnouncement = NO;
}
}

- (BOOL)_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText
{
// When the dictation is running we can't update the attributed text on the backed up text view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ NS_ASSUME_NONNULL_BEGIN
oldLayoutMetrics:(facebook::react::LayoutMetrics const &)oldLayoutMetrics NS_REQUIRES_SUPER;
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask NS_REQUIRES_SUPER;
- (void)prepareForRecycle NS_REQUIRES_SUPER;
- (void)announceForAccessibility:(NSString *)announcement;

/*
* This is a fragment of temporary workaround that we need only temporary and will get rid of soon.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,13 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
[self invalidateLayer];
}

- (void)announceForAccessibility:(NSString*)announcement
{
if ([announcement length] != 0) {
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, announcement);
}
}

fabOnReact marked this conversation as resolved.
Show resolved Hide resolved
- (void)prepareForRecycle
{
[super prepareForRecycle];
Expand Down
1 change: 1 addition & 0 deletions React/Views/UIView+React.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
@property (nonatomic, copy) NSArray<NSDictionary *> *accessibilityActions;
@property (nonatomic, copy) NSDictionary *accessibilityValueInternal;
@property (nonatomic, copy) NSString *accessibilityLanguage;
@property (nonatomic, copy) NSString *accessibilityErrorMessage;

/**
* Used in debugging to get a description of the view hierarchy rooted at
Expand Down
11 changes: 11 additions & 0 deletions React/Views/UIView+React.m
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,17 @@ - (NSString *)accessibilityLanguage
return objc_getAssociatedObject(self, _cmd);
}

- (NSString *)accessibilityErrorMessage
{
return objc_getAssociatedObject(self, _cmd);
}

- (void)setAccessibilityErrorMessage:(NSString *)accessibilityErrorMessage
{
objc_setAssociatedObject(
self, @selector(accessibilityErrorMessage), accessibilityErrorMessage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage
{
objc_setAssociatedObject(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ TextInputProps::TextInputProps(
"selection",
sourceProps.selection,
std::optional<Selection>())),
accessibilityErrorMessage(convertRawProp(
context,
rawProps,
"accessibilityErrorMessage",
sourceProps.accessibilityErrorMessage,
{})),
inputAccessoryViewID(convertRawProp(
context,
rawProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class TextInputProps final : public ViewProps, public BaseTextProps {

std::string const inputAccessoryViewID{};

std::string accessibilityErrorMessage{};

bool onKeyPressSync{false};
bool onChangeSync{false};

Expand Down