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

[pull] master from facebook:master #129

Merged
merged 4 commits into from
Jul 6, 2020
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,27 @@
#import <UIKit/UIKit.h>

#import <react/attributedstring/AttributedString.h>
#import <react/attributedstring/ParagraphAttributes.h>
#import <react/textlayoutmanager/RCTTextLayoutManager.h>

#import "RCTParagraphComponentView.h"

@interface RCTParagraphComponentAccessibilityProvider : NSObject

- (instancetype)initWithString:(facebook::react::AttributedString)attributedString view:(UIView *)view;
- (instancetype)initWithString:(facebook::react::AttributedString)attributedString
layoutManager:(RCTTextLayoutManager *)layoutManager
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
frame:(CGRect)frame
view:(UIView *)view;

/*
* Returns an array of `UIAccessibilityElement`s to be used for `UIAccessibilityContainer` implementation.
*/
- (NSArray<UIAccessibilityElement *> *)accessibilityElements;

/**
@abstract Array of accessibleElements for use in UIAccessibilityContainer implementation.
@abstract To make sure the provider is up to date.
*/
- (NSArray<UIAccessibilityElement *> *)accessibilityElements;
- (BOOL)isUpToDate:(facebook::react::AttributedString)currentAttributedString;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#import <Foundation/Foundation.h>
#import <react/components/text/ParagraphProps.h>
#import <react/textlayoutmanager/RCTAttributedTextUtils.h>
#import <react/textlayoutmanager/RCTTextLayoutManager.h>
#import <react/textlayoutmanager/TextLayoutManager.h>

Expand All @@ -20,27 +21,89 @@
@implementation RCTParagraphComponentAccessibilityProvider {
NSMutableArray<UIAccessibilityElement *> *_accessibilityElements;
AttributedString _attributedString;
RCTTextLayoutManager *_layoutManager;
ParagraphAttributes _paragraphAttributes;
CGRect _frame;
__weak UIView *_view;
}

- (instancetype)initWithString:(AttributedString)attributedString view:(UIView *)view
- (instancetype)initWithString:(facebook::react::AttributedString)attributedString
layoutManager:(RCTTextLayoutManager *)layoutManager
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
frame:(CGRect)frame
view:(UIView *)view
{
if (self = [super init]) {
_attributedString = attributedString;
_layoutManager = layoutManager;
_paragraphAttributes = paragraphAttributes;
_frame = frame;
_view = view;
}
return self;
}

- (NSArray<UIAccessibilityElement *> *)accessibilityElements
{
// verify if accessibleElements are exist
if (_accessibilityElements) {
return _accessibilityElements;
}

__block NSInteger numOfLink = 0;
// build an array of the accessibleElements
NSMutableArray<UIAccessibilityElement *> *elements = [NSMutableArray new];

NSString *accessibilityLabel = [_view valueForKey:@"accessibilityLabel"];
if (!accessibilityLabel.length) {
accessibilityLabel = RCTNSStringFromString(_attributedString.getString());
}
// add first element has the text for the whole textview in order to read out the whole text
UIAccessibilityElement *firstElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:_view];
firstElement.isAccessibilityElement = YES;
firstElement.accessibilityTraits = UIAccessibilityTraitStaticText;
firstElement.accessibilityLabel = accessibilityLabel;
firstElement.accessibilityFrameInContainerSpace = _view.bounds;
[elements addObject:firstElement];

// add additional elements for those parts of text with embedded link so VoiceOver could specially recognize links

[_layoutManager getRectWithAttributedString:_attributedString
paragraphAttributes:_paragraphAttributes
enumerateAttribute:RCTTextAttributesAccessibilityRoleAttributeName
frame:_frame
usingBlock:^(CGRect fragmentRect, NSString *_Nonnull fragmentText, NSString *value) {
UIAccessibilityElement *element =
[[UIAccessibilityElement alloc] initWithAccessibilityContainer:self->_view];
element.isAccessibilityElement = YES;
if ([value isEqualToString:@"link"]) {
element.accessibilityTraits = UIAccessibilityTraitLink;
numOfLink++;
}
element.accessibilityLabel = fragmentText;
element.accessibilityFrameInContainerSpace = fragmentRect;
[elements addObject:element];
}];

if (numOfLink > 0) {
[elements enumerateObjectsUsingBlock:^(UIAccessibilityElement *element, NSUInteger idx, BOOL *_Nonnull stop) {
element.accessibilityHint = [NSString stringWithFormat:@"Link %ld of %ld.", (unsigned long)idx, (long)numOfLink];
}];

NSString *firstElementHint = (numOfLink == 1)
? @"One link found, swipe right to move to the link."
: [NSString stringWithFormat:@"%ld links found, swipe right to move to the first link.", (long)numOfLink];

firstElement.accessibilityHint = firstElementHint;
}

