Xamarin.Tip – Build Your Own CheckBox in Xamarin.Forms

So how many times have you looked this up? Maybe tried an open source control that didn’t quite work the way you wanted or was super restricted? Just used two different images and switch their IsVisible flag depending if it was tapped or not?
Screw that.

Here’s how you can build your own nice looking checkboxes for iOS and Android with Xamarin.Forms.

Getting Everything Together

So Android has a built in Checkbox view – so the decision on how to handle that is easy. However, iOS doesn’t like checkboxes… note sure why, but they don’t and don’t have a good built in UIControl for it. You could build your own, but I would suggest using BEMCheckbox: https://github.com/Boris-Em/BEMCheckBox

This thing looks good, works well, is super flexible, and well supported. Even better, our super awesome community has wrapped it in a good binding library and stuck it on NuGet!

GitHub: https://github.com/saturdaymp/XPlugins.iOS.BEMCheckBox
NuGet: https://www.nuget.org/packages/SaturdayMP.XPlugins.iOS.BEMCheckBox/

Let’s focus on using this for the iOS renderer, and get started building this damn thing once and for all.

The Xamarin.Forms Side

We’re going to need a bunch of properties for colors for different states, as well as a Command and Event for handling when the check changes. Here’s the class to encapsulate all that:

Checkbox.cs

using System;
using System.Windows.Input;
using Xamarin.Forms;
...

/// <summary>
/// A xamarin.forms custom checkbox control that will use native renderers under the hood
/// </summary>
public class Checkbox : View
{
    public event EventHandler OnCheckChanged;

    public static BindableProperty OutlineColorProperty = BindableProperty.Create(nameof(OutlineColor), typeof(Color), typeof(Checkbox), Color.Black);
    public static BindableProperty InnerColorProperty = BindableProperty.Create(nameof(InnerColor), typeof(Color), typeof(Checkbox), Color.White);
    public static BindableProperty CheckColorProperty = BindableProperty.Create(nameof(CheckColor), typeof(Color), typeof(Checkbox), Color.Black);
    public static BindableProperty CheckedOutlineColorProperty = BindableProperty.Create(nameof(CheckedOutlineColor), typeof(Color), typeof(Checkbox), Color.Black);
    public static BindableProperty CheckedInnerColorProperty = BindableProperty.Create(nameof(CheckedInnerColor), typeof(Color), typeof(Checkbox), Color.White);
    public static BindableProperty IsCheckedProperty = BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(Checkbox), false, BindingMode.TwoWay);
    public static BindableProperty CheckedCommandProperty = BindableProperty.Create(nameof(CheckedCommand), typeof(ICommand), typeof(Checkbox), null);
    public static BindableProperty CheckedCommandParameterProperty = BindableProperty.Create(nameof(CheckedCommandParameter), typeof(object), typeof(Checkbox), null);

    public object CheckedCommandParameter
    {
        get
        {
            return GetValue(CheckedCommandParameterProperty);
        }
        set
        {
            SetValue(CheckedCommandParameterProperty, value);
        }
    }
    public ICommand CheckedCommand
    {
        get
        {
            return (ICommand)GetValue(CheckedCommandProperty);
        }
        set
        {
            SetValue(CheckedCommandProperty, value);
        }
    }
    public bool IsChecked
    {
        get
        {
            return (bool)GetValue(IsCheckedProperty);
        }
        set
        {
            SetValue(IsCheckedProperty, value);
        }
    }
    public Color CheckColor
    {
        get
        {
            return (Color)GetValue(CheckColorProperty);
        }
        set
        {
            SetValue(CheckColorProperty, value);
        }
    }
    public Color InnerColor
    {
        get
        {
            return (Color)GetValue(InnerColorProperty);
        }
        set
        {
            SetValue(InnerColorProperty, value);
        }
    }
    public Color OutlineColor
    {
        get
        {
            return (Color)GetValue(OutlineColorProperty);
        }
        set
        {
            SetValue(OutlineColorProperty, value);
        }
    }
    public Color CheckedInnerColor
    {
        get
        {
            return (Color)GetValue(CheckedInnerColorProperty);
        }
        set
        {
            SetValue(CheckedInnerColorProperty, value);
        }
    }
    public Color CheckedOutlineColor
    {
        get
        {
            return (Color)GetValue(CheckedOutlineColorProperty);
        }
        set
        {
            SetValue(CheckedOutlineColorProperty, value);
        }
    }

    public void FireCheckChange()
    {
        OnCheckChanged?.Invoke(this, new CheckChangedArgs
        {
            IsChecked = IsChecked
        });
    }

    public class CheckChangedArgs : EventArgs
    {
        public bool IsChecked { get; set; }
    }
}

Now that we have a component set up to manage all the different colors and handle the checked state and events, we need to create native renderers to use and update these!

Let’s start with Android.

Android Checkbox Renderer

Android has a built in CheckBox control, so we can use that directly in our renderer. Here’s what that might look like:

AndroidCheckboxRenderer.cs

using System;
using Android.Content;
using Android.Content.Res;
using Android.Support.V7.Widget;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
...

/// <summary>
/// Xamarin.Forms custom renderer for the Checkbox control
/// </summary>
public class CheckboxRenderer : ViewRenderer<Checkbox, AppCompatCheckBox>, CompoundButton.IOnCheckedChangeListener
{
    private const int DEFAULT_SIZE = 28;

    public CheckboxRenderer(Context context) : base(context)
    {
    }

    /// <summary>
    /// Used for registration with dependency service to ensure it isn't linked out
    /// </summary>
    public static void Init()
    {
        // intentionally empty
    }

    /// <summary>
    /// Update element bindable property from event
    /// </summary>
    /// <param name="buttonView">Button view.</param>
    /// <param name="isChecked">If set to <c>true</c> is checked.</param>
    public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
    {
        ((IViewController)Element).SetValueFromRenderer(Checkbox.IsCheckedProperty, isChecked);
        Element.CheckedCommand?.Execute(Element.CheckedCommandParameter);
    }

