Skip to content

Commit

Permalink
[iOS] RadioButton a11y (#10832)
Browse files Browse the repository at this point in the history
* First attempt of radiobutton a11y

* Make Switch trait work as expected

* Update control gallery sample

* Make class internal for potential backport

* Update based on feedback

* Add comment

* Update based on feedback
  • Loading branch information
rachelkang committed Nov 28, 2022
1 parent 73a7eb3 commit 4e1770e
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,39 @@

</RadioButton>

<Label FontSize="Small" Text="The RadioButton below is the same as above, but also has a Semantic Description set on it for better accessibility."/>

<RadioButton GroupName="test"
SemanticProperties.Description="{Binding Source={x:Reference embeddedButton1}, Path=Text}"
TextColor="Green"
TextTransform="Uppercase"
FontAttributes="Bold"
FontSize="12"
FontFamily="Arial">

<RadioButton.Content>
<Button x:Name="embeddedButton1" Text="It's a button inside a button." TextColor="Green"></Button>
</RadioButton.Content>

</RadioButton>

<Label FontSize="Small" Text="The RadioButton below is the same as above, but has a Semantic Description and Semantic Hint set on it for better accessibility."/>

<RadioButton GroupName="test"
SemanticProperties.Description="{Binding Source={x:Reference embeddedButton2}, Path=Text}"
SemanticProperties.Hint="Tests semantics on a radio button with a button set as its content"
TextColor="Green"
TextTransform="Uppercase"
FontAttributes="Bold"
FontSize="12"
FontFamily="Arial">

<RadioButton.Content>
<Button x:Name="embeddedButton2" Text="It's a button inside a button." TextColor="Green"></Button>
</RadioButton.Content>

</RadioButton>

<Label FontSize="Small" Text="A Content View which already has these properties set/bound should ignore the RadioButton properties."/>

<RadioButton GroupName="test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@
</x:Array>
</ListView.ItemsSource>
</ListView>
</StackLayout>
<Label FontSize="Small" Text="RadioButton with a Semantic Description and Semantic Hint"/>
<RadioButton
Content="RadioButton, Group=null"
SemanticProperties.Description="Semantic description"
SemanticProperties.Hint="Semantic hint" />
</StackLayout>
</ScrollView>
</ContentPage>

Expand Down
18 changes: 18 additions & 0 deletions src/Controls/src/Core/HandlerImpl/RadioButton/RadioButton.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,23 @@ public partial class RadioButton : IRadioButton
Color IButtonStroke.StrokeColor => (Color)GetValue(BorderColorProperty);

int IButtonStroke.CornerRadius => (int)GetValue(CornerRadiusProperty);

private protected override Semantics UpdateSemantics()
{
var semantics = base.UpdateSemantics();

if (ControlTemplate != null)
{
string contentAsString = ContentAsString();

if (!string.IsNullOrWhiteSpace(contentAsString) && string.IsNullOrWhiteSpace(semantics?.Description))
{
semantics ??= new Semantics();
semantics.Description = contentAsString;
}
}

return semantics;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.Maui.Controls
/// <include file="../../../../docs/Microsoft.Maui.Controls/VisualElement.xml" path="Type[@FullName='Microsoft.Maui.Controls.VisualElement']/Docs/*" />
public partial class VisualElement : IView
{
Semantics _semantics;
Semantics? _semantics;
bool _isLoadedFired;
EventHandler? _loaded;
EventHandler? _unloaded;
Expand Down Expand Up @@ -163,28 +163,23 @@ bool IView.IsFocused

Visibility IView.Visibility => IsVisible.ToVisibility();

Semantics IView.Semantics
{
get
{
UpdateSemantics();
return _semantics;
}
}
Semantics? IView.Semantics => UpdateSemantics();

void UpdateSemantics()
private protected virtual Semantics? UpdateSemantics()
{
if (!this.IsSet(SemanticProperties.HintProperty) &&
!this.IsSet(SemanticProperties.DescriptionProperty) &&
!this.IsSet(SemanticProperties.HeadingLevelProperty))
{
return;
_semantics = null;
return _semantics;
}

_semantics ??= new Semantics();
_semantics.Description = SemanticProperties.GetDescription(this);
_semantics.HeadingLevel = SemanticProperties.GetHeadingLevel(this);
_semantics.Hint = SemanticProperties.GetHint(this);
return _semantics;
}

static double EnsurePositive(double value)
Expand Down
15 changes: 8 additions & 7 deletions src/Core/src/Handlers/RadioButton/RadioButtonHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ protected override ContentView CreatePlatformView()
_ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a {nameof(ContentView)}");
_ = MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} cannot be null");

return new ContentView
{
CrossPlatformMeasure = VirtualView.CrossPlatformMeasure,
CrossPlatformArrange = VirtualView.CrossPlatformArrange
};
return new SemanticSwitchContentView(VirtualView);
}

public override void SetVirtualView(IView view)
Expand Down Expand Up @@ -45,8 +41,13 @@ public static void MapContent(IRadioButtonHandler handler, IContentView page)
UpdateContent(handler);
}

[MissingMapper]
public static void MapIsChecked(IRadioButtonHandler handler, IRadioButton radioButton) { }
public static void MapIsChecked(IRadioButtonHandler handler, IRadioButton radioButton)
{
if (radioButton.IsChecked)
handler.PlatformView.AccessibilityValue = "1";
else
handler.PlatformView.AccessibilityValue = "0";
}

[MissingMapper]
public static void MapTextColor(IRadioButtonHandler handler, ITextStyle textStyle) { }
Expand Down
42 changes: 42 additions & 0 deletions src/Core/src/Platform/iOS/SemanticSwitchContentView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using CoreGraphics;
using Microsoft.Maui.Graphics;
using ObjCRuntime;
using UIKit;

namespace Microsoft.Maui.Platform;

// This class should remain internal until further refactored.
// It is currently being used only for RadioButton accessibility on iOS.
internal class SemanticSwitchContentView : ContentView
{
UIAccessibilityTrait _accessibilityTraits;

internal SemanticSwitchContentView(IContentView virtualView)
{
CrossPlatformMeasure = virtualView.CrossPlatformMeasure;
CrossPlatformArrange = virtualView.CrossPlatformArrange;
IsAccessibilityElement = true;
}

static UIKit.UIAccessibilityTrait? s_switchAccessibilityTraits;
UIKit.UIAccessibilityTrait SwitchAccessibilityTraits
{
get
{
if (s_switchAccessibilityTraits == null ||
s_switchAccessibilityTraits == UIKit.UIAccessibilityTrait.None)
{
s_switchAccessibilityTraits = new UIKit.UISwitch().AccessibilityTraits;
}

return s_switchAccessibilityTraits ?? UIKit.UIAccessibilityTrait.None;
}
}

public override UIAccessibilityTrait AccessibilityTraits
{
get => _accessibilityTraits |= SwitchAccessibilityTraits;
set => _accessibilityTraits = value | SwitchAccessibilityTraits;
}
}

0 comments on commit 4e1770e

Please sign in to comment.