// add accessible element for truncation attributed string for automation purposes only
_accessibilityElements = [NSMutableArray new];
_accessibilityElements = elements;
return _accessibilityElements;
}

- (BOOL)isUpToDate:(facebook::react::AttributedString)currentAttributedString
{
return currentAttributedString == _attributedString;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#import "RCTParagraphComponentView.h"
#import "RCTParagraphComponentAccessibilityProvider.h"

#import <react/components/text/ParagraphComponentDescriptor.h>
#import <react/components/text/ParagraphProps.h>
Expand All @@ -26,6 +27,7 @@
@implementation RCTParagraphComponentView {
ParagraphShadowNode::ConcreteState::Shared _state;
ParagraphAttributes _paragraphAttributes;
RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand All @@ -35,7 +37,6 @@ - (instancetype)initWithFrame:(CGRect)frame
_props = defaultProps;

self.isAccessibilityElement = YES;
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
Expand Down Expand Up @@ -138,6 +139,29 @@ - (NSString *)accessibilityLabel
return RCTNSStringFromString(_state->getData().attributedString.getString());
}

- (NSArray *)accessibilityElements
{
if (![_accessibilityProvider isUpToDate:_state->getData().attributedString]) {
RCTTextLayoutManager *textLayoutManager =
(RCTTextLayoutManager *)unwrapManagedObject(_state->getData().layoutManager->getNativeTextLayoutManager());
CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame());
_accessibilityProvider =
[[RCTParagraphComponentAccessibilityProvider alloc] initWithString:_state->getData().attributedString
layoutManager:textLayoutManager
paragraphAttributes:_state->getData().paragraphAttributes
frame:frame
view:self];
}

self.isAccessibilityElement = NO;
return _accessibilityProvider.accessibilityElements;
}

- (UIAccessibilityTraits)accessibilityTraits
{
return [super accessibilityTraits] | UIAccessibilityTraitStaticText;
}

- (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
{
if (!_state) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN

NSString *const RCTAttributedStringIsHighlightedAttributeName = @"IsHighlighted";
NSString *const RCTAttributedStringEventEmitterKey = @"EventEmitter";
NSString *const RCTTextAttributesAccessibilityRoleAttributeName = @"AccessibilityRole";

/*
* Creates `NSTextAttributes` from given `facebook::react::TextAttributes`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex
attributes[RCTAttributedStringIsHighlightedAttributeName] = @YES;
}

if (!textAttributes.accessibilityRole.empty()) {
attributes[RCTTextAttributesAccessibilityRoleAttributeName] =
[NSString stringWithCString:textAttributes.accessibilityRole.c_str() encoding:NSUTF8StringEncoding];
}

return [attributes copy];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@

NS_ASSUME_NONNULL_BEGIN

/**
@abstract Enumeration block for text fragments.
*/

using RCTTextLayoutFragmentEnumerationBlock =
void (^)(CGRect fragmentRect, NSString *_Nonnull fragmentText, NSString *value);

/**
* iOS-specific TextLayoutManager
*/
Expand All @@ -38,6 +45,12 @@ NS_ASSUME_NONNULL_BEGIN
frame:(CGRect)frame
atPoint:(CGPoint)point;

- (void)getRectWithAttributedString:(facebook::react::AttributedString)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
enumerateAttribute:(NSString *)enumerateAttribute
frame:(CGRect)frame
usingBlock:(RCTTextLayoutFragmentEnumerationBlock)block;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,44 @@ - (NSAttributedString *)_nsAttributedStringFromAttributedString:(AttributedStrin
return unwrapManagedObject(sharedNSAttributedString);
}

- (void)getRectWithAttributedString:(AttributedString)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
enumerateAttribute:(NSString *)enumerateAttribute
frame:(CGRect)frame
usingBlock:(RCTTextLayoutFragmentEnumerationBlock)block
{
NSTextStorage *textStorage = [self
_textStorageAndLayoutManagerWithAttributesString:[self _nsAttributedStringFromAttributedString:attributedString]
paragraphAttributes:paragraphAttributes
size:frame.size];

NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
[layoutManager ensureLayoutForTextContainer:textContainer];

NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];

[textStorage enumerateAttribute:enumerateAttribute
inRange:characterRange
options:0
usingBlock:^(NSString *value, NSRange range, BOOL *pause) {
if (!value) {
return;
}

[layoutManager
enumerateEnclosingRectsForGlyphRange:range
withinSelectedGlyphRange:range
inTextContainer:textContainer
usingBlock:^(CGRect enclosingRect, BOOL *_Nonnull stop) {
block(
enclosingRect,
[textStorage attributedSubstringFromRange:range].string,
value);
*stop = YES;
}];
}];
}

@end