    public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
    {
        var sizeConstraint = base.GetDesiredSize(widthConstraint, heightConstraint);

        if (sizeConstraint.Request.Width == 0)
        {
            var width = widthConstraint;
            if (widthConstraint <= 0)
            {
                System.Diagnostics.Debug.WriteLine("Default values");
                width = DEFAULT_SIZE;
            }
            else if (widthConstraint <= 0)
            {
                width = DEFAULT_SIZE;
            }

            sizeConstraint = new SizeRequest(new Size(width, sizeConstraint.Request.Height),
                new Size(width, sizeConstraint.Minimum.Height));
        }

        return sizeConstraint;
    }


    /// <summary>
    /// Called when the control is created or changed
    /// </summary>
    /// <param name="e">E.</param>
    protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            if (Control == null)
            {
                var checkBox = new AppCompatCheckBox(Context);

                if (Element.OutlineColor != default(Color))
                {
                    var backgroundColor = GetBackgroundColorStateList(Element.OutlineColor);
                    checkBox.SupportButtonTintList = backgroundColor;
                    checkBox.BackgroundTintList = GetBackgroundColorStateList(Element.InnerColor);
                    checkBox.ForegroundTintList = GetBackgroundColorStateList(Element.OutlineColor);

                }
                checkBox.SetOnCheckedChangeListener(this);
                SetNativeControl(checkBox);
            }

            Control.Checked = e.NewElement.IsChecked;
        }
    }


    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        if (e.PropertyName == nameof(Element.IsChecked))
        {
            Control.Checked = Element.IsChecked;
        }
        else
        {
            var backgroundColor = GetBackgroundColorStateList(Element.CheckColor);
            Control.SupportButtonTintList = backgroundColor;
            Control.BackgroundTintList = GetBackgroundColorStateList(Element.InnerColor);
            Control.ForegroundTintList = GetBackgroundColorStateList(Element.OutlineColor);
        }
    }

    /// <summary>
    /// Sync from native control
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    private void CheckBoxCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
    {
        Element.IsChecked = e.IsChecked;
    }


    private ColorStateList GetBackgroundColorStateList(Color color)
    {
        return new ColorStateList(
            new[]
            {
                new[] {-global::Android.Resource.Attribute.StateEnabled}, // checked
                new[] {-global::Android.Resource.Attribute.StateChecked}, // unchecked
                new[] {global::Android.Resource.Attribute.StateChecked} // checked
            },
            new int[]
            {
                color.WithSaturation(0.1).ToAndroid(),
                color.ToAndroid(),
                color.ToAndroid()
            });
    }

}

This is basically just setting up the ColorStateList for the control based on the colors from the controls and then wiring up the state to and events to the Xamarin.Forms control we created and the native Android Checkbox.

Now let’s look at iOS.

iOS Checkbox Renderer

Unlike Android, iOS does NOT have any form of a Checkbox control. The most common workaround for this is to use a simple image of a check and a square and interchange them and manage the state. But we want more flexibility, animations, and all the fun stuff that comes with a fully built control.

Here’s how we solved this issue for this particular set of Controls – using the BEMCheckBox.

BEMCheckBox is a third party control from Boris-Em and our lovely Xamarin.Community has already created Bindings for it AND a NuGet package. Here are the links:

Be sure to install this NuGet package in your iOS project, and then we can get to work on the renderer:

iOSCheckboxRenderer.cs

using System;
using SaturdayMP.XPlugins.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
...

/// <summary>
/// Xamarin.Forms custom renderer for the Checkbox control
/// </summary>
public class CheckboxRenderer : ViewRenderer<Checkbox, BEMCheckBox>
{
    private const int DEFAULT_SIZE = 28;

    /// <summary>
    /// Used for registration with dependency service to ensure it isn't linked out
    /// </summary>
    public static void Initialize()
    {
        // intentionally empty
    }


    public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
    {
        var sizeConstraint = base.GetDesiredSize(widthConstraint, heightConstraint);

        if (sizeConstraint.Request.Width == 0)
        {
            var width = widthConstraint;
            if (widthConstraint <= 0)
            {
                System.Diagnostics.Debug.WriteLine("Default values");
                width = DEFAULT_SIZE;
            }
            else if (widthConstraint <= 0)
            {
                width = DEFAULT_SIZE;
            }

            sizeConstraint = new SizeRequest(new Size(width, sizeConstraint.Request.Height),
                new Size(width, sizeConstraint.Minimum.Height));
        }

        return sizeConstraint;
    }


    /// <summary>
    /// Called when the control is created or changed
    /// </summary>
    /// <param name="e">E.</param>
    protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            if (Control == null)
            {
                var checkBox = new BEMCheckBox();

                checkBox.BoxType = BEMBoxType.Square;
                checkBox.OnAnimationType = BEMAnimationType.Fill;
                checkBox.OffAnimationType = BEMAnimationType.Fill;

                // set default colors
                UpdateColors(checkBox);

                SetNativeControl(checkBox);
            }

            Control.On = e.NewElement.IsChecked;
            Control.ValueChanged += Control_ValueChanged;
        }
    }

    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        if(e.PropertyName == nameof(Element.IsChecked))
        {
            Control.On = Element.IsChecked; 
        }
        else
        {
            UpdateColors(Control);
        }
    }

    void Control_ValueChanged(object sender, EventArgs e)
    {
        Element.IsChecked = Control.On;
        Element.CheckedCommand?.Execute(Element.CheckedCommandParameter);
    }

    private void UpdateColors(BEMCheckBox nativeCheckBox)
    {
        nativeCheckBox.TintColor = Element.OutlineColor.ToUIColor();
        nativeCheckBox.OffFillColor = Element.InnerColor.ToUIColor();
        nativeCheckBox.OnFillColor = Element.CheckedInnerColor.ToUIColor();
        nativeCheckBox.OnTintColor = Element.CheckedOutlineColor.ToUIColor();
        nativeCheckBox.OnCheckColor = Element.CheckColor.ToUIColor();
    }
}

We follow the same sort of pattern as we did with the Android renderer, but this time we use the BEMCheckBox and wire up the colors, animation styles (which you can change to whatever you want), and setup the events and state.

Using the CheckBox

Now we can use this in our XAML with bindings and styles and everything. We can use it in lists for multi select, or anywhere else!

MainPage.xaml

