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);
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 {
132 return self.nativeSwitch.accessibilityValue;
136- (UIAccessibilityTraits)accessibilityTraits {
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];
180 if (!
self.scrollView.isDoingSystemScrolling) {
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;
221 result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height() + [
self scrollExtentMax]);
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;
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
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];
324 return [
self.children count] != 0;
327#pragma mark - Semantic object method
329- (
BOOL)isAccessibilityBridgeAlive {
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
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) {
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];
423 NSDictionary* attributeDict = @{
424 UIAccessibilitySpeechAttributeSpellOut : @YES,
426 [attributedString setAttributes:attributeDict range:range];
431 return attributedString;
434- (void)showOnScreen {
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();
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];
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(
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) {
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]) {
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]) {
712 if (
self.node.flags.isSlider) {
721- (void)accessibilityIncrement {
722 if (![
self isAccessibilityBridgeAlive]) {
726 self.node.value =
self.node.increasedValue;
731- (void)accessibilityDecrement {
732 if (![
self isAccessibilityBridgeAlive]) {
736 self.node.value =
self.node.decreasedValue;
741- (
BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
742 if (![
self isAccessibilityBridgeAlive]) {
753- (
BOOL)accessibilityPerformEscape {
754 if (![
self isAccessibilityBridgeAlive]) {
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) {
775 self.bridge->DispatchSemanticsAction(
self.uid,
780- (void)accessibilityElementDidLoseFocus {
781 if (![
self isAccessibilityBridgeAlive]) {
784 self.bridge->AccessibilityObjectDidLoseFocus(
self.uid);
786 self.bridge->DispatchSemanticsAction(
self.uid,
791- (
BOOL)accessibilityRespondsToUserInteraction {
792 if (
self.node.flags.isAccessibilityFocusBlocked) {
801 if (!
self.node.customAccessibilityActions.empty()) {
812#pragma mark - Designated initializers
814- (instancetype)initWithBridge:(
fml::
WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
816 self = [
super initWithBridge:bridge uid:uid];
820#pragma mark - UIAccessibility overrides
822- (UIAccessibilityTraits)accessibilityTraits {
823 UIAccessibilityTraits traits = UIAccessibilityTraitNone;
826 traits |= UIAccessibilityTraitAdjustable;
831 traits |= UIAccessibilityTraitButton;
834 traits |= UIAccessibilityTraitSelected;
836 if (
self.node.flags.isButton) {
837 traits |= UIAccessibilityTraitButton;
840 traits |= UIAccessibilityTraitNotEnabled;
842 if (
self.node.flags.isHeader) {
843 traits |= UIAccessibilityTraitHeader;
845 if (
self.node.flags.isImage) {
846 traits |= UIAccessibilityTraitImage;
848 if (
self.node.flags.isLiveRegion) {
849 traits |= UIAccessibilityTraitUpdatesFrequently;
851 if (
self.node.flags.isLink) {
852 traits |= UIAccessibilityTraitLink;
855 self.accessibilityLabel.length != 0 && !
self.node.flags.isTextField) {
856 traits = UIAccessibilityTraitStaticText;
864@property(nonatomic, weak) UIView* platformView;
869- (instancetype)initWithBridge:(
fml::WeakPtr<
flutter::AccessibilityBridgeIos>)bridge
873 _platformView = platformView;
874 [platformView setFlutterAccessibilityContainer:self];
880 return self.platformView;
889#pragma mark - initializers
891- (instancetype)initWithSemanticsObject:(
SemanticsObject*)semanticsObject
893 FML_DCHECK(semanticsObject) <<
"semanticsObject must be set";
898 self = [
super initWithAccessibilityContainer:bridge->view()];
901 _semanticsObject = semanticsObject;
908#pragma mark - UIAccessibilityContainer overrides
910- (NSInteger)accessibilityElementCount {
914- (nullable
id)accessibilityElementAtIndex:(NSInteger)index {
915 if (index < 0 || index >= [
self accessibilityElementCount]) {
919 return self.semanticsObject.nativeAccessibility;
924 if ([child hasChildren]) {
925 return child.accessibilityContainer;
930- (NSInteger)indexOfAccessibilityElement:(
id)element {
931 if (element ==
self.semanticsObject.nativeAccessibility) {
935 NSArray<SemanticsObject*>* children =
self.semanticsObject.children;
936 for (
size_t i = 0;
i < [children count];
i++) {
946#pragma mark - UIAccessibilityElement protocol
948- (
BOOL)isAccessibilityElement {
952- (CGRect)accessibilityFrame {
958 return UIScreen.mainScreen.bounds;
961- (
id)accessibilityContainer {
967 :
self.semanticsObject.parent.accessibilityContainer;
970#pragma mark - UIAccessibilityAction overrides
972- (
BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
973 return [
self.semanticsObject accessibilityScroll:direction];
constexpr int32_t kRootNodeId
constexpr float kScrollExtentMaxForInf
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)
BOOL isAccessibilityBridgeAlive()
void accessibilityBridgeDidFinishUpdate()
NSArray< SemanticsObject * > * children
fml::WeakPtr< flutter::AccessibilityBridgeIos > bridge
std::shared_ptr< flutter::AccessibilityBridgeMac > _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