Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ internal static MauiAppBuilder RemapForControls(this MauiAppBuilder builder)
ContentPage.RemapForControls();
ImageButton.RemapForControls();

Slider.RemapForControls();
return builder;
}
}
16 changes: 16 additions & 0 deletions src/Controls/src/Core/Slider/Slider.Mapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using Microsoft.Maui.Controls.Compatibility;

namespace Microsoft.Maui.Controls
{
public partial class Slider
{
internal static new void RemapForControls()
{
// Adjust the mappings to preserve Controls.Slider legacy behaviors
#if IOS
SliderHandler.Mapper.ReplaceMapping<Slider, ISliderHandler>(PlatformConfiguration.iOSSpecific.Slider.UpdateOnTapProperty.PropertyName, MapUpdateOnTap);
#endif
}
}
}
19 changes: 19 additions & 0 deletions src/Controls/src/Core/Slider/Slider.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;

namespace Microsoft.Maui.Controls
{
public partial class Slider
{
internal static void MapUpdateOnTap(SliderHandler handler, Slider slider) =>
MapUpdateOnTap((ISliderHandler)handler, slider);

internal static void MapUpdateOnTap(ISliderHandler handler, Slider slider)
{
if (handler is SliderHandler sliderHandler)
{
sliderHandler.MapUpdateOnTap(slider.OnThisPlatform().GetUpdateOnTap());
}
}
}
}
30 changes: 30 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Slider/SliderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Handlers.Compatibility;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
[Category(TestCategory.Slider)]
public partial class SliderTests : ControlsHandlerTestBase
{
void SetupBuilder()
{
EnsureHandlerCreated(builder =>
{
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<Slider, SliderHandler>();
});
});
}
}
}
38 changes: 38 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Slider/SliderTests.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Xunit;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Slider = Microsoft.Maui.Controls.Slider;
using Microsoft.Maui.Controls.PlatformConfiguration;

namespace Microsoft.Maui.DeviceTests
{
public partial class SliderTests
{
[Fact("Slider Does Not Leak")]
public async Task DoesNotLeak()
{
SetupBuilder();
WeakReference platformViewReference = null;
WeakReference handlerReference = null;

await InvokeOnMainThreadAsync(() =>
{
var layout = new VerticalStackLayout();
var slider = new Slider();
slider.On<iOS>().SetUpdateOnTap(true);
layout.Add(slider);

var handler = CreateHandler<LayoutHandler>(layout);
handlerReference = new WeakReference(slider.Handler);
platformViewReference = new WeakReference(slider.Handler.PlatformView);
});

await AssertionExtensions.WaitForGC(handlerReference, platformViewReference);
}
}
}
1 change: 1 addition & 0 deletions src/Controls/tests/DeviceTests/TestCategory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public static class TestCategory
public const string SearchBar = "SearchBar";
public const string Shape = "Shape";
public const string Shell = "Shell";
public const string Slider = "Slider";
public const string SwipeView = "SwipeView";
public const string TabbedPage = "TabbedPage";
public const string TextInput = "TextInput";
Expand Down
10 changes: 10 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue20098.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue20098"
xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls">
<VerticalStackLayout>
<Label AutomationId="label" Text="{Binding Value, Source={x:Reference slider}}"/>
<Slider AutomationId="slider" ios:Slider.UpdateOnTap="True" x:Name="slider" Minimum="0" Maximum="100" Value="0"/>
</VerticalStackLayout>
</ContentPage>
15 changes: 15 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue20098.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Maui.Controls.Sample.Issues
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.Github, 20098, "iOS-specific Slider.UpdateOnTab", PlatformAffected.iOS)]
public partial class Issue20098 : ContentPage
{
public Issue20098()
{
InitializeComponent();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#if IOS
using NUnit.Framework;
using NUnit.Framework.Legacy;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue20098: _IssuesUITest
{
public Issue20098(TestDevice device) : base(device)
{
}

public override string Issue => "iOS-specific Slider.UpdateOnTab";

[Test]
[Category(UITestCategories.Picker)]
public void UpdateOnTabShouldWork()
{
_ = App.WaitForElement("slider");
var pickerRect = App.FindElement("slider").GetRect();
var label = App.FindElement("label");
App.Click(pickerRect.X + pickerRect.Width / 2, pickerRect.Y);
var text = label.GetText();

ClassicAssert.AreNotEqual("0", text);
}
}
#endif
46 changes: 46 additions & 0 deletions src/Core/src/Handlers/Slider/SliderHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Microsoft.Maui.Handlers
public partial class SliderHandler : ViewHandler<ISlider, UISlider>
{
readonly SliderProxy _proxy = new();
UITapGestureRecognizer? _sliderTapRecognizer;

protected override UISlider CreatePlatformView()
{
Expand All @@ -28,6 +29,51 @@ protected override void DisconnectHandler(UISlider platformView)
{
base.DisconnectHandler(platformView);
_proxy.Disconnect(platformView);
if (_sliderTapRecognizer is not null)
{
_sliderTapRecognizer.ShouldReceiveTouch = null;
platformView.RemoveGestureRecognizer(_sliderTapRecognizer);
_sliderTapRecognizer.Dispose();
_sliderTapRecognizer = null;
}
}

internal void MapUpdateOnTap(bool isMapUpdateOnTapEnabled)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PureWeen do we have "design docs" on where this should live? For other Controls-only things we just kept it in the XAML control class?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, if we're not adding an interface at the core level then controls level inside the xaml class makes sense

{
if (isMapUpdateOnTapEnabled)
{
if (_sliderTapRecognizer is null)
{
var weakPlatformSlider = new WeakReference<UISlider>(PlatformView);
var weakVirtualSlider = new WeakReference<ISlider>(VirtualView);
_sliderTapRecognizer = new UITapGestureRecognizer((recognizer) =>
{
if (weakPlatformSlider.TryGetTarget(out var platformSlider) && weakVirtualSlider.TryGetTarget(out var slider))
{
var tappedLocation = recognizer.LocationInView(platformSlider);
if (tappedLocation != default)
{
var val = tappedLocation.X * platformSlider.MaxValue / platformSlider.Frame.Size.Width;
slider.Value = val;
}
}
})
{
CancelsTouchesInView = false,
ShouldRecognizeSimultaneously = (gesture, otherGesture) => true
};

PlatformView.AddGestureRecognizer(_sliderTapRecognizer);
}
}
else
{
if (_sliderTapRecognizer != null)
{
PlatformView.RemoveGestureRecognizer(_sliderTapRecognizer);
_sliderTapRecognizer = null;
}
}
}

public static void MapMinimum(ISliderHandler handler, ISlider slider)
Expand Down