...
 <StackLayout Orientation="Horizontal" Spacing="16">
    <components:Checkbox CheckedCommandParameter="{Binding .}" IsChecked="{Binding UseExistingAddress}" VerticalOptions="Center" OutlineColor="{DynamicResource PrimaryTextColor}" CheckedOutlineColor="{DynamicResource PrimaryTextColor}" CheckColor="{DynamicResource PrimaryColor}"  WidthRequest="{StaticResource CheckboxSize}" HeightRequest="{StaticResource CheckboxSize}"/>
    <Label Text="Use existing address" VerticalOptions="Center" Style="{DynamicResource BodySecondary}"/>
</StackLayout>
...

See how good it looks?

iOS with empty fill:
Screen Shot 2018-04-09 at 12.05.31 PM
Screen Shot 2018-04-09 at 12.03.19 PM

Android with fill:
Screen Shot 2018-04-09 at 12.05.24 PM
Screen Shot 2018-04-09 at 12.03.40 PM


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Advertisement

Xamarin.Tip – Updating a Xamarin.Forms Button Style When Disabled

Ever wanted to change the way the Xamarin.Forms disabled button looks? For example, changing the background color and text color, or the shape?

Here’s a quick tip with no custom renderers to build a Xamarin.Forms Button that can have its style updated when the IsEnabled is set to false. The concept is basically to create a class that inherits from Button, and keep track of the original Style property, then add a BindableProperty for the style to apply when disabled, and flip between the two when IsEnabled changes.

Here is what that class looks like:

DisablingButton.cs

/// <summary>
/// Xamarin.Forms Button that allows for applying a different style when enabled.
/// </summary>
public class DisablingButton : Button
{
    private Style _normalStyle;

    public static readonly BindableProperty DisabledStyleProperty =
        BindableProperty.Create(nameof(DisabledStyle), typeof(Style), typeof(DisablingButton), null, BindingMode.TwoWay, null, (obj, oldValue, newValue) => { });

    public Style DisabledStyle
    {
        get { return (Style)GetValue(DisabledStyleProperty); }
        set { SetValue(DisabledStyleProperty, value); }
    }

    public DisablingButton()
    {
        _normalStyle = Style;

        PropertyChanged += ExtendedButton_PropertyChanged;

    }

    private void ExtendedButton_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(IsEnabled) && DisabledStyle != null)
        {
            if (IsEnabled)
                Style = _normalStyle;
            else
                Style = DisabledStyle;
        }
    }
}

So now we can use this with two different styles, and bind it all in our XAML. For example –

Styles.xaml

...
<Style x:Key="StickyBlueButton" TargetType="Button">
    <Setter Property="HeightRequest" Value="50"/>
    <Setter Property="BackgroundColor" Value="{DynamicResource BlueAccentColor}"/>
    <Setter Property="TextColor" Value="White"/>
    <Setter Property="BorderRadius" Value="0"/>
    <Setter Property="FontSize" Value="16"/>
    <Setter Property="FontAttributes" Value="Bold"/>
    <Setter Property="Margin" Value="0"/>
    <Setter Property="HorizontalOptions" Value="FillAndExpand"/>
    <Setter Property="FontFamily" Value="{StaticResource ProximaNovaBold}"/>
    <Setter Property="VerticalOptions" Value="End"/>
</Style>
<Style x:Key="StickyBlueButtonDisabled" TargetType="Button">
    <Setter Property="HeightRequest" Value="50"/>
    <Setter Property="BackgroundColor" Value="{DynamicResource LightGray}"/>
    <Setter Property="TextColor" Value="White"/>
    <Setter Property="BorderRadius" Value="0"/>
    <Setter Property="FontSize" Value="16"/>
    <Setter Property="FontAttributes" Value="Bold"/>
    <Setter Property="Margin" Value="0"/>
    <Setter Property="HorizontalOptions" Value="FillAndExpand"/>
    <Setter Property="FontFamily" Value="{StaticResource ProximaNovaBold}"/>
    <Setter Property="VerticalOptions" Value="End"/>
</Style>
...

and then use them in the page…

MainPage.xaml

...
<components:DisablingButton 
    IsEnabled="{Binding IsEnabled}"
    Command="{Binding CreateNewItemCommand}" 
    Style="{DynamicResource StickyBlueButton}" 
    DisabledStyle="{DynamicResource StickyBlueButtonDisabled}" 
    Text="Add item" />
...

And here’s what it looks like!

IsEnabled = true
Screen Shot 2018-04-02 at 4.38.07 PM

IsEnabled = false
Screen Shot 2018-04-02 at 4.41.59 PM


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Tip – Build Your Own AccordionView in Xamarin.Forms

If you’re like me, you probably tried to find some decent “Accordion” style view for your Xamarin.Forms app. You probably found some on NuGet that aren’t supported anymore or some old GitHub repositories. Here’s my advice – give up the search and just give in and build your own. But you don’t have to do it alone! Here is a guide to build a simple but flexible and reusable AccordionView. If enough people like it, maybe we’ll put it up on NuGet for consumption.

