Xamarin.Tip – Add Easter Eggs on Shake

Here’s a quick and fun tip to add a little flare to your Xamarin applications whether in Xamarin.Forms or Xamarin Native! In a later post, we will also look at how to use the Shake Gesture to create useful tools for yourself as a developer, or for your users! This fun post is the foundation for the more serious stuff and will show you how to use the Shake Recognition to do just about anything you want.

In order to handle shake gestures natively, we have to write some Xamarin Native code, but we will assume you are using Xamarin.Forms. We’ll start with Android.

Android Shake Detection

Android is the more complicated between the two major platforms. The process is basically this:

  • Listen to Accelerometer events
  • Check value changes of the x, y, and z axis
  • Compare deltas against a threshold to determine if motion is a shake
  • Execute the fun stuff!

Let’s do this in the MainActivity since every Xamarin.Forms application needs at least that. If you are using Xamarin Native or a mix, then you may want to implement this elsewhere such as your Application class or abstracted with a shared BaseActvity or add it as a field to the Activities you want it in.

MainActivity.cs

/// <summary>
/// Main Activity and entry point for Xamarin.Forms
/// </summary>
public class MainActivity : Activity, Android.Hardware.ISensorEventListener
{
    #region Shake properties
    bool hasUpdated = false;
    DateTime lastUpdate;
    float lastX = 0.0f;
    float lastY = 0.0f;
    float lastZ = 0.0f;

    const int ShakeDetectionTimeLapse = 250;
    const double ShakeThreshold = 800;
    #endregion


    /// <summary>
    /// Sets up the internal lifecycle for registering views and the shake detection
    /// </summary>
    /// <param name="savedInstanceState">Saved instance state.</param>
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        // all the other set up stuff here

        // shake management set up
        // Register this as a listener with the underlying service.
        var sensorManager = GetSystemService(SensorService) as Android.Hardware.SensorManager;
        var sensor = sensorManager.GetDefaultSensor(Android.Hardware.SensorType.Accelerometer);
        sensorManager.RegisterListener(this, sensor, Android.Hardware.SensorDelay.Game);

    }


    #region Android.Hardware.ISensorEventListener implementation

    /// <summary>
    /// Handles when the sensor range changes
    /// </summary>
    /// <param name="sensor">Sensor.</param>
    /// <param name="accuracy">Accuracy.</param>
    public void OnAccuracyChanged(Android.Hardware.Sensor sensor, Android.Hardware.SensorStatus accuracy)
    {
    }

    /// <summary>
    /// Detects sensor changes and is set up to listen for shakes.
    /// </summary>
    /// <param name="e">E.</param>
    public async void OnSensorChanged(Android.Hardware.SensorEvent e)
    {
        if (e.Sensor.Type == Android.Hardware.SensorType.Accelerometer)
        {
            var x = e.Values[0];
            var y = e.Values[1];
            var z = e.Values[2];

            // use to check against last time it was called so we don't register every delta
            var currentTime = DateTime.Now;
            if (hasUpdated == false)
            {
                hasUpdated = true;
                lastUpdate = currentTime;
                lastX = x;
                lastY = y;
                lastZ = z;
            }
            else
            {
                if ((currentTime - lastUpdate).TotalMilliseconds > ShakeDetectionTimeLapse)
                {
                    var diffTime = (float)(currentTime - lastUpdate).TotalMilliseconds;
                    lastUpdate = currentTime;
                    var total = x + y + z - lastX - lastY - lastZ;
                    var speed = Math.Abs(total) / diffTime * 10000;

                    if (speed > ShakeThreshold)
                    {
                        // We have a shake folks!
                        await EasterEggAsync();
                    }

                    lastX = x;
                    lastY = y;
                    lastZ = z;
                }
            }
        }
    }

    /// <summary>
    /// Execute the easter egg async.
    /// </summary>
    protected virtual async Task EasterEggAsync()
    {
        // HEY OVER HERE! DO SOMETHING COOL!
    }
#endregion

}

Now all you have to do is add whatever your easter egg logic is in the EasterEggAsync method! In this method, you can also track what the current Xamarin.Forms Page is visible by tracking the App.Current.MainPage to add some context to the page your in.

Now let’s look at the iOS implementation.

iOS Shake Detection

iOS makes this process a whole lot easier. We don’t have to do any crazy accelerometer calculations, state tracking, or any of the gross stuff above for Android. Instead, we simply use the built in Gesture API from UIKit and register that we are listening to these types of gestures in the AppDelegate:

AppDelegate.cs

