15 UIAccessibilityScrollDirection direction) {
22 case UIAccessibilityScrollDirectionRight:
23 case UIAccessibilityScrollDirectionPrevious:
25 return flutter::SemanticsAction::kScrollRight;
26 case UIAccessibilityScrollDirectionLeft:
27 case UIAccessibilityScrollDirectionNext:
29 return flutter::SemanticsAction::kScrollLeft;
30 case UIAccessibilityScrollDirectionUp:
31 return flutter::SemanticsAction::kScrollDown;
32 case UIAccessibilityScrollDirectionDown:
33 return flutter::SemanticsAction::kScrollUp;
36 return flutter::SemanticsAction::kScrollUp;
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);
54 SkPoint point = SkPoint::Make(local_point.x, local_point.y);
59 UIScreen* screen = reference.
bridge->view().window.screen;
61 CGFloat scale = (screen ?: UIScreen.mainScreen).scale;
62 auto result = CGPointMake(point.x() / scale, point.y() / scale);
63 return [reference.
bridge->view() convertPoint:result toView:nil];
70 SkPoint::Make(local_rect.origin.x, local_rect.origin.y),
71 SkPoint::Make(local_rect.origin.x + local_rect.size.width, local_rect.origin.y),
72 SkPoint::Make(local_rect.origin.x + local_rect.size.width,
73 local_rect.origin.y + local_rect.size.height),
74 SkPoint::Make(local_rect.origin.x,
75 local_rect.origin.y + local_rect.size.height)
77 for (
auto& point : quad) {
81 NSCAssert(rect.setBoundsCheck(quad, 4),
@"Transformed points can't form a rect");
82 rect.setBounds(quad, 4);
87 UIScreen* screen = reference.
bridge->view().window.screen;
89 CGFloat scale = (screen ?: UIScreen.mainScreen).scale;
91 CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale);
92 return UIAccessibilityConvertFrameToScreenCoordinates(result, reference.
bridge->view());
98 @property(nonatomic, retain, readonly) UISwitch* nativeSwitch;
103 - (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
105 self = [
super initWithBridge:bridge uid:uid];
107 _nativeSwitch = [[UISwitch alloc] init];
112 - (NSMethodSignature*)methodSignatureForSelector:(
SEL)sel {
113 NSMethodSignature* result = [
super methodSignatureForSelector:sel];
115 result = [
self.nativeSwitch methodSignatureForSelector:sel];
120 - (void)forwardInvocation:(NSInvocation*)anInvocation {
121 anInvocation.target =
self.nativeSwitch;
122 [anInvocation invoke];
125 - (NSString*)accessibilityValue {
126 self.nativeSwitch.on =
self.node.flags.isToggled == flutter::SemanticsTristate::kTrue ||
127 self.node.flags.isChecked == flutter::SemanticsCheckState::kTrue;
132 return self.nativeSwitch.accessibilityValue;
136 - (UIAccessibilityTraits)accessibilityTraits {
137 self.nativeSwitch.enabled =
self.node.flags.isEnabled == flutter::SemanticsTristate::kTrue;
139 return self.nativeSwitch.accessibilityTraits;
150 - (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
152 self = [
super initWithBridge:bridge uid:uid];
155 [_scrollView setShowsHorizontalScrollIndicator:NO];
156 [_scrollView setShowsVerticalScrollIndicator:NO];
157 [_scrollView setContentInset:UIEdgeInsetsZero];
158 [_scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
159 [
self.bridge->view() addSubview:_scrollView];
165 [_scrollView removeFromSuperview];
177 self.scrollView.frame =
self.accessibilityFrame;
178 self.scrollView.contentSize = [
self contentSizeInternal];
181 [
self.scrollView setContentOffset:self.contentOffsetInternal animated:NO];
186 return self.scrollView;
191 - (float)scrollExtentMax {
195 float scrollExtentMax =
self.node.scrollExtentMax;
196 if (isnan(scrollExtentMax)) {
197 scrollExtentMax = 0.0f;
198 }
else if (!isfinite(scrollExtentMax)) {
201 return scrollExtentMax;
204 - (float)scrollPosition {
208 float scrollPosition =
self.node.scrollPosition;
209 if (isnan(scrollPosition)) {
210 scrollPosition = 0.0f;
212 NSCAssert(isfinite(scrollPosition),
@"The scrollPosition must not be infinity");
213 return scrollPosition;
216 - (CGSize)contentSizeInternal {
218 const SkRect& rect =
self.node.rect;
220 if (
self.
node.actions & flutter::kVerticalScrollSemanticsActions) {
221 result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height() + [
self scrollExtentMax]);
222 }
else if (
self.
node.actions & flutter::kHorizontalScrollSemanticsActions) {
223 result = CGRectMake(rect.x(), rect.y(), rect.width() + [
self scrollExtentMax], rect.height());
225 result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
230 - (CGPoint)contentOffsetInternal {
232 CGPoint origin =
self.scrollView.frame.origin;
233 const SkRect& rect =
self.node.rect;
234 if (
self.
node.actions & flutter::kVerticalScrollSemanticsActions) {
236 }
else if (
self.
node.actions & flutter::kHorizontalScrollSemanticsActions) {
241 return CGPointMake(result.x - origin.x, result.y - origin.y);
259 NSMutableArray<SemanticsObject*>* _children;
263 #pragma mark - Designated initializers
265 - (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
267 FML_DCHECK(bridge) <<
"bridge must be set";
273 self = [
super initWithAccessibilityContainer:bridge->view()];
278 _children = [[NSMutableArray alloc] init];
279 _childrenInHitTestOrder = [[NSArray alloc] init];
295 [_children removeAllObjects];
301 #pragma mark - Semantic object property accesser
307 _children = [children mutableCopy];
313 - (void)setChildrenInHitTestOrder:(NSArray<
SemanticsObject*>*)childrenInHitTestOrder {
317 _childrenInHitTestOrder = [childrenInHitTestOrder copy];
323 - (BOOL)hasChildren {
324 return [
self.children count] != 0;
327 #pragma mark - Semantic object method
329 - (BOOL)isAccessibilityBridgeAlive {
330 return self.
bridge.get() != nil;
333 - (void)setSemanticsNode:(const
flutter::SemanticsNode*)node {
337 - (void)accessibilityBridgeDidFinishUpdate {
343 - (BOOL)nodeWillCauseLayoutChange:(const
flutter::SemanticsNode*)node {
344 return self.node.rect != node->rect ||
self.node.transform != node->transform;
350 - (BOOL)nodeWillCauseScroll:(const
flutter::SemanticsNode*)node {
351 return !isnan(
self.node.scrollPosition) && !isnan(node->scrollPosition) &&
352 self.node.scrollPosition != node->scrollPosition;
359 - (BOOL)nodeShouldTriggerAnnouncement:(const
flutter::SemanticsNode*)node {
361 if (!node || !node->flags.isLiveRegion) {
366 if (!
self.node.flags.isLiveRegion) {
371 return self.node.label != node->label;
374 - (void)replaceChildAtIndex:(NSInteger)index withChild:(
SemanticsObject*)child {
378 [_children replaceObjectAtIndex:index withObject:child];
381 - (NSString*)routeName {
384 if (
self.node.flags.namesRoute) {
385 NSString* newName =
self.accessibilityLabel;
386 if (newName != nil && [newName length] > 0) {
390 if ([
self hasChildren]) {
392 NSString* newName = [child routeName];
393 if (newName != nil && [newName length] > 0) {
401 - (id)nativeAccessibility {
405 - (NSAttributedString*)createAttributedStringFromString:(NSString*)string
407 (const
flutter::StringAttributes&)attributes {
408 NSMutableAttributedString* attributedString =
409 [[NSMutableAttributedString alloc] initWithString:string];
410 for (
const auto& attribute : attributes) {
411 NSRange range = NSMakeRange(attribute->start, attribute->end - attribute->start);
412 switch (attribute->type) {
413 case flutter::StringAttributeType::kLocale: {
414 std::shared_ptr<flutter::LocaleStringAttribute> locale_attribute =
415 std::static_pointer_cast<flutter::LocaleStringAttribute>(attribute);
416 NSDictionary* attributeDict = @{
417 UIAccessibilitySpeechAttributeLanguage : @(locale_attribute->locale.data()),
419 [attributedString setAttributes:attributeDict range:range];
422 case flutter::StringAttributeType::kSpellOut: {
423 NSDictionary* attributeDict = @{
424 UIAccessibilitySpeechAttributeSpellOut : @YES,
426 [attributedString setAttributes:attributeDict range:range];
431 return attributedString;
434 - (void)showOnScreen {
435 self.bridge->DispatchSemanticsAction(
self.uid, flutter::SemanticsAction::kShowOnScreen);
438 #pragma mark - UIAccessibility overrides
440 - (BOOL)isAccessibilityElement {
441 if (![
self isAccessibilityBridgeAlive]) {
450 if (
self.node.flags.scopesRoute) {
454 return [
self isFocusable];
457 - (NSString*)accessibilityLanguage {
458 if (![
self isAccessibilityBridgeAlive]) {
462 if (!
self.node.locale.empty()) {
463 return @(
self.node.locale.data());
465 return self.bridge->GetDefaultLocale();
468 - (bool)isFocusable {
477 return (
self.node.flags.hasImplicitScrolling &&
self.node.flags.isHidden)
479 || !
self.node.label.empty() || !
self.node.value.empty() || !
self.node.hint.empty() ||
480 (
self.node.actions & ~
flutter::kScrollableSemanticsActions) != 0;
484 if (
self.node.flags.scopesRoute) {
485 [edges addObject:self];
487 if ([
self hasChildren]) {
489 [child collectRoutes:edges];
495 if (!
self.node.HasAction(flutter::SemanticsAction::kCustomAction)) {
498 int32_t action_id = action.
uid;
499 std::vector<uint8_t> args;
501 args.push_back(action_id);
502 args.push_back(action_id >> 8);
503 args.push_back(action_id >> 16);
504 args.push_back(action_id >> 24);
505 self.bridge->DispatchSemanticsAction(
506 self.uid, flutter::SemanticsAction::kCustomAction,
507 fml::MallocMapping::Copy(args.data(), args.size() *
sizeof(uint8_t)));
511 - (NSString*)accessibilityIdentifier {
512 if (![
self isAccessibilityBridgeAlive]) {
516 if (
self.node.identifier.empty()) {
519 return @(
self.node.identifier.data());
522 - (NSString*)accessibilityLabel {
523 if (![
self isAccessibilityBridgeAlive]) {
526 NSString* label = nil;
527 if (!
self.node.label.empty()) {
528 label = @(
self.node.label.data());
530 if (!
self.node.tooltip.empty()) {
531 label = label ? [NSString stringWithFormat:@"%@\n%@", label, @(self.node.tooltip.data())]
532 : @(
self.node.tooltip.data());
537 - (bool)containsPoint:(CGPoint)point {
539 return CGRectContainsPoint([
self globalRect], point);
543 - (id)search:(CGPoint)point {
546 if ([child containsPoint:point]) {
547 id childSearchResult = [child search:point];
548 if (childSearchResult != nil) {
549 return childSearchResult;
554 if ([
self containsPoint:point] && [
self isFocusable]) {
555 return self.nativeAccessibility;
567 - (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event {
568 return [
self search:point];
572 - (BOOL)accessibilityScrollToVisible {
578 - (BOOL)accessibilityScrollToVisibleWithChild:(
id)child {
580 [child showOnScreen];
586 - (NSAttributedString*)accessibilityAttributedLabel {
587 NSString* label =
self.accessibilityLabel;
588 if (label.length == 0) {
591 return [
self createAttributedStringFromString:label withAttributes:self.node.labelAttributes];
594 - (NSString*)accessibilityHint {
595 if (![
self isAccessibilityBridgeAlive]) {
599 if (
self.node.hint.empty()) {
602 return @(
self.node.hint.data());
605 - (NSAttributedString*)accessibilityAttributedHint {
606 NSString* hint = [
self accessibilityHint];
607 if (hint.length == 0) {
610 return [
self createAttributedStringFromString:hint withAttributes:self.node.hintAttributes];
613 - (NSString*)accessibilityValue {
614 if (![
self isAccessibilityBridgeAlive]) {
618 if (!
self.node.value.empty()) {
619 return @(
self.node.value.data());
623 if (
self.node.flags.isInMutuallyExclusiveGroup) {
629 if (
self.node.flags.isToggled == flutter::SemanticsTristate::kTrue ||
630 self.node.flags.isChecked == flutter::SemanticsCheckState::kTrue) {
632 }
else if (
self.node.flags.isToggled == flutter::SemanticsTristate::kFalse ||
633 self.node.flags.isChecked == flutter::SemanticsCheckState::kFalse) {
640 - (NSAttributedString*)accessibilityAttributedValue {
641 NSString* value = [
self accessibilityValue];
642 if (value.length == 0) {
645 return [
self createAttributedStringFromString:value withAttributes:self.node.valueAttributes];
648 - (CGRect)accessibilityFrame {
649 if (![
self isAccessibilityBridgeAlive]) {
650 return CGRectMake(0, 0, 0, 0);
653 if (
self.node.flags.isHidden) {
654 return [
super accessibilityFrame];
656 return [
self globalRect];
659 - (CGRect)globalRect {
660 const SkRect& rect =
self.node.rect;
661 CGRect localRect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
665 #pragma mark - UIAccessibilityElement protocol
667 - (void)setAccessibilityContainer:(
id)container {
672 - (id)accessibilityContainer {
681 if (![
self isAccessibilityBridgeAlive]) {
685 if ([
self hasChildren] ||
self.uid ==
kRootNodeId) {
686 if (
self.container == nil) {
690 return self.container;
692 if (
self.parent == nil) {
698 return self.parent.accessibilityContainer;
701 #pragma mark - UIAccessibilityAction overrides
703 - (BOOL)accessibilityActivate {
704 if (![
self isAccessibilityBridgeAlive]) {
707 if (!
self.node.HasAction(flutter::SemanticsAction::kTap)) {
712 if (
self.node.flags.isSlider) {
717 self.bridge->DispatchSemanticsAction(
self.uid, flutter::SemanticsAction::kTap);
721 - (void)accessibilityIncrement {
722 if (![
self isAccessibilityBridgeAlive]) {
725 if (
self.node.HasAction(flutter::SemanticsAction::kIncrease)) {
726 self.node.value =
self.node.increasedValue;
727 self.bridge->DispatchSemanticsAction(
self.uid, flutter::SemanticsAction::kIncrease);
731 - (void)accessibilityDecrement {
732 if (![
self isAccessibilityBridgeAlive]) {
735 if (
self.node.HasAction(flutter::SemanticsAction::kDecrease)) {
736 self.node.value =
self.node.decreasedValue;
737 self.bridge->DispatchSemanticsAction(
self.uid, flutter::SemanticsAction::kDecrease);
741 - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
742 if (![
self isAccessibilityBridgeAlive]) {
746 if (!
self.node.HasAction(action)) {
749 self.bridge->DispatchSemanticsAction(
self.uid, action);
753 - (BOOL)accessibilityPerformEscape {
754 if (![
self isAccessibilityBridgeAlive]) {
757 if (!
self.node.HasAction(flutter::SemanticsAction::kDismiss)) {
760 self.bridge->DispatchSemanticsAction(
self.uid, flutter::SemanticsAction::kDismiss);
764 #pragma mark UIAccessibilityFocus overrides
766 - (void)accessibilityElementDidBecomeFocused {
767 if (![
self isAccessibilityBridgeAlive]) {
770 self.bridge->AccessibilityObjectDidBecomeFocused(
self.uid);
771 if (
self.node.flags.isHidden ||
self.node.flags.isHeader) {
774 if (
self.node.HasAction(flutter::SemanticsAction::kDidGainAccessibilityFocus)) {
775 self.bridge->DispatchSemanticsAction(
self.uid,
776 flutter::SemanticsAction::kDidGainAccessibilityFocus);
780 - (void)accessibilityElementDidLoseFocus {
781 if (![
self isAccessibilityBridgeAlive]) {
784 self.bridge->AccessibilityObjectDidLoseFocus(
self.uid);
785 if (
self.node.HasAction(flutter::SemanticsAction::kDidLoseAccessibilityFocus)) {
786 self.bridge->DispatchSemanticsAction(
self.uid,
787 flutter::SemanticsAction::kDidLoseAccessibilityFocus);
791 - (BOOL)accessibilityRespondsToUserInteraction {
793 if ((
self.node.actions & ~flutter::kSystemActions) != 0) {
797 if (!
self.node.customAccessibilityActions.empty()) {
808 #pragma mark - Designated initializers
810 - (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
812 self = [
super initWithBridge:bridge uid:uid];
816 #pragma mark - UIAccessibility overrides
818 - (UIAccessibilityTraits)accessibilityTraits {
819 UIAccessibilityTraits traits = UIAccessibilityTraitNone;
820 if (
self.
node.HasAction(flutter::SemanticsAction::kIncrease) ||
821 self.node.HasAction(flutter::SemanticsAction::kDecrease)) {
822 traits |= UIAccessibilityTraitAdjustable;
825 if (
self.
node.flags.isToggled != flutter::SemanticsTristate::kNone ||
826 self.node.flags.isChecked != flutter::SemanticsCheckState::kNone) {
827 traits |= UIAccessibilityTraitButton;
829 if (
self.
node.flags.isSelected == flutter::SemanticsTristate::kTrue) {
830 traits |= UIAccessibilityTraitSelected;
832 if (
self.
node.flags.isButton) {
833 traits |= UIAccessibilityTraitButton;
835 if (
self.
node.flags.isEnabled == flutter::SemanticsTristate::kFalse) {
836 traits |= UIAccessibilityTraitNotEnabled;
838 if (
self.
node.flags.isHeader) {
839 traits |= UIAccessibilityTraitHeader;
841 if (
self.
node.flags.isImage) {
842 traits |= UIAccessibilityTraitImage;
844 if (
self.
node.flags.isLiveRegion) {
845 traits |= UIAccessibilityTraitUpdatesFrequently;
847 if (
self.
node.flags.isLink) {
848 traits |= UIAccessibilityTraitLink;
850 if (traits == UIAccessibilityTraitNone && ![
self hasChildren] &&
851 self.accessibilityLabel.length != 0 && !
self.node.flags.isTextField) {
852 traits = UIAccessibilityTraitStaticText;
860 @property(nonatomic, weak) UIView* platformView;
865 - (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
869 _platformView = platformView;
870 [platformView setFlutterAccessibilityContainer:self];
876 return self.platformView;
882 fml::WeakPtr<flutter::AccessibilityBridgeIos> _bridge;
885 #pragma mark - initializers
887 - (instancetype)initWithSemanticsObject:(
SemanticsObject*)semanticsObject
888 bridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge {
889 FML_DCHECK(semanticsObject) <<
"semanticsObject must be set";
894 self = [
super initWithAccessibilityContainer:bridge->view()];
897 _semanticsObject = semanticsObject;
904 #pragma mark - UIAccessibilityContainer overrides
906 - (NSInteger)accessibilityElementCount {
907 return self.semanticsObject.
children.count + 1;
910 - (nullable id)accessibilityElementAtIndex:(NSInteger)index {
911 if (index < 0 || index >= [
self accessibilityElementCount]) {
915 return self.semanticsObject.nativeAccessibility;
920 if ([child hasChildren]) {
921 return child.accessibilityContainer;
926 - (NSInteger)indexOfAccessibilityElement:(
id)element {
927 if (element ==
self.semanticsObject.nativeAccessibility) {
931 NSArray<SemanticsObject*>* children =
self.semanticsObject.children;
932 for (
size_t i = 0; i < [children count]; i++) {
942 #pragma mark - UIAccessibilityElement protocol
944 - (BOOL)isAccessibilityElement {
948 - (CGRect)accessibilityFrame {
954 return UIScreen.mainScreen.bounds;
957 - (id)accessibilityContainer {
963 :
self.semanticsObject.
parent.accessibilityContainer;
966 #pragma mark - UIAccessibilityAction overrides
968 - (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
969 return [
self.semanticsObject accessibilityScroll:direction];
constexpr int32_t kRootNodeId
constexpr float kScrollExtentMaxForInf
BOOL isAccessibilityBridgeAlive()
void accessibilityBridgeDidFinishUpdate()
flutter::SemanticsNode node
NSArray< SemanticsObject * > * children
fml::WeakPtr< 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)