Here’s the approach we are taking – We need a list of “sections”. Each Section has a header and the body content. For the sake of the simple example, we’ll do it with just a label body, but this could easily be done with a DataTemplate or more complicated view (I use the HtmlLabel for this irl https://github.com/matteobortolazzo/HtmlLabelPlugin).

Let’s create a view called AccordionSectionView:

AccordionSectionView.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="YourNamespace.AccordionSectionView">
    <ContentView.Content>
        <StackLayout Orientation="Vertical" Spacing="0">
            <Grid x:Name="HeaderView">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="48"/>
                </Grid.ColumnDefinitions>
                <Label x:Name="HeaderLabel"  VerticalOptions="Center" Margin="16" LineBreakMode="WordWrap" />
                <Label x:Name="IndicatorLabel" Style="{DynamicResource BodySecondaryBold}"  FontSize="32" VerticalOptions="Center" HorizontalOptions="Center" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Margin="0,16,16,16" Grid.Column="1"/>
                <Grid.GestureRecognizers>
                    <TapGestureRecognizer Tapped="Handle_Tapped"/>
                </Grid.GestureRecognizers>
            </Grid>
            <Grid x:Name="BodyView">
                <Label x:Name="BodyLabel" Style="{DynamicResource Body}" FontSize="16" Margin="16"/>
            </Grid>
        </StackLayout>
    </ContentView.Content>
</ContentView>

So we have a StackLayout with a container Grid for the header and our header has a TapGestureRecognizer.

Now let’s look at how the code behind wires it all up.

AccordionSectionView.xaml.cs

/// <summary>
/// Accordion section view.
/// </summary>
public partial class AccordionSectionView : ContentView
{
    #region Bindable Properties
    public static BindableProperty HeaderBackgroundColorProperty = 
        BindableProperty.Create(nameof(HeaderBackgroundColor),                                                                                                      
            typeof(Color),                                                                                                  
            typeof(AccordionSectionView),                                                                                              
            defaultValue: Color.Transparent,                                                                                               
            propertyChanged: (bindable, oldVal, newVal) =>                                                                                                       
            {                                                                                                    
                ((AccordionSectionView)bindable).UpdateHeaderBackgroundColor();                                                                                                
            });

        public static BindableProperty HeaderOpenedBackgroundColorProperty = 
            BindableProperty.Create(nameof(HeaderOpenedBackgroundColor),                                                                                                  
                typeof(Color),                                                                                                       
                typeof(AccordionSectionView),
                defaultValue: Color.Transparent,                                                                                                   
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                               
                {                                                                                                  
                    ((AccordionSectionView)bindable).UpdateHeaderBackgroundColor();                                                                                              
                });

        public static BindableProperty HeaderTextColorProperty =
            BindableProperty.Create(nameof(HeaderTextColor),                                                                                        
                typeof(Color),                                                                                                 
                typeof(AccordionSectionView),                                                                                        
                defaultValue: Color.Black,                                                                                          
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                         
                {                                                                                             
                    ((AccordionSectionView)bindable).UpdateHeaderTextColor((Color)newVal);                                                                                         
                });

        public static BindableProperty HeaderTextProperty =
            BindableProperty.Create(nameof(HeaderTextProperty),                                                                                    
                typeof(string),                                                                                    
                typeof(AccordionSectionView),                                                                                   
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                   
                {                                                                                      
                    ((AccordionSectionView)bindable).UpdateHeaderText((string)newVal);                                                                                   
                });

        public static BindableProperty BodyTextColorProperty =
            BindableProperty.Create(nameof(BodyTextColor),                                                                                      
                typeof(Color),                                                                                      
                typeof(AccordionSectionView),                                                                                       
                defaultValue: Color.Black,                                                                                       
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                       
                {                                                                                         
                    ((AccordionSectionView)bindable).UpdateBodyTextColor((Color)newVal)                                                                                     
                });

        public static BindableProperty BodyTextProperty = 
            BindableProperty.Create(nameof(BodyText),                                                                                 
                typeof(string),                                                                                  
                typeof(AccordionSectionView),                                                                                  
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                  
                {                                                                                      
                    ((AccordionSectionView)bindable).UpdateBodyText((string)newVal);                                                                                 
                });


        public static BindableProperty IsBodyVisibleProperty = 
            BindableProperty.Create(nameof(IsBodyVisible),                                                                                             
                typeof(bool),                                                                                       
                typeof(AccordionSectionView),                                                                                      
                defaultValue: true,
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                       
                {                                                                                           
                    ((AccordionSectionView)bindable).UpdateBodyVisibility((bool)newVal);                                                                                          
                });
    #endregion

    #region Public Properties
    public Color HeaderBackgroundColor
    {
        get
        {
            return (Color)GetValue(HeaderBackgroundColorProperty);
        }
        set
        {
            SetValue(HeaderBackgroundColorProperty, value);
        }
    }
    public Color HeaderOpenedBackgroundColor
    {
        get
        {
            return (Color)GetValue(HeaderOpenedBackgroundColorProperty);
        }
        set
        {
                SetValue(HeaderOpenedBackgroundColorProperty, value);
        }
    }
    public Color HeaderTextColor
    {
        get
        {
            return (Color)GetValue(HeaderTextColorProperty);
        }
        set
        {
            SetValue(HeaderTextColorProperty, value);
        }
    }
    public string HeaderText
    {
        get
        {
            return (string)GetValue(HeaderTextProperty);
        }
        set
        {
            SetValue(HeaderTextProperty, value);
        }
    }
    public Color BodyTextColor
    {
        get
        {
            return (Color)GetValue(BodyTextColorProperty);
        }
        set
        {
            SetValue(BodyTextColorProperty, value);
        }
    }
    public string BodyText
    {
        get
        {
            return (string)GetValue(BodyTextProperty);
        }
        set
        {
            SetValue(BodyTextProperty, value);
        }
    }

    public bool IsBodyVisible
    {
        get
        {
            return (bool)GetValue(IsBodyVisibleProperty);
        }
        set
        {
            SetValue(IsBodyVisibleProperty, value);
        }
    }

    #endregion


    public AccordionSectionView()
    {
        InitializeComponent();
        IsBodyVisible = false; 
        if (Resources != null)
        {
            Resources.MergedWith = typeof(PrimaryTheme);
        }
        else
        {
            Resources = new ResourceDictionary
            {
                MergedWith = typeof(PrimaryTheme)
            };
        }
    }

    /// <summary>
    /// Updates the color of the header background.
    /// </summary>
    /// <param name="color">Color.</param>
    public void UpdateHeaderBackgroundColor(Color color)
    {
        HeaderView.BackgroundColor = color;
    }

    /// <summary>
    /// Updates the color of the header background.
    /// </summary>
    public void UpdateHeaderBackgroundColor()
    {
        if (IsBodyVisible)
        {
            HeaderView.BackgroundColor = HeaderOpenedBackgroundColor;
            BodyView.BackgroundColor = HeaderOpenedBackgroundColor;
        }
        else
        {
            HeaderView.BackgroundColor = HeaderBackgroundColor;
        }
    }

    /// <summary>
    /// Updates the color of the header text.
    /// </summary>
    /// <param name="color">Color.</param>
    public void UpdateHeaderTextColor(Color color)
    {
        HeaderLabel.TextColor = color;
    }

    /// <summary>
    /// Updates the color of the body text.
    /// </summary>
    /// <param name="color">Color.</param>
    public void UpdateBodyTextColor(Color color)
    {
        BodyLabel.TextColor = color;
    }

    /// <summary>
    /// Updates the header text.
    /// </summary>
    /// <param name="text">Text.</param>
    public void UpdateHeaderText(string text)
    {
        HeaderLabel.Text = text;
    }

    /// <summary>
    /// Updates the body text.
    /// </summary>
    /// <param name="text">Text.</param>
    public void UpdateBodyText(string text)
    {
        BodyLabel.Text = text;
    }

    public void UpdateBodyVisibility(bool isVisible)
    {
        BodyView.IsVisible = isVisible;
        IndicatorLabel.Text = "+";
        if(isVisible)
        {
            IndicatorLabel.RotateTo(45, 100);
        }
        else
        {
            IndicatorLabel.RotateTo(0, 100);
        }
    }

    private void Handle_Tapped(object sender, System.EventArgs e)
    {
        IsBodyVisible = !IsBodyVisible;
        UpdateHeaderBackgroundColor();
    }
}

We start by wiring up some bindable properties for the look and feel of the header and body sections, and then handle animations of the IndicatorLabel when the body is opened and closed. These even lets us update the content when the body is opened or closed.

Now we can use this in our pages. Bonus points when used with me DynamicStackLayout control to create dynamic bindable accordions!

MainPage.xaml

...
 <suave:DynamicStackLayout x:Name="Accordion" Orientation="Vertical" ItemsSource="{Binding Items}" Spacing="1" BackgroundColor="{DynamicResource AlternateBackground}">
    <suave:DynamicStackLayout.ItemTemplate>
        <DataTemplate>
                <components:AccordionSectionView HeaderText="{Binding Title}" IsBodyVisible="False" HeaderBackgroundColor="White" HeaderOpenedBackgroundColor="{DynamicResource AlternateBackground}" HeaderTextColor="Black" BodyTextColor="Black" BodyText="{Binding HtmlContent}" />
        </DataTemplate>
    </suave:DynamicStackLayout.ItemTemplate>
</suave:DynamicStackLayout>
...

Now all together we have something pretty cool!
Screen Shot 2018-03-26 at 4.40.34 PM


Screen Shot 2018-03-26 at 4.40.48 PM

And that’s it! Let me know what you’ve done to build your own Accordions and collapsible views!



If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Tip – iOS CardView

We’ve talked here before plenty of times on ways we can bring Material Design to iOS, but mostly in the context of Xamarin.Forms. In this quick Xamarin.Tip, we will look at a super simple control to create a CardView in Xamarin.iOS – not Xamarin.Forms.

If you’re looking for the Xamarin.Forms implementation of this concept, check out this post: Xamarin.Tips – Making Your iOS Frame Shadows More Material

I may end up making a quick NuGet package for this control, even just to save me some time with implementing it over and over again, but don’t hold me to it.

Make Your Own CardView

The goal here is to create a view that fills the need of the Material Design CardView spec, and make it customizable within the storyboard editor (oh yeah, storyboard support is awesome).

This can all be achieved with a super simple class:

CardView.cs

    /// <summary>
    /// A simple view with child views to have a set shadow in design time.
    /// This can be imported into a storyboard or xib with these design time variables
    /// </summary>
    [Register("CardView"), DesignTimeVisible(true)]
    public class CardView : UIView
    {
        private float cornerRadius;
        private UIColor shadowColor;
        private int shadowOffsetHeight;
        private int shadowOffsetWidth;
        private float shadowOpacity;

        [DisplayName("Shadow Opacity"), Export("shadowOpacity"), Browsable(true)]
        public float ShadowOpacity
        {
            get { return shadowOpacity; }
            set
            {
                shadowOpacity = value;
                SetNeedsDisplay();
            }
        }

        [DisplayName("Shadow Offset Width"), Export("shadowOffsetWidth"), Browsable(true)]
        public int ShadowOffsetWidth
        {
            get { return shadowOffsetWidth; }
            set
            {
                shadowOffsetWidth = value;
                SetNeedsDisplay();
            }
        }

        [DisplayName("Shadow Offset Height"), Export("shadowOffsetHeight"), Browsable(true)]
        public int ShadowOffsetHeight
        {
            get { return shadowOffsetHeight; }
            set
            {
                shadowOffsetHeight = value;
                SetNeedsDisplay();
            }
        }

        [DisplayName("Corner Radius"), Export("CornerRadius"), Browsable(true)]
        public float CornerRadius
        {
            get { return cornerRadius; }
            set
            {
                cornerRadius = value;
                SetNeedsDisplay();
            }
        }

        [DisplayName("Shadow Color"), Export("ShadowColor"), Browsable(true)]
        public UIColor ShadowColor
        {
            get { return shadowColor; }
            set
            {
                shadowColor = value;
                SetNeedsDisplay();
            }
        }

        public CardView()
        {
            Initialize();
        }

        public CardView(NSCoder coder) : base(coder)
        {
            Initialize();
        }

        protected CardView(NSObjectFlag t) : base(t)
        {
            Initialize();
        }

        protected internal CardView(IntPtr handle) : base(handle)
        {
        }

        public CardView(CGRect frame) : base(frame)
        {
            Initialize();
        }


        public override void AwakeFromNib()
        {
            base.AwakeFromNib();
            Initialize();
        }

        private void Initialize()
        {
            ShadowColor = UIColor.Gray;
            CornerRadius = 0;
            ShadowOffsetHeight = 1;
            ShadowOffsetWidth = 0;
            ShadowOpacity = 0.2f;
            SetupCard();
        }

        private void SetupCard()
        {
            Layer.CornerRadius = CornerRadius;
            UIBezierPath bezierPath = UIBezierPath.FromRoundedRect(Bounds, CornerRadius);
            Layer.MasksToBounds = false;
            Layer.ShadowColor = ShadowColor.CGColor;
            Layer.ShadowOffset = new CGSize(shadowOffsetWidth, shadowOffsetHeight);
            Layer.ShadowOpacity = shadowOpacity;
            Layer.ShadowPath = bezierPath.CGPath;
        }

        public override void LayoutSubviews()
        {
            base.LayoutSubviews();
            SetupCard();
        }
    }

What’s going on here is that we register properties such as the CornerRadius and all the shadow properties, and also register the CardView class itself with decorator attributes DesignTimeVisible, Register, Export, and DisplayName.

So now, all we have to do is go to our storyboard, and change the class of a UIView to our CardView, then we will see these properties as options to edit.

You can now use the storyboard editor to add subview to the CardView and create a UI that is simply awesome, like this!
Screen Shot 2018-03-23 at 2.03.00 PM


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Tip – iOS Pill Button (with Storyboard Support)

Here’s another simple but useful control for Xamarin.iOS – the PillButton. The name describes it pretty well, it’s a button shaped like a pill or capsule and allows for managing the border color within your storyboard files by exposing an outlet for the color.

In the end, we want something that looks like this:
Screen Shot 2018-03-14 at 2.45.59 PM

Let’s create a simple PillButton class:

PillButton.cs

    /// <summary>
    /// Pill button. A button in a capsule shape
    /// </summary>
    [Register("PillButton"), DesignTimeVisible(true)]
    public class PillButton : UIButton
    {
        [DisplayName("Border Color"), Export("BorderColor"), Browsable(true)]
        public UIColor BorderColor { get; set; } = UIColor.Clear;

        public PillButton(IntPtr handle)
            : base(handle)
        {

        }

        public PillButton(CGRect frame)
            : base(frame)
        {
            SetupBorder();
        }

        public override void AwakeFromNib()
        {
            base.AwakeFromNib();
            SetupBorder();
        }

        /// <summary>
        /// Sets up the border radius and the color
        /// </summary>
        protected void SetupBorder()
        {
            Layer.BorderColor = BorderColor.CGColor;
            Layer.BorderWidth = 2;
            Layer.CornerRadius = Frame.Height / 2;
            Layer.MasksToBounds = true;
        }
    }

Let’s breakdown how this works:

  • We setup the border to use the Border color property and apply it to the layer
  • Apply the CornerRadius as 1/2 the height of the final frame
  • Call this SetupBorder() method from the constructor as well as in AwakeFromNib() which is called by the storyboard renderer.
  • We also need to decorate our class with a Register and DesignTimeVisible attribute in order for it to be usable in the storyboard
  • Lastly we need to decorate our BorderColor property with [DisplayName("Border Color"), Export("BorderColor"), Browsable(true)] so it can be used in the storyboard as well.

Now in our storyboard files, we can drag and drop a UIButton on the ViewController, then set the class to our PillButton, and once we do we can see the property exposed for the BorderColor:
Screen Shot 2018-03-14 at 2.47.41 PM

Also, we can even see these applied in our storyboard so that it really does look like it will in the app:

Screen Shot 2018-03-14 at 2.47.17 PM

And that’s it! The moral of the story is to not be afraid to subclass controls to make your life easier when re-using them. It’s a normal practice in iOS development, and that practice comes right on over to our Xamarin.iOS development!


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Tip – Bindable iOS UIRefreshControl

Here’s another super simple tip that has made my life easier with a basic control for iOS – the BindableRefreshControl. The premise is to wrap the BeginRefreshing() and EndRefreshing() calls into a bindable property that can be applied with a one-way binding from a ViewModel.

To premise this a bit, the existing UIRefreshControl from iOS has the two methods mentioned above as well as a Refreshing bool property that is readonly. The need for wrapping this in a property came from wanting to bind my native ViewControllers to a ViewModel and a property such as IsLoading.

So here’s the control:

BindableRefreshControl.cs

    /// <summary>
    /// Bindable refresh control.
    /// Adds a refresh event to bind to.
    /// </summary>
    public class BindableRefreshControl : UIRefreshControl
    {
        private bool _isRefreshing;
        public bool IsRefreshing
        {
            get
            {
                return _isRefreshing;
            }
            set
            {
                _isRefreshing = value;
                if (value)
                    BeginRefreshing();
                else
                    EndRefreshing();
            }
        }
    }

Yeah that’s literally it.

However, I now can use it like this (using MvvmLight):

MainViewModel.cs

public class MainViewModel : ViewModelBase
{
    private bool _isLoading;
    public bool IsLoading
    {
        get
        {
            return _isLoading;
        }
        set
        {
            Set(ref _isLoading, value);
        }
    }
}

And in the ViewController:
MainViewController.cs

public class MainViewController : UIViewController
{
    private BindableRefreshControl _refreshControl;
    private MainViewModel _viewModel;
    protected override void ViewDidLoad() 
    {
        _viewModel = new MainViewModel();
        _refreshControl = new BindableRefreshControl();
        ...
        ...
        this.SetBinding(() => _viewModel.IsLoading,
                        () => _refreshControl.IsRefreshing));
    }
}