[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{

    public override UIWindow Window { get; set; }
    public static AppDelegate Current { get; private set; }


    public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
    {
        // Allow shake detection
        UIApplication.SharedApplication.ApplicationSupportsShakeToEdit = true;

        // make this more accessible
        Current = this;
        Forms.Init();

        // we did it! the app is launched!
        return true;
    }
}

Now that we have registered with the UIApplication.SharedApplication.ApplicationSupportsShakeToEdit, we can start overriding the gesture recognizer. If you’re using Xamarin.Forms, you can do this right in the AppDelegate but if you’re not, then you can use this override method in any ViewController or even any sub-UIView class!

/// <summary>
/// Handles when a general motion has ended on the view controller. 
/// We use this to detect the shake of the phone to execute and easter egg
/// </summary>
/// <param name="motion">Motion.</param>
/// <param name="evt">Evt.</param>
public override async void MotionEnded(UIEventSubtype motion, UIEvent evt)
{
    Console.WriteLine("Motion detected");
    if (motion == UIEventSubtype.MotionShake)
    {
        Console.WriteLine("and was a shake");

        await EasterEggAsync();
    }
}

/// <summary>
/// Execute the easter egg async.
/// </summary>
protected virtual async Task EasterEggAsync()
{
    // HEY OVER HERE! DO SOMETHING COOL!
}

// if you're in the view controller then you also need to:

public override bool CanBecomeFirstResponder
{
    get
    {
        return true;
    }
}

/// <summary>
/// Enables the first responder set to allow for shake detection
/// </summary>
/// <param name="animated">If set to <c>true</c> animated.</param>
public override void ViewWillAppear(bool animated)
{
    base.ViewWillAppear(animated);
    this.BecomeFirstResponder();
    // .. other logic
}

Doing this enabled the gesture to be picked up at the application level or in the current UIViewController. You need to ensure the CanBecomeFirstResponder is true and that you call BecomeFirstResponder() on appearing so that the MotionEnded override can be called.

Once again, just fill in the EasterEggAsync method with whatever easter egg you want to invoke!

Conclusion

Easter eggs can make a neat experience in your mobile applications, but using this shake gesture can also help make debugging easier as well as give a QA team the chance to quickly move through stories in the app. In a future post, we will talk more about these possibilities and the benefits of building testing and development options into your apps for testing rather than user-focused and fun easter eggs.


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 – Expanding ViewCells

Here’s another helpful Xamarin tip to allow you to expand your ViewCells – for example when the user taps a ViewCell, expand it to reveal more information.
Take this gif as an example:

ExpandingCell

There are 3 major parts to making a feature like this work:
1. Bind visibility to the hidden parts of your cell
2. Handle updating that visibility on selection or tap
3. Force the ViewCell to remeasure its size to show the hidden bits.

Let’s first create a new class called ExpandingViewCell:

ExpandingViewCell.cs

/// <summary>
/// Expanding view cell.
/// </summary>
public class ExpandingViewCell : ViewCell
{
    protected override void OnTapped()
    {
        base.OnTapped();
        ForceUpdateSize();
    }
}

Yeah it’s literally that stupid easy. Override OnTapped and call ForceUpdateSize(). However, it’s important to note that this is so easy because of the order – calling the base.OnTapped() first allows for your click / select events to fire off before measuring the size. This means that if we bind an update to show / hide certain controls on tap, those will show first, then we remeasure to make sure they fit.

Here’s the XAML for the ViewCell shown in the gif above:

<components:ExpandingViewCell>
    <StackLayout Orientation="Vertical" Spacing="4" Padding="64,8,16,8" Margin="0">
        <Label Text="{Binding DateLabel}" IsVisible="{Binding ShowDate}" Style="{DynamicResource BodySecondary}" HorizontalOptions="End" HorizontalTextAlignment="End" />
        <Frame CornerRadius="12" Padding="12" Margin="0" BackgroundColor="{Binding MessageColor, Converter={StaticResource ColorConverter}}" VerticalOptions="Center" HorizontalOptions="EndAndExpand" effects:LongPressedEffect.Command="{Binding Path=BindingContext.MessageOptionsCommand,Source={x:Reference Page}}" effects:LongPressedEffect.CommandParameter="{Binding .}">
            <Frame.Effects>
                <effects:LongPressedEffect />
            </Frame.Effects>
            <Label Text="{Binding Content}" Style="{DynamicResource BodySecondary}" />
        </Frame>
        <Label Text="{Binding SentDateLabel}" IsVisible="{Binding IsSentDateVisible}" Style="{DynamicResource BodySecondary}" HorizontalOptions="End" HorizontalTextAlignment="End" />
    </StackLayout>
</components:ExpandingViewCell>

There is a lot going on in this cell (it’s a chat page after all, so it’s a feature rich cell). I even have some long press handlers from my LongPressedEffect that you can read about here: Xamarin.Tip – Xamarin.Forms Long Press Effect

The important pieces to note are the IsVisible="{Binding IsSentDateVisible}" to show the date label or not.

Now in order to update this, we need to bind our SelectedItem in our ListView.

Here’s an example from the gif:

<ListView x:Name="ChatListView" ItemsSource="{Binding Messages}" SelectedItem="{Binding SelectedMessage, Mode=TwoWay}" HasUnevenRows="True" SeparatorVisibility="None" ItemTemplate="{StaticResource BubbleSelector}" />

We use the DataTemplateSelector to choose between the sent bubble and the received bubble, but that isn’t important. If you want to read more about that, check out this doc from Xamarin: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/selector

Now lastly, let’s look at what’s going on in the ViewModel behind the ListView and how we are binding the SelectedItem.

ChatPageViewModel.cs

...

public ChatMessageViewModel SelectedMessage
{
    get
    {
        return _selectedMessage;
    }
    set
    {
        Set(ref _selectedMessage, value);
        if (value != null)
        {
            ToggleDetails(value);
            SelectedMessage = null;
        }
    }
}

/// <summary>
/// Toggles the details of the given message.
/// </summary>
/// <param name="message">The message view model.</param>
private void ToggleDetails(ChatMessageViewModel message)
{
    message.ShowSenderName = !message.ShowSenderName;
    message.IsSentDateVisible = !message.IsSentDateVisible;
}

...

When the SelectedMessage we bound is changed, and not null, we toggle the ShowSenderName and the IsSentDateVisible to show/hide those views.

So the process looks like this:
– ViewCell is tapped
– ViewCell base call
– SelectedItem toggled
– SelectedMessage toggles the ViewModel properties
– Two way binding triggers to show / hide the inner views
– ViewCell ForceUpdateSize called
– ViewCell expands or collapses

And once again, we get a nice little effect like this!

ExpandingCell


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 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.

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.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.

Xamarin.NuGet – Xamarin.Forms Dynamic Bindable StackLayout

I recently released a component I commonly use in my Xamarin.Forms applications for binding data to a wrapping layout here: Xamarin.NuGet – DynamicWrapLayout Announcement! In the spirit of this type of control, I’ve also released a new NuGet package for a bindable DynamicStackLayout. It’s a simple control that allows you to create a StackLayout and bind an ItemsSource collection and an ItemTemplate. This is useful for smaller, but dynamic collections with the use of the orientation changing of a StackLayout. This means you could have a horizontally scrolling list of cards, bind the orientation or change it to vertical, and play with positioning more easily than using a ListView. I would still highly suggest using a ListView over this control for a standard vertical stack of dynamic content since this control does NOT use any view recycling or virtualization which can cause performance issues with large collections or constantly changing collection.

Get it here

NuGet: https://www.nuget.org/packages/DynamicStackLayout

Github: https://github.com/SuavePirate/DynamicStackLayout

In the end, you get something like this!

Be sure to read the documentation below:

DynamicStackLayout

A Xamarin.Forms layout for creating dynamically wrapped views. Inspired by the WrapLayout example: https://developer.xamarin.com/samples/xamarin-forms/UserInterface/CustomLayout/WrapLayout/

Installation

It’s on NuGet! https://www.nuget.org/packages/DynamicStackLayout/

Install-Package DynamicStackLayout

Be sure to install in all projects that use it.

Usage

There are two key properties that make this control useful – the ItemsSource (like a ListView) and the ItemTemplate (although, you can also just add children to the view – it does both!)
Be sure to wrap it in a ScrollView though

XAML

Add the xmlns:

xmlns:suave=&quot;clr-namespace:SuaveControls.DynamicStackLayout;assembly=SuaveControls.DynamicStackLayout&quot;

Use it in your View:

&lt;ScrollView&gt;
    &lt;suave:DynamicStackLayout ItemsSource=&quot;{Binding Items}&quot; HorizontalOptions=&quot;Fill&quot;&gt;
        &lt;suave:DynamicStackLayout.ItemTemplate&gt;
            &lt;DataTemplate&gt;
                &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
                    &lt;Label Text=&quot;{Binding .}&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
                &lt;/StackLayout&gt;
            &lt;/DataTemplate&gt;
        &lt;/suave:DynamicStackLayout.ItemTemplate&gt;
    &lt;/suave:DynamicStackLayout&gt;
&lt;/ScrollView&gt;

Don’t like data-binding and want to just use child views? You can do that too!

&lt;ScrollView&gt;
    &lt;suave:DynamicStackLayout HorizontalOptions=&quot;Fill&quot;&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;0&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;1&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;2&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;3&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;4&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
    &lt;/suave:DynamicStackLayout&gt;
&lt;/ScrollView&gt;

Features

  • Bindable child views
  • Bindable to collections
  • Handles layout changing well (try rotating the device)
  • Doesn’t require custom renderers (All Xamarin.Forms baby!)

Notes

This does not use any native view virtualization, which means performance does not scale well with extremely large data sets.

Coming soon

  • ItemSelected event and SelectedItem bindable property (for now, you can add custom gestures and commands to your DataTemplate and handle the events yourself)
  • Better Collection Updating

 

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.