Просмотр исходного кода

Better VoiceOver experience for in-app messages (#7445)

* Better accessibility labels for FIAM images and close buttons

* At FIAM display time, announce that the message has appeared in VoiceOver mode

* In VoiceOver mode, announce the close button last. This is a better accessibility experience for finding the close button.

* For modal messages, include the action button in accessibility elements. Exclude the image view if there's no image.

* Update CHANGELOG

* Include campaign name in accessibility label for images

* Fix failing modal message UI tests
christibbs 5 лет назад
Родитель
Сommit
f6d3a8c614

+ 2 - 1
FirebaseInAppMessaging/CHANGELOG.md

@@ -1,4 +1,5 @@
-# 2021-2 -- v7.7.0
+# 2021-2 -- v.7.7.0
+- [fixed] Fixed accessibility experience for in-app messages (#7445).
 - [fixed] Fixed conversion tracking for in-app messages with a conversion event but not a button / action URL (#7306).
 
 # 2021-1 -- v7.5.0

+ 6 - 6
FirebaseInAppMessaging/Resources/FIRInAppMessageDisplayStoryboard.storyboard

@@ -22,14 +22,14 @@
                             <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rA8-LH-EYm">
                                 <rect key="frame" x="44" y="59" width="294" height="222"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                                <accessibility key="accessibilityConfiguration" identifier="image-view-in-image-only-view">
+                                <accessibility key="accessibilityConfiguration" identifier="image-view-in-image-only-view" label="In-app message image">
                                     <bool key="isElement" value="YES"/>
                                 </accessibility>
                             </imageView>
                             <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vYW-0X-e5S">
                                 <rect key="frame" x="313" y="42" width="40" height="40"/>
                                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                                <accessibility key="accessibilityConfiguration" identifier="close-button"/>
+                                <accessibility key="accessibilityConfiguration" identifier="close-button" label="Close button"/>
                                 <state key="normal" backgroundImage="close-with-transparency.png"/>
                                 <connections>
                                     <action selector="closeButtonClicked:" destination="lGH-bl-7Xw" eventType="touchUpInside" id="lpP-J3-1Jc"/>
@@ -62,7 +62,7 @@
                         <subviews>
                             <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-vw-7up">
                                 <rect key="frame" x="5" y="47" width="60" height="60"/>
-                                <accessibility key="accessibilityConfiguration" identifier="banner-image-view">
+                                <accessibility key="accessibilityConfiguration" identifier="banner-image-view" label="In-app message image">
                                     <bool key="isElement" value="YES"/>
                                 </accessibility>
                                 <constraints>
@@ -144,7 +144,7 @@
                                     </label>
                                     <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="700" translatesAutoresizingMaskIntoConstraints="NO" id="c7x-Lf-M58">
                                         <rect key="frame" x="24" y="82.000000000000043" width="306" height="230.66666666666663"/>
-                                        <accessibility key="accessibilityConfiguration" identifier="modal-image-view">
+                                        <accessibility key="accessibilityConfiguration" identifier="modal-image-view" label="In-app message image">
                                             <bool key="isElement" value="YES"/>
                                         </accessibility>
                                         <constraints>
@@ -305,7 +305,7 @@
                             </view>
                             <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="akA-AE-VPc">
                                 <rect key="frame" x="364" y="167.66666666666666" width="40" height="40"/>
-                                <accessibility key="accessibilityConfiguration" identifier="close-button"/>
+                                <accessibility key="accessibilityConfiguration" identifier="close-button" label="Close button"/>
                                 <constraints>
                                     <constraint firstAttribute="width" constant="40" identifier="Close button width = 40" id="Dwi-In-x47"/>
                                     <constraint firstAttribute="height" constant="40" identifier="Close button height = 40" id="tIe-pH-jRO"/>
@@ -392,7 +392,7 @@
                                 <subviews>
                                     <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="gB0-8W-G9z">
                                         <rect key="frame" x="0.0" y="0.0" width="240" height="160"/>
-                                        <accessibility key="accessibilityConfiguration" identifier="card-image-view">
+                                        <accessibility key="accessibilityConfiguration" identifier="card-image-view" label="In-app message image">
                                             <bool key="isElement" value="YES"/>
                                         </accessibility>
                                         <constraints>

+ 4 - 0
FirebaseInAppMessaging/Sources/DefaultUI/Banner/FIRIAMBannerViewController.m

@@ -142,6 +142,7 @@ static const CGFloat kSwipeUpThreshold = -10.0f;
       }
     }
     self.imageView.image = image;
+    self.imageView.accessibilityLabel = self.inAppMessage.campaignInfo.campaignName;
   } else {
     // Hide image and remove the bottom constraint between body label and image view.
     self.imageViewWidthConstraint.constant = 0;
@@ -286,6 +287,9 @@ static const CGFloat kSwipeUpThreshold = -10.0f;
                      self.view.center = normalCenterPoint;
                    }
                    completion:nil];