And like that, your native iOS spinners can be easily bound to your view model properties!


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Forms MarkdownTextView NuGet Announcement!

A while back I talked about building your own Markdown Label / TextView to your Xamarin applications including Xamarin Native and Xamarin Forms. I decided to put the control I’ve been working off of over on NuGet so anyone can use its basic form.

Note that this control isn’t final, I am working on some refactoring and enhancements, but if you want something that just works, then give it a shot, or contribute to the repository!

Previous Posts

Down below you can find the documentation as it’s found on the GitHub repo!

MarkdownTextView

alt tag

A Xamarin.Forms component to display markdown text in a TextView

Installation

Now on NuGet!

Install-Package MarkdownTextView.Forms

https://www.nuget.org/packages/MarkdownTextView.Forms

Usage

  • Call Init before calling Xamarin.Forms.Init()
  • iOS: SPControls.MarkdownTextView.iOS.MarkdownTextView.Init();
  • Android: SPControls.MarkdownTextView.Droid.MarkdownTextView.Init();
  • Use the control
  • In Xaml:
<ContentPage ...
             xmlns:spcontrols="clr-namespace:SPControls.Forms;assembly=SPControls.MarkdownTextView"
             ...>
  <spcontrols:MarkdownTextView Markdown="{Binding MarkdownString}" />
