profession
profession

Reputation: 1

How to make clickable slider for iOS project with Xamarin Forms?

I use slider to make rating stars feature. For this I put slider in background to recive position of finger. This works pretty fine on android, because whenever you touch slider it changes its value. UISlider on iOS fires events only when you touch its thumb.

I think, it could be done like this.

1.Add tap gesture to whole slider control

        UILongPressGestureRecognizer uiTap = new UILongPressGestureRecognizer(Tapped);
        uiTap.MinimumPressDuration = 0;
        AddGestureRecognizer(uiTap);

2. Read coordinates from touch and pass them to control. But I don't know how to do this.

My xaml looks like this.

<Grid>
    <Slider/>
    <Grid>
    <!-- here is my stars -->
    </Grid>
</Grid>

Here good answers, with code on objective-c: https://stackoverflow.com/a/22982080/10139785

Upvotes: 0

Views: 1024

Answers (3)

O_G
O_G

Reputation: 11

if you're on the recent stable Xamarin.Forms Microsoft Documentation has an easier way for you. Basically set a property on iOS specific namespace for the slider. Surprise!

Here's the gist of it in Xaml:

<ContentPage ...
         xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core">
<StackLayout ...>
    <Slider ... ios:Slider.UpdateOnTap="true" />
    ...
</StackLayout>

or in the code behind:

using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
...

//(in constructor or OnAppearing say)
var slider = new Xamarin.Forms.Slider();
slider.On<iOS>().SetUpdateOnTap(true);

Hope this helps.

Upvotes: 1

RyanO
RyanO

Reputation: 51

I have created a similar solution in Xamarin iOS C#

(Adapted from this answer)

[Register(nameof(UISliderCustom))]
[DesignTimeVisible(true)]
public class UISliderCustom : UISlider
{

    public UISliderCustom(IntPtr handle) : base(handle) { }

    public UISliderCustom()
    {
        // Called when created from code.
        Initialize();
    }

    public override void AwakeFromNib()
    {
        // Called when loaded from xib or storyboard.
        Initialize();
    }

    void Initialize()
    {
        // Common initialization code here.

        var longPress = new UILongPressGestureRecognizer(tapAndSlide);
        longPress.MinimumPressDuration = 0;
        //longPress.CancelsTouchesInView = false;
        this.AddGestureRecognizer(longPress);
        this.UserInteractionEnabled = true;

    }

    private void tapAndSlide(UILongPressGestureRecognizer gesture)
    {
        System.Diagnostics.Debug.WriteLine($"{nameof(UISliderCustom)} RecognizerState {gesture.State}");

        // need to propagate events down the chain
        // I imagine iOS does something similar
        // for whatever recogniser on the thumb control
        // It's not enough to set CancelsTouchesInView because
        // if clicking on the track away from the thumb control
        // the thumb gesture recogniser won't pick it up anyway
        switch (gesture.State)
        {
            case UIGestureRecognizerState.Cancelled:
                this.SendActionForControlEvents(UIControlEvent.TouchCancel);
                break;

            case UIGestureRecognizerState.Began:
                this.SendActionForControlEvents(UIControlEvent.TouchDown);
                break;

            case UIGestureRecognizerState.Changed:
                this.SendActionForControlEvents(UIControlEvent.ValueChanged);                    
                break;

            case UIGestureRecognizerState.Ended:
                this.SendActionForControlEvents(UIControlEvent.TouchUpInside);
                break;

            case UIGestureRecognizerState.Failed:
                //?
                break;

            case UIGestureRecognizerState.Possible:
                //?
                break;

        }

        var pt = gesture.LocationInView(this);
        var thumbWidth = CurrentThumbImage.Size.Width;
        var value = 0f;

        if (pt.X <= thumbWidth / 2)
        {
            value = this.MinValue;
        }
        else if (pt.X >= this.Bounds.Size.Width - thumbWidth / 2)
        {
            value = this.MaxValue;
        }
        else
        {
            var percentage = ((pt.X - thumbWidth / 2) / (this.Bounds.Size.Width - thumbWidth));
            var delta = percentage * (this.MaxValue - this.MinValue);
            value = this.MinValue + (float)delta;
        }

        if (gesture.State == UIGestureRecognizerState.Began)
        {               
            UIView.Animate(0.35, 0, UIViewAnimationOptions.CurveEaseInOut,
                () =>
                {
                    this.SetValue(value, true);
                },
                null);
        }
        else
        {
            this.SetValue(value, animated: false);
        }

    }

}

Upvotes: 0

profession
profession

Reputation: 1

Okay, I was able to adapt code on swift from this answer into C# code, so everything works now as expected.

Here is my code:

using MyProject.iOS.Renderers;
using CoreGraphics;
using Foundation;
using System;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(Slider), typeof(ClickableSliderRenderer))]
namespace MyProject.iOS.Renderers
{
public class ClickableSliderRenderer : SliderRenderer
{
    UILongPressGestureRecognizer uiTap;
    protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
    {
        base.OnElementChanged(e);

        uiTap = new UILongPressGestureRecognizer(Tapped);
        uiTap.MinimumPressDuration = 0;
        AddGestureRecognizer(uiTap);

    }

    private void Tapped(object sender)
    {
        CGPoint point = uiTap.LocationInView(this);
        nfloat thumbWidth = Control.Subviews.LastOrDefault().Bounds.Size.Width;
        nfloat value;

        if (point.X <= thumbWidth / 2.0)
            value = Control.MinValue;
        else if (point.X >= Control.Bounds.Size.Width - thumbWidth / 2)
            value = Control.MaxValue;
        else
        {
            var percentage = ((point.X - thumbWidth / 2) / (Control.Bounds.Size.Width - thumbWidth));
            var delta = percentage * (Control.MaxValue - Control.MinValue);

            value = Control.MinValue + delta;
        }

        if (uiTap.State == UIGestureRecognizerState.Began)
        {
            UIView.Animate(0.35, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animation:
                () =>
                {
                    Control.SetValue((float)value, true);
                    Control.SendActionForControlEvents(UIControlEvent.ValueChanged);
                }, completion: null);
        }
        else if (uiTap.State == UIGestureRecognizerState.Changed)
        {
            Control.SetValue((float)value, false);
            Control.SendActionForControlEvents(UIControlEvent.ValueChanged);
        }
        else
        {
            Control.SetValue((float)value, false);
        }

        Control.ThumbRectForBounds(Bounds, Bounds, (float)value);
    }
}
}

Upvotes: 0

Related Questions