+
+  // Announce via VoiceOver that the banner has appeared. Highlight the title label.
+  UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.titleLabel);
 }
 
 - (void)setupAutoDismissTimer {

+ 9 - 0
FirebaseInAppMessaging/Sources/DefaultUI/Card/FIRIAMCardViewController.m

@@ -111,6 +111,8 @@
   self.bodyTextView.text = self.cardDisplayMessage.body;
   self.bodyTextView.textColor = self.cardDisplayMessage.textColor;
 
+  self.imageView.accessibilityLabel = self.inAppMessage.campaignInfo.campaignName;
+
   [self.primaryActionButton setTitle:self.cardDisplayMessage.primaryActionButton.buttonText
                             forState:UIControlStateNormal];
   [self.primaryActionButton
@@ -148,6 +150,13 @@
   [self.textAreaScrollView setContentOffset:CGPointZero];
 }
 
+- (void)viewDidAppear:(BOOL)animated {
+  [super viewDidAppear:animated];
+
+  // Announce via VoiceOver that the card message has appeared. Highlight the title label.
+  UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.titleLabel);
+}
+
 @end
 
 #endif  // TARGET_OS_IOS

+ 13 - 0
FirebaseInAppMessaging/Sources/DefaultUI/ImageOnly/FIRIAMImageOnlyViewController.m

@@ -89,11 +89,17 @@
   [super viewDidLoad];
   [self.view setBackgroundColor:[UIColor.grayColor colorWithAlphaComponent:0.5]];
 
+  // Close button should be announced last for better VoiceOver experience.
+  self.view.accessibilityElements = @[ self.imageView, self.closeButton ];
+
   if (self.imageOnlyMessage.imageData) {
     UIImage *image = [UIImage imageWithData:self.imageOnlyMessage.imageData.imageRawData];
     self.imageOriginalSize = image.size;
     [self.imageView setImage:image];
     self.imageView.contentMode = UIViewContentModeScaleAspectFit;
+    self.imageView.accessibilityLabel = self.inAppMessage.campaignInfo.campaignName;
+  } else {
+    self.imageView.isAccessibilityElement = NO;
   }
 
   [self setupRecognizers];
@@ -176,6 +182,13 @@
   }
 }
 
+- (void)viewDidAppear:(BOOL)animated {
+  [super viewDidAppear:animated];
+
+  // Announce via VoiceOver that the image-only message has appeared. Highlight the image.
+  UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.imageView);
+}
+
 - (void)flashCloseButton:(UIButton *)closeButton {
   closeButton.alpha = 1.0f;
   [UIView animateWithDuration:2.0

+ 16 - 0
FirebaseInAppMessaging/Sources/DefaultUI/Modal/FIRIAMModalViewController.m

@@ -130,6 +130,9 @@ static CGFloat LandScapePaddingBetweenImageAndTextColumn = 24;
     [self.imageView
         setImage:[UIImage imageWithData:self.modalDisplayMessage.imageData.imageRawData]];
     self.imageView.contentMode = UIViewContentModeScaleAspectFit;
+    self.imageView.accessibilityLabel = self.inAppMessage.campaignInfo.campaignName;
+  } else {
+    self.imageView.isAccessibilityElement = NO;
   }
 
   self.messageCardView.backgroundColor = self.modalDisplayMessage.displayBackgroundColor;
@@ -163,6 +166,12 @@ static CGFloat LandScapePaddingBetweenImageAndTextColumn = 24;
   [self.view addConstraint:self.imageActualHeightConstraint];
   self.imageActualHeightConstraint.active = YES;
   self.fixedMessageCardHeightConstraint.active = NO;
+
+  // Close button should be announced last for better VoiceOver experience.
+  self.view.accessibilityElements = @[
+    self.titleLabel, self.imageView, self.bodyTextView, self.actionButton, self.closeButton,
+    self.messageCardView
+  ];
 }
 
 // for text display UIview, which could be a UILabel or UITextView, decide the fit height under a
@@ -456,6 +465,13 @@ struct TitleBodyButtonHeightInfo {
   }
 }
 
+- (void)viewDidAppear:(BOOL)animated {
+  [super viewDidAppear:animated];
+
+  // Announce via VoiceOver that the modal message has appeared. Highlight the title label.
+  UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.titleLabel);
+}
+
 - (void)flashCloseButton:(UIButton *)closeButton {
   closeButton.alpha = 1.0f;
   [UIView animateWithDuration:2.0