</ContentPage>
  • or in C#:
var mdTextView = new MarkdownTextView();
mdTextView.Markdown = "# this is my *header* tag";

TODO

  • Add other properties for updating markdown settings
  • Add text color settings
  • Add UWP Support

Xamarin.Controls – Material Form Control Updates

A while back I put together a GitHub repository for Material Design Controls for form elements (Entry, Picker, etc.) for Xamarin.Forms (iOS, Android, and UWP).
You can find some of the original posts about that here:

And you can find the GitHub repo and NuGet package here:

In this post, I wanted to focus on some of the newer features I’ve added to the latest 2018.1.28-pre1 release.

The gist is:

  • Invalid vs. Valid State
  • Font updates
  • Color flexibility

With these, you can now specify updated default colors, invalid state colors, and the original focused accent colors.
Here’s an example of a Style resource that uses these:

    <!-- Material Entry Styles -->
    <Style x:Key="PrimaryMaterialEntry" TargetType="material:MaterialEntry">
        <Setter Property="AccentColor" Value="{DynamicResource PrimaryColor}"/>
        <Setter Property="DefaultColor" Value="Gray"/>
        <Setter Property="InvalidColor" Value="Red"/>
    </Style>

What does validation state mean? You can attach behaviors to easily add validation to your material form controls. Here is an example behavior on the MaterialEntry that requires a length, and sets the state:

   /// <summary>
    /// Material entry length behavior. Allows for the limitation of the text with a min and max length
    /// </summary>
    public class MaterialEntryLengthValidationBehavior : Behavior<MaterialEntry>
    {
        public int MaxLength { get; set; }
        public int MinLength { get; set; } = 0;

        /// <summary>
        /// Attach events on attachment to view
        /// </summary>
        /// <param name="bindable">Bindable.</param>
        protected override void OnAttachedTo(MaterialEntry bindable)
        {
            base.OnAttachedTo(bindable);
            bindable.TextChanged += OnEntryTextChanged;
            bindable.EntryUnfocused += Bindable_EntryUnfocused;
        }

        /// <summary>
        /// Detach events on detaching from view
        /// </summary>
        /// <param name="bindable">Bindable.</param>
        protected override void OnDetachingFrom(MaterialEntry bindable)
        {
            base.OnDetachingFrom(bindable);
            bindable.TextChanged -= OnEntryTextChanged;
            bindable.EntryUnfocused -= Bindable_EntryUnfocused;
        }

        /// <summary>
        /// Stop text input once max is hit
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        void OnEntryTextChanged(object sender, TextChangedEventArgs e)
        {
            var entry = (Entry)sender;

            if (entry.Text == null)
                return;

            // if Entry text is longer than valid length
            if (entry.Text.Length > this.MaxLength)
            {
                string entryText = entry.Text;

                entryText = entryText.Remove(entryText.Length - 1); // remove last char

                entry.Text = entryText;
            }


        }

        /// <summary>
        /// Set invalid on unfocus if the min is not met
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        void Bindable_EntryUnfocused(object sender, FocusEventArgs e)
        {
            var entry = (MaterialEntry)sender;
            if (MinLength > 0)
            {
                if (entry.Text == null || entry.Text.Length < this.MinLength)
                {
                    entry.IsValid = false;
                }
                else
                {
                    entry.IsValid = true;
                }
            }
        }
    }

