diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7606.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7606.cs new file mode 100644 index 00000000000..8fa987aa62f --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7606.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 7606, "[Bug] When a view appears it is not accessible via VoiceOver", + PlatformAffected.iOS)] +#if UITEST + [NUnit.Framework.Category(Core.UITests.UITestCategories.Github10000)] + [NUnit.Framework.Category(UITestCategories.ManualReview)] + [NUnit.Framework.Category(UITestCategories.Accessibility)] +#endif + public class Issue7606 : TestContentPage + { + protected override void Init() + { + Label visibilityLabel = new Label() + { + Text = "Swipe right and I should be read by voice over.", + IsVisible = false + }; + + Content = new StackLayout() + { + Children = + { + new Button() + { + Text = "Click Me", + Command = new Command(() => visibilityLabel.IsVisible = !visibilityLabel.IsVisible) + }, + visibilityLabel + } + }; + } + + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8613.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8613.cs new file mode 100644 index 00000000000..e32cf653a94 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8613.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 8613, "[Bug] Accessibility, screenreader ignores or skips items in nested stacklayout", + PlatformAffected.iOS)] +#if UITEST + [NUnit.Framework.Category(Core.UITests.UITestCategories.Github10000)] + [NUnit.Framework.Category(UITestCategories.ManualReview)] + [NUnit.Framework.Category(UITestCategories.Accessibility)] +#endif + public class Issue8613 : TestContentPage + { + Label CreateLabel(bool? IsInAccessibleTree, string text) + { + Label label = new Label() + { + Text = text + }; + + if(IsInAccessibleTree.HasValue) + AutomationProperties.SetIsInAccessibleTree(label, IsInAccessibleTree); + + return label; + } + + Entry CreateEntry(bool IsInAccessibleTree, string placeholderText) + { + Entry entry = new Entry() + { + Placeholder = placeholderText + }; + + AutomationProperties.SetIsInAccessibleTree(entry, IsInAccessibleTree); + return entry; + } + + protected override void Init() + { + // Based on Sample + // https://github.com/xamarin/xamarin-forms-samples/blob/master/UserInterface/Accessibility/Accessibility/AccessibilityPage.xaml + Content = new ScrollView() + { + Content = + new StackLayout() + { + Children = + { + CreateLabel(true, "Voice Over Swiping should progress sequentially through all visible elements"), + CreateLabel(true, "Second Label IsInAccessibleTree = true"), + new StackLayout() + { + Children = + { + new Label() + { + Text = "Enter Your Name: ", + }, + CreateEntry(true, "If Voice Over swiping gets stuck here test has failed"), + }, + }, + CreateLabel(true, "Label After the Entry") + }, + }, + }; + } + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8691.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8691.cs new file mode 100644 index 00000000000..aee6454aad4 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue8691.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 8691, "[Bug] TabIndex is ignored for first element on page for VoiceOver", + PlatformAffected.iOS)] +#if UITEST + [NUnit.Framework.Category(Core.UITests.UITestCategories.Github10000)] + [NUnit.Framework.Category(UITestCategories.ManualReview)] + [NUnit.Framework.Category(UITestCategories.Accessibility)] +#endif + public class Issue8691 : TestContentPage + { + protected override void Init() + { + + Content = new StackLayout() + { + Children = + { + new Label() + { + Text = "2nd TabIndex", + TabIndex = 20 + }, + new Label() + { + Text = "I should be the first element focused when voice over is on", + TabIndex = 10 + }, + new Label() + { + Text = "3rd TabIndex", + TabIndex = 30 + }, + } + }; + } + + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9137.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9137.cs new file mode 100644 index 00000000000..20e7828890d --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue9137.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +using Xamarin.Forms.Core.UITests; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Github, 9137, "A11y: Image in a11y tree stops voiceover from hopping to the next element", + PlatformAffected.iOS)] +#if UITEST + [NUnit.Framework.Category(Core.UITests.UITestCategories.Github10000)] + [NUnit.Framework.Category(UITestCategories.ManualReview)] + [NUnit.Framework.Category(UITestCategories.Accessibility)] +#endif + public class Issue9137 : TestContentPage + { + Label CreateLabel(bool IsInAccessibleTree, string text) + { + Label label = new Label() + { + Text = text + }; + AutomationProperties.SetIsInAccessibleTree(label, IsInAccessibleTree); + return label; + } + + protected override void Init() + { + Content = new StackLayout() + { + Children = + { + new StackLayout() + { + Children = + { + CreateLabel(false, "IsInAccessibleTree is false") + }, + }, + new StackLayout() + { + Children = + { + new StackLayout() + { + Children = + { + new Label() + { + Text = "Turn Voice Over on and verify that you can swipe all the way forward and then backwards. If you get stuck toggling between the same two elements test has failed", + TabIndex = 10 + }, + new Image() + { + TabIndex = 20, + Source = "coffee.png" + }, + }, + }, + new Label() + { + Text = "Tab Index 70", + TabIndex = 70 + } + }, + }, + } + }; + } + } +} diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index b2ce36e9119..f9a4a982bac 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -26,6 +26,10 @@ + + + + diff --git a/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs b/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs index 5b96cb0e74b..64d2e486246 100644 --- a/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs +++ b/Xamarin.Forms.Core.UITests.Shared/UITestCategories.cs @@ -64,6 +64,7 @@ internal static class UITestCategories public const string Github5000 = "Github5000"; public const string Github10000 = "Github10000"; public const string RadioButton = "RadioButton"; + public const string Accessibility = "Accessibility"; public const string Shape = "Shape"; } } diff --git a/Xamarin.Forms.Platform.iOS/Renderers/PageContainer.cs b/Xamarin.Forms.Platform.iOS/Renderers/PageContainer.cs index 7f4512905a1..1851da2aafb 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/PageContainer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/PageContainer.cs @@ -1,4 +1,5 @@ using Foundation; +using ObjCRuntime; using System; using System.Collections.Generic; using UIKit; @@ -8,8 +9,9 @@ namespace Xamarin.Forms.Platform.iOS internal class PageContainer : UIView, IUIAccessibilityContainer { readonly IAccessibilityElementsController _parent; - List _accessibilityElements = null; + NSArray _accessibilityElements = null; bool _disposed; + bool _loaded; public PageContainer(IAccessibilityElementsController parent) { @@ -22,15 +24,32 @@ public PageContainer() IsAccessibilityElement = false; } - List AccessibilityElements + public override bool IsAccessibilityElement { + get => false; + set => base.IsAccessibilityElement = value; + } + + [Internals.Preserve(Conditional = true)] + public virtual NSArray AccessibilityElements + { + [Export("accessibilityElements", ArgumentSemantic.Copy)] get { + if (_loaded) + return _accessibilityElements; + // lazy-loading this list so that the expensive call to GetAccessibilityElements only happens when VoiceOver is on. if (_accessibilityElements == null || _accessibilityElements.Count == 0) { - _accessibilityElements = _parent.GetAccessibilityElements(); + var elements =_parent.GetAccessibilityElements(); + if(elements != null) + { + _accessibilityElements = NSArray.FromNSObjects(elements.ToArray()); + } } + + _loaded = true; return _accessibilityElements; } } @@ -38,6 +57,7 @@ List AccessibilityElements public void ClearAccessibilityElements() { _accessibilityElements = null; + _loaded = false; } protected override void Dispose(bool disposing) @@ -49,38 +69,5 @@ protected override void Dispose(bool disposing) } base.Dispose(disposing); } - - [Export("accessibilityElementCount")] - [Internals.Preserve(Conditional = true)] - nint AccessibilityElementCount() - { - if (AccessibilityElements == null || AccessibilityElements.Count == 0) - return 0; - - // Note: this will only be called when VoiceOver is enabled - return AccessibilityElements.Count; - } - - [Export("accessibilityElementAtIndex:")] - [Internals.Preserve(Conditional = true)] - NSObject GetAccessibilityElementAt(nint index) - { - if (AccessibilityElements == null || AccessibilityElements.Count == 0) - return NSNull.Null; - - // Note: this will only be called when VoiceOver is enabled - return AccessibilityElements[(int)index]; - } - - [Export("indexOfAccessibilityElement:")] - [Internals.Preserve(Conditional = true)] - int GetIndexOfAccessibilityElement(NSObject element) - { - if (AccessibilityElements == null || AccessibilityElements.Count == 0) - return int.MaxValue; - - // Note: this will only be called when VoiceOver is enabled - return AccessibilityElements.IndexOf(element); - } } } \ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/Renderers/PageRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/PageRenderer.cs index 94951f14d72..de58ae9343c 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/PageRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/PageRenderer.cs @@ -44,18 +44,10 @@ void IEffectControlProvider.RegisterEffect(Effect effect) public event EventHandler ElementChanged; - List DefaultOrder() - { - var views = new List(); - if (Container != null) - views.AddRange(Container.DescendantsTree()); - return views; - } - public List GetAccessibilityElements() { if (Container == null || Element == null) - return new List(); + return null; SortedDictionary> tabIndexes = null; foreach (var child in Element.LogicalChildren) @@ -68,11 +60,11 @@ public List GetAccessibilityElements() } if (tabIndexes == null) - return DefaultOrder(); + return null; // Just return all elements on the page in order. if (tabIndexes.Count <= 1) - return DefaultOrder(); + return null; var views = new List(); foreach (var idx in tabIndexes?.Keys)