15 UIAccessibilityScrollDirection direction) {
22 case UIAccessibilityScrollDirectionRight:
23 case UIAccessibilityScrollDirectionPrevious:
26 case UIAccessibilityScrollDirectionLeft:
27 case UIAccessibilityScrollDirectionNext:
30 case UIAccessibilityScrollDirectionUp:
32 case UIAccessibilityScrollDirectionDown:
40 SkM44 globalTransform = [reference node].transform;
42 globalTransform = parent.node.transform * globalTransform;
44 return globalTransform;
48 SkV4 vector =
transform.map(point.x(), point.y(), 0, 1);
49 return SkPoint::Make(vector.x / vector.w, vector.y / vector.w);
53 UIView* containerView = [reference bridgeView];
58 SkPoint point = SkPoint::Make(local_point.x, local_point.y);
63 UIScreen* screen = containerView.window.screen;
65 CGFloat scale = (screen ?: UIScreen.mainScreen).scale;
66 auto result = CGPointMake(point.x() / scale, point.y() / scale);
67 return [containerView convertPoint:result toView:nil];
71 UIView* containerView = [reference bridgeView];
78 SkPoint::Make(local_rect.origin.x, local_rect.origin.y),
79 SkPoint::Make(local_rect.origin.x + local_rect.size.width, local_rect.origin.y),
80 SkPoint::Make(local_rect.origin.x + local_rect.size.width,
81 local_rect.origin.y + local_rect.size.height),
82 SkPoint::Make(local_rect.origin.x,
83 local_rect.origin.y + local_rect.size.height)
85 for (
auto& point : quad) {
89 NSCAssert(rect.setBoundsCheck({quad, 4}),
@"Transformed points can't form a rect");
90 rect.setBounds({quad, 4});
95 UIScreen* screen = containerView.window.screen;
97 CGFloat scale = (screen ?: UIScreen.mainScreen).scale;
99 CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale);
100 return UIAccessibilityConvertFrameToScreenCoordinates(result, containerView);
106@property(nonatomic, retain, readonly) UISwitch* nativeSwitch;
111- (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
113 self = [
super initWithBridge:bridge uid:uid];
115 _nativeSwitch = [[UISwitch alloc] init];
120- (NSMethodSignature*)methodSignatureForSelector:(
SEL)sel {
121 NSMethodSignature* result = [
super methodSignatureForSelector:sel];
123 result = [
self.nativeSwitch methodSignatureForSelector:sel];
128- (void)forwardInvocation:(NSInvocation*)anInvocation {
129 anInvocation.target =
self.nativeSwitch;
130 [anInvocation invoke];
133- (NSString*)accessibilityValue {
140 return self.nativeSwitch.accessibilityValue;
144- (UIAccessibilityTraits)accessibilityTraits {
147 return self.nativeSwitch.accessibilityTraits;
158- (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
160 self = [
super initWithBridge:bridge uid:uid];
163 [_scrollView setShowsHorizontalScrollIndicator:NO];
164 [_scrollView setShowsVerticalScrollIndicator:NO];
165 [_scrollView setContentInset:UIEdgeInsetsZero];
166 [_scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
167 UIView* containerView =
self.bridgeView;
169 [containerView addSubview:_scrollView];
176 [_scrollView removeFromSuperview];
188 self.scrollView.frame =
self.accessibilityFrame;
189 self.scrollView.contentSize = [
self contentSizeInternal];
191 if (!
self.scrollView.isDoingSystemScrolling) {
192 [
self.scrollView setContentOffset:self.contentOffsetInternal animated:NO];
197 return self.scrollView;
202- (float)scrollExtentMax {
206 float scrollExtentMax =
self.node.scrollExtentMax;
207 if (isnan(scrollExtentMax)) {
208 scrollExtentMax = 0.0f;
209 }
else if (!isfinite(scrollExtentMax)) {
212 return scrollExtentMax;
215- (float)scrollPosition {
219 float scrollPosition =
self.node.scrollPosition;
220 if (isnan(scrollPosition)) {
221 scrollPosition = 0.0f;
223 NSCAssert(isfinite(scrollPosition),
@"The scrollPosition must not be infinity");
224 return scrollPosition;
227- (CGSize)contentSizeInternal {
229 const SkRect& rect =
self.node.rect;
232 result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height() + [
self scrollExtentMax]);
234 result = CGRectMake(rect.x(), rect.y(), rect.width() + [
self scrollExtentMax], rect.height());
236 result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
241- (CGPoint)contentOffsetInternal {
243 CGPoint origin =
self.scrollView.frame.origin;
244 const SkRect& rect =
self.node.rect;
252 return CGPointMake(result.x - origin.x, result.y - origin.y);
275#pragma mark - Designated initializers
277- (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
285 self = [
super initWithAccessibilityContainer:bridge ? bridge->view() : nil];
288 _weakBridge = bridge;
290 _children = [[NSMutableArray alloc] init];
291 _childrenInHitTestOrder = [[NSArray alloc] init];
307 [_children removeAllObjects];
313#pragma mark - Semantic object property accesser
316 return [_children copy];
329- (void)setChildrenInHitTestOrder:(NSArray<
SemanticsObject*>*)childrenInHitTestOrder {
333 _childrenInHitTestOrder = [childrenInHitTestOrder copy];
340 return [
self.children count] != 0;
343#pragma mark - Semantic object method
345- (
flutter::AccessibilityBridgeIos*)bridge {
346 return _weakBridge.get();
349- (UIView*)bridgeView {
351 return bridge ? bridge->
view() : nil;
354- (void)setSemanticsNode:(const
flutter::SemanticsNode*)node {
358- (void)accessibilityBridgeDidFinishUpdate {
364- (
BOOL)nodeWillCauseLayoutChange:(const
flutter::SemanticsNode*)node {
365 return self.node.rect != node->rect ||
self.node.transform != node->transform;
371- (
BOOL)nodeWillCauseScroll:(const
flutter::SemanticsNode*)node {
372 return !isnan(
self.node.scrollPosition) && !isnan(node->scrollPosition) &&
373 self.node.scrollPosition != node->scrollPosition;
380- (
BOOL)nodeShouldTriggerAnnouncement:(const
flutter::SemanticsNode*)node {
382 if (!node || !node->flags.isLiveRegion) {
387 if (!
self.node.flags.isLiveRegion) {
392 return self.node.label != node->label;
395- (void)replaceChildAtIndex:(NSInteger)index withChild:(
SemanticsObject*)child {
399 [_children replaceObjectAtIndex:index withObject:child];
402- (NSString*)routeName {
405 if (
self.node.flags.namesRoute) {
406 NSString* newName =
self.accessibilityLabel;
407 if (newName != nil && [newName
length] > 0) {
411 if ([
self hasChildren]) {
413 NSString* newName = [child routeName];
414 if (newName != nil && [newName
length] > 0) {
422- (
id)nativeAccessibility {
426- (NSAttributedString*)createAttributedStringFromString:(NSString*)string
429 NSMutableAttributedString* attributedString =
430 [[NSMutableAttributedString alloc] initWithString:string];
431 for (
const auto& attribute : attributes) {
432 NSRange range = NSMakeRange(attribute->start, attribute->end - attribute->start);
433 switch (attribute->type) {
435 std::shared_ptr<flutter::LocaleStringAttribute> locale_attribute =
436 std::static_pointer_cast<flutter::LocaleStringAttribute>(attribute);
437 NSDictionary* attributeDict = @{
438 UIAccessibilitySpeechAttributeLanguage : @(locale_attribute->locale.data()),
440 [attributedString setAttributes:attributeDict range:range];
444 NSDictionary* attributeDict = @{
445 UIAccessibilitySpeechAttributeSpellOut : @YES,
447 [attributedString setAttributes:attributeDict range:range];
452 return attributedString;
455- (void)showOnScreen {
463#pragma mark - UIAccessibility overrides
465- (
BOOL)isAccessibilityElement {
475 if (
self.node.flags.scopesRoute) {
479 return [
self isFocusable];
482- (NSString*)accessibilityLanguage {
488 if (!
self.node.locale.empty()) {
489 return @(
self.node.locale.data());
503 return (
self.node.flags.hasImplicitScrolling &&
self.node.flags.isHidden)
505 || !
self.node.label.empty() || !
self.node.value.empty() || !
self.node.hint.empty() ||
506 (
self.node.actions &
~flutter::kScrollableSemanticsActions) != 0;
510 if (
self.node.flags.scopesRoute) {
511 [edges addObject:self];
513 if ([
self hasChildren]) {
515 [child collectRoutes:edges];
528 int32_t action_id =
action.uid;
529 std::vector<uint8_t>
args;
531 args.push_back(action_id);
532 args.push_back(action_id >> 8);
533 args.push_back(action_id >> 16);
534 args.push_back(action_id >> 24);
541- (NSString*)accessibilityIdentifier {
546 if (
self.node.identifier.empty()) {
549 return @(
self.node.identifier.data());
552- (NSString*)accessibilityLabel {
556 NSString* label = nil;
557 if (!
self.node.label.empty()) {
558 label = @(
self.node.label.data());
560 if (!
self.node.tooltip.empty()) {
561 label = label ? [NSString stringWithFormat:@"%@\n%@", label, @(self.node.tooltip.data())]
562 : @(
self.node.tooltip.data());
567- (bool)containsPoint:(CGPoint)point {
569 return CGRectContainsPoint([
self globalRect], point);
573- (
id)search:(CGPoint)point {
576 if ([child containsPoint:point]) {
577 id childSearchResult = [child search:point];
578 if (childSearchResult != nil) {
579 return childSearchResult;
584 if ([
self containsPoint:point] && [
self isFocusable]) {
585 return self.nativeAccessibility;
597- (
id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event {
601 return [
self search:point];
605- (
BOOL)accessibilityScrollToVisible {
611- (
BOOL)accessibilityScrollToVisibleWithChild:(
id)child {
613 [child showOnScreen];
619- (NSAttributedString*)accessibilityAttributedLabel {
620 NSString* label =
self.accessibilityLabel;
621 if (label.length == 0) {
624 return [
self createAttributedStringFromString:label withAttributes:self.node.labelAttributes];
627- (NSString*)accessibilityHint {
632 if (
self.node.hint.empty()) {
635 return @(
self.node.hint.data());
638- (NSAttributedString*)accessibilityAttributedHint {
639 NSString* hint = [
self accessibilityHint];
640 if (hint.length == 0) {
643 return [
self createAttributedStringFromString:hint withAttributes:self.node.hintAttributes];
646- (NSString*)accessibilityValue {
651 if (!
self.node.value.empty()) {
652 return @(
self.node.value.data());
656 if (
self.node.flags.isInMutuallyExclusiveGroup) {
673- (NSAttributedString*)accessibilityAttributedValue {
674 NSString*
value = [
self accessibilityValue];
675 if (
value.length == 0) {
678 return [
self createAttributedStringFromString:value withAttributes:self.node.valueAttributes];
681- (CGRect)accessibilityFrame {
686 if (
self.node.flags.isHidden) {
687 return [
super accessibilityFrame];
689 return [
self globalRect];
692- (CGRect)globalRect {
693 const SkRect& rect =
self.node.rect;
694 CGRect localRect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
698#pragma mark - UIAccessibilityElement protocol
700- (void)setAccessibilityContainer:(
id)container {
705- (
id)accessibilityContainer {
719 if (
self.container == nil) {
722 return self.container;
724 if (
self.parent == nil) {
730 return self.parent.accessibilityContainer;
733#pragma mark - UIAccessibilityAction overrides
735- (
BOOL)accessibilityActivate {
745 if (
self.node.flags.isSlider) {
754- (void)accessibilityIncrement {
760 self.node.value =
self.node.increasedValue;
765- (void)accessibilityDecrement {
771 self.node.value =
self.node.decreasedValue;
776- (
BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
789- (
BOOL)accessibilityPerformEscape {
801#pragma mark UIAccessibilityFocus overrides
803- (void)accessibilityElementDidBecomeFocused {
809 if (
self.node.flags.isHidden ||
self.node.flags.isHeader) {
817- (void)accessibilityElementDidLoseFocus {
828- (
BOOL)accessibilityRespondsToUserInteraction {
829 if (
self.node.flags.isAccessibilityFocusBlocked) {
838 if (!
self.node.customAccessibilityActions.empty()) {
849#pragma mark - Designated initializers
851- (instancetype)initWithBridge:(
fml::
WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
853 self = [
super initWithBridge:bridge uid:uid];
857#pragma mark - UIAccessibility overrides
859- (UIAccessibilityTraits)accessibilityTraits {
860 UIAccessibilityTraits traits = UIAccessibilityTraitNone;
863 traits |= UIAccessibilityTraitAdjustable;
868 traits |= UIAccessibilityTraitButton;
871 traits |= UIAccessibilityTraitSelected;
873 if (
self.node.flags.isButton) {
874 traits |= UIAccessibilityTraitButton;
877 traits |= UIAccessibilityTraitNotEnabled;
879 if (
self.node.headingLevel > 0) {
881 traits |= UIAccessibilityTraitHeader;
883 if (
self.node.flags.isImage) {
884 traits |= UIAccessibilityTraitImage;
886 if (
self.node.flags.isLiveRegion) {
887 traits |= UIAccessibilityTraitUpdatesFrequently;
889 if (
self.node.flags.isLink) {
890 traits |= UIAccessibilityTraitLink;
893 self.accessibilityLabel.length != 0 && !
self.node.flags.isTextField) {
894 traits = UIAccessibilityTraitStaticText;
902@property(nonatomic, weak) UIView* platformView;
907- (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
911 _platformView = platformView;
912 [platformView setFlutterAccessibilityContainer:self];
918 return self.platformView;
926#pragma mark - initializers
928- (instancetype)initWithSemanticsObject:(
SemanticsObject*)semanticsObject {
929 FML_DCHECK(semanticsObject) <<
"semanticsObject must be set";
934 self = [
super initWithAccessibilityContainer:semanticsObject.bridgeView];
937 _semanticsObject = semanticsObject;
943#pragma mark - UIAccessibilityContainer overrides
945- (NSInteger)accessibilityElementCount {
949- (nullable
id)accessibilityElementAtIndex:(NSInteger)index {
950 if (index < 0 || index >= [
self accessibilityElementCount]) {
954 return self.semanticsObject.nativeAccessibility;
959 if ([child hasChildren]) {
960 return child.accessibilityContainer;
965- (NSInteger)indexOfAccessibilityElement:(
id)element {
966 if (element ==
self.semanticsObject.nativeAccessibility) {
970 NSArray<SemanticsObject*>* children =
self.semanticsObject.children;
971 for (
size_t i = 0;
i < [children count];
i++) {
981#pragma mark - UIAccessibilityElement protocol
983- (
BOOL)isAccessibilityElement {
987- (CGRect)accessibilityFrame {
993 return UIScreen.mainScreen.bounds;
996- (
id)accessibilityContainer {
999 :
self.semanticsObject.parent.accessibilityContainer;
1002#pragma mark - UIAccessibilityAction overrides
1004- (
BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
1005 return [
self.semanticsObject accessibilityScroll:direction];
NS_ASSUME_NONNULL_BEGIN constexpr int32_t kRootNodeId
constexpr float kScrollExtentMaxForInf
NSMutableArray< SemanticsObject * > * _children
Interface that represents an accessibility bridge for iOS.
virtual void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action)=0
virtual void AccessibilityObjectDidLoseFocus(int32_t id)=0
virtual void AccessibilityObjectDidBecomeFocused(int32_t id)=0
virtual NSString * GetDefaultLocale()=0
virtual UIView * view() const =0
static MallocMapping Copy(const T *begin, const T *end)
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
#define FML_DCHECK(condition)
NSArray< SemanticsObject * > * children
nullable UIView * bridgeView()
void accessibilityBridgeDidFinishUpdate()
nullable flutter::AccessibilityBridgeIos * bridge()
CGRect ConvertRectToGlobal(SemanticsObject *reference, CGRect local_rect)
flutter::SemanticsAction GetSemanticsActionForScrollDirection(UIAccessibilityScrollDirection direction)
CGPoint ConvertPointToGlobal(SemanticsObject *reference, CGPoint local_point)
SkM44 GetGlobalTransform(SemanticsObject *reference)
SkPoint ApplyTransform(SkPoint &point, const SkM44 &transform)
auto WeakPtr(const std::shared_ptr< T > &pointer)
constexpr int kHorizontalScrollSemanticsActions
constexpr int kSystemActions
The following actions are not user-initiated.
constexpr int kVerticalScrollSemanticsActions
std::vector< StringAttributePtr > StringAttributes
@ kDidLoseAccessibilityFocus
@ kDidGainAccessibilityFocus