Then you can attach it to a MaterialEntry

<material:MaterialEntry Placeholder="CVV" Keyboard="Numeric" Text="{Binding CVV}" Style="{DynamicResource PrimaryMaterialEntry}">
    <material:MaterialEntry.Behaviors>
        <behaviors:MaterialEntryLengthValidationBehavior MaxLength="3" MinLength="3"/>
    </material:MaterialEntry.Behaviors>
</material:MaterialEntry>

And then you get:
Screen Shot 2018-02-22 at 4.19.52 PMScreen Shot 2018-02-22 at 4.19.57 PMScreen Shot 2018-02-22 at 4.20.03 PMScreen Shot 2018-02-22 at 4.20.11 PM

These validation states, and updated color properties are available on all the Material Forms controls, so install the preview nuget package and get started!

Install-Package MaterialFormControls -Version 2018.1.28-pre1

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.NuGet – Native Behaviors

I’ve been doing a lot of mix development between Xamarin.Forms and Native Xamarin iOS/Android. Because of this, we abstract away concepts that can be shared regardless of using Xamarin.Forms or not. One thing I personally like, and use all the time, is Xamarin.Forms Behaviors. I wanted a way to use the same pattern with my native components. I went ahead and created NativeBehaviors.

Get it

Concepts

Like mentioned above, we wanted to follow the same style as Xamarin.Forms Behaviors without having a dependency on Xamarin.Forms. You can create a NativeBehavior for any native control, and then handle attaching and detaching the behavior within the lifecycle of your ViewController, Activity, Fragment, etc.

This helps us keep our event handling cleaned up in memory while still being useful, easy to follow, re-usable, and flexible.

Documentation

Read below for the documentation on how to build your own NativeBehaviors and start making your controls more flexible!

NativeBehaviors

A behaviors implementation for Xamarin Native controls meant to reflect the usefulness of Xamarin.Forms.Behaviors

Installation

This project is up on NuGet now:

Install-Package NativeBehaviors

Usage

These behaviors are meant to reflect the same use as Xamarin.Forms Behaviors, but require a little more work to manage the lifecycle of events since native views don’t have a collection of Behaviors as a property.

  1. Create a custom Behavior on your native platform. This example will use an iOS UITextField

TextFieldPhoneMaskBehavior.cs

/// <summary>
/// Text field phone mask behavior.
/// Format of (###) ###-####
/// </summary>
public class TextFieldPhoneMaskBehavior : NativeBehavior<UITextField>
{
    public override string BehaviorName => nameof(TextFieldPhoneMaskBehavior);

    private string _previousText = string.Empty;
    /// <summary>
    /// Attach text changed event
    /// </summary>
    /// <param name="bindable">Bindable.</param>
    protected override void OnAttachedTo(UITextField bindable)
    {
        bindable.EditingChanged += Bindable_TextChanged;
    }

    /// <summary>
    /// Detach event
    /// </summary>
    /// <param name="bindable">Bindable.</param>
    protected override void OnDetachingFrom(UITextField bindable)
    {
        bindable.EditingChanged -= Bindable_TextChanged;
    }

    /// <summary>
    /// Apply mask
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    void Bindable_TextChanged(object sender, EventArgs e)
    {
        var textField = sender as UITextField;

        // only apply change if typing forward
        if (textField != null && textField.Text.Length > _previousText.Length)
        {
            if (textField.Text.Length == 1)
            {
                // we have our first number, add the ( behind it
                textField.Text = $"({textField.Text}";
            }
            if (textField.Text.Length == 4)
            {
                // finish the area code
                textField.Text += ") ";
            }
            if (textField.Text.Length == 9)
            {
                // add dash
                textField.Text += "-";
            }
        }
        _previousText = textField.Text;
    }
}
  1. Attach Behavior in lifecycle

MyViewController.cs

using NativeBehaviors; // needed for extension method
...
private TextFieldPhoneMaskBehavior _phoneMaskBehavior;

protected override void ViewWillAppear(bool animated)
{
    ...
    _phoneMaskBehavior = new TextFieldPhoneMaskBehavior();
    myTextField.AttachBehavior(_phoneMaskBehavior);
    ...
}
...
  1. Detach Behavior in lifecycle

MyViewController.cs

using NativeBehaviors; // needed for extension method
...
protected override void ViewDidDisappear()
{
    ...
    myTextField.DetachBehavior(_phoneMaskBehavior);
    ...
}
...

Use the OnCreate and OnDestroy events in your Android Activities.

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Tip – Xamarin.Forms Long Press Effect

Here’s a quick and helpful tool to use in your Xamarin.Forms applications! How many times have you wanted to add a long press handler? Seems like something that should be a simple Gesture built into the platform, but we have to fend for ourselves. Luckily the solution is pretty simple using Xamarin.Forms Effects!

Let’s first create our shared Effect in our shared code:

LongPressedEffect.cs

    /// <summary>
    /// Long pressed effect. Used for invoking commands on long press detection cross platform
    /// </summary>
    public class LongPressedEffect : RoutingEffect
    {
        public LongPressedEffect() : base("MyApp.LongPressedEffect")
        {
        }

        public static readonly BindableProperty CommandProperty = BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(LongPressedEffect), (object)null);
        public static ICommand GetCommand(BindableObject view)
        {
            return (ICommand)view.GetValue(CommandProperty);
        }

        public static void SetCommand(BindableObject view, ICommand value)
        {
            view.SetValue(CommandProperty, value);
        }


        public static readonly BindableProperty CommandParameterProperty = BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(LongPressedEffect), (object)null);
        public static object GetCommandParameter(BindableObject view)
        {
            return view.GetValue(CommandParameterProperty);
        }

        public static void SetCommandParameter(BindableObject view, object value)
        {
            view.SetValue(CommandParameterProperty, value);
        }
    }

Now we have 2 bindable properties – the Command that we want to bind when the long press is detected and the CommandParameter to pass into the Command.

We can use these in our native Effect implementations to invoke when the press is detected. Let’s create our Android implementation.

AndroidLongPressedEffect.cs

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(AndroidLongPressedEffect), "LongPressedEffect")]
namespace AndroidAppNamespace.Effects
{
    /// <summary>
    /// Android long pressed effect.
    /// </summary>
    public class AndroidLongPressedEffect : PlatformEffect
    {
        private bool _attached;

        /// <summary>
        /// Initializer to avoid linking out
        /// </summary>
        public static void Initialize() { }

        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="T:Yukon.Application.AndroidComponents.Effects.AndroidLongPressedEffect"/> class.
        /// Empty constructor required for the odd Xamarin.Forms reflection constructor search
        /// </summary>
        public AndroidLongPressedEffect()
        {
        }

        /// <summary>
        /// Apply the handler
        /// </summary>
        protected override void OnAttached()
        {
            //because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
            if (!_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick += Control_LongClick;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick += Control_LongClick;
                }
                _attached = true;
            }
        }

        /// <summary>
        /// Invoke the command if there is one
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
        {
            Console.WriteLine("Invoking long click command");
            var command = LongPressedEffect.GetCommand(Element);
            command?.Execute(LongPressedEffect.GetCommandParameter(Element));
        }

        /// <summary>
        /// Clean the event handler on detach
        /// </summary>
        protected override void OnDetached()
        {
            if (_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick -= Control_LongClick;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick -= Control_LongClick;
                }
                _attached = false;
            }
        }
    }

And now for iOS:

iOSLongPressedEffect.cs

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(iOSLongPressedEffect), "LongPressedEffect")]
namespace iOSNamespace.Effects
{
    /// <summary>
    /// iOS long pressed effect
    /// </summary>
    public class iOSLongPressedEffect : PlatformEffect
    {
        private bool _attached;
        private readonly UILongPressGestureRecognizer _longPressRecognizer;
        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="T:Yukon.Application.iOSComponents.Effects.iOSLongPressedEffect"/> class.
        /// </summary>
        public iOSLongPressedEffect()
        {
            _longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
        }

        /// <summary>
        /// Apply the handler
        /// </summary>
        protected override void OnAttached()
        {
            //because an effect can be detached immediately after attached (happens in listview), only attach the handler one time
            if (!_attached)
            {
                Container.AddGestureRecognizer(_longPressRecognizer);
                _attached = true;
            }
        }

        /// <summary>
        /// Invoke the command if there is one
        /// </summary>
        private void HandleLongClick()
        {
            var command = LongPressedEffect.GetCommand(Element);
            command?.Execute(LongPressedEffect.GetCommandParameter(Element));
        }

        /// <summary>
        /// Clean the event handler on detach
        /// </summary>
        protected override void OnDetached()
        {
            if (_attached)
            {
                Container.RemoveGestureRecognizer(_longPressRecognizer);
                _attached = false;
            }
        }

    }

Now that we have our 2 implementations, let’s use it in our XAML!

MyPage.xaml

<Label Text="Long Press Me!" effects:LongPressedEffect.Command="{Binding ShowAlertCommand}" effects:LongPressedEffect.CommandParameter="{Binding .}">
    <Label.Effects>
        <effects:LongPressedEffect />
    </Label.Effects>
</Label>

Now you can start handling long presses on any control! If you want to add it to a ListView just attach it to either the ViewCell or the internal View of the cell.

 

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.