Xamarin.Tip – Embed Your Xamarin.Forms Pages in Your Android Activities

The number one complaint I hear about Xamarin.Forms is the slow startup time of the applications that use it. The team at Xamarin has done a lot to help with this and give some more options such as XAML Compilation, Lazy Loading, and Ahead of Time Compilation. Check out some of David Ortinau’s suggestions here: 5 Ways to Boost Xamarin.Forms App Startup Time.

However, one of the best ways I’ve found to help with this issue is to use Xamarin.Forms Embedding to its full potential. Embedding became available in Xamarin.Forms 2.5 and at a high level allows you to embed your Xamarin.Forms pages into your Native Xamarin views by using the extension methods .CreateFragment(Context context); for Android and .CreateViewController(); for iOS. This is most commonly used for when you want to share some UI in your Xamarin Native apps using Xamarin.Forms, however you still need to call Xamarin.Forms.Init() which is one of the main culprits in the slow startup time.

The solution proposed here still allows you to create almost all of your views in Xamarin.Forms by using embedding, but requires some architecture and design changes. The premise is this:

  • First Activity is non-Xamarin.Forms and loads your app right away
  • Init Xamarin.Forms after this activity is loaded
  • Embed Xamarin.Forms pages in other Activities
  • Lift navigation out of Xamarin.Forms and into the native navigation.

This also has advantages outside the startup time such as better performance on transitions, more natural look and feel to end-users, performance gains in other areas, and a smaller app-size.

This means:

  • No NavigationPage
  • No Xamarin.Forms toolbar (using the native Toolbar control instead)
  • Still have MVVM and all our bindings we would expect

So if you’re already using a framework that is not tied down to Xamarin.Forms such as MvvmLight, you don’t have to change much behind the scenes since the INavigationService is abstracted.

Let’s kick this off by creating an inheritable Activity that handles the embedding and layout how we want.

xamarin_forms_activity.axml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <android.support.design.widget.AppBarLayout     
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/actionBarSize"
        android:layout_gravity="top"
        app:elevation="0dp">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?android:attr/actionBarSize"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
    </android.support.design.widget.AppBarLayout>
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

This layout gives us the native Android toolbar (with the shadow! Another plus!) and a space for us to embed in this FrameLayout.

Now let’s create the Activity:

XamarinFormsActivity.cs

/// <summary>
/// Base xamarin forms activity.
/// This activity contains a single fragment in the layout and renders the fragment pulled from the Xamarin.Forms page
/// </summary>
public abstract class XamarinFormsActivity<TPage> : AppCompatActivity
    where TPage : ContentPage, new()
{
    protected readonly TPage _page;
    protected int _containerLayoutId = Resource.Layout.activity_fragment_container;
    public Android.Support.V7.Widget.Toolbar Toolbar { get; set; }
    public AppBarLayout AppBar { get; set; }

    public XamarinFormsActivity()
    {
        _page = new TPage();
    }

    /// <summary>
    /// Creates the activity and maps the Xamarin.Forms page to the fragment
    /// </summary>
    /// <param name="savedInstanceState">Saved instance state.</param>
    protected override void OnCreate(Android.OS.Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.xamarin_forms_activity);

        Toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
        if (Toolbar?.Parent != null)
        {
            AppBar = Toolbar?.Parent as AppBarLayout;
            SetSupportActionBar(Toolbar);
        }

        // register the fragment
        var transaction = SupportFragmentManager.BeginTransaction();
        transaction.Add(Resource.Id.fragment_container, _page.CreateSupportFragment(this));
        transaction.Commit();
        SupportActionBar?.SetDisplayHomeAsUpEnabled(true);
        SupportActionBar?.SetDisplayShowHomeEnabled(true);
        Toolbar?.SetBackgroundColor(Android.Graphics.Color.White);
        // everything else from this point should be managed by the Xamarin.Forms page behind the fragment
    }
}

So now we can create a simple Xamarin.Forms page:

SomePage.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"     x:Class="MyApp.Pages.SomePage">
    <ContentPage.Content>
        <Grid>
            <Label Text="I'm Embedded!" HorizontalOptions="Center" VerticalOptions="Center"/>
        </Grid>
    </ContentPage.Content>
</ContentPage>

Then create the associated Activity:

SomeActivity.cs

public class SomeActivity : XamarinFormsActivity<SomePage>
{
    protected override void OnCreate(Bundle savedInstance)
    {
        SupportActionBar.Title = "Some Page";
    }
}

Now all we have to do is kick off this SomeActivity after calling Xamarin.Forms.Init() and we are good to go! If we have a MainActivity we can call it before navigating if it isn’t initialized, or execute it in OnResume or a similar lifecycle event.

MainActivity.cs

public class MainActivity : AppCompatActivity 
{    

    protected override void OnCreate(Bundle savedInstance)
    {
        base.OnCreate(savedInstance);
        SetContentView(Resource.Layout.main_activity);
        var someButton = FindViewBy<Button>(Resource.Id.some_button);
        someButton.Click += delegate 
        {
             if(!Xamarin.Forms.Forms.IsInitialized)
                 Xamarin.Forms.Forms.Init(this, savedInstance);
             StartActivity(typeof(SomeActivity));
        }
    } 
}

And there you have it! Some new Xamarin.Forms embedding for performance and other extra benefits šŸ™‚

In future posts of this subject, we’ll look at the same setup for iOS, extending interactions between the Xamarin.Forms Page and the native Activity and ViewControllers, using advanced native components with the embedded Xamarin.Forms Page, and more!

Let me know what you think of this pattern – have you used it? What else would you want to hear about it??

Be sure to checkout some of the Xamarin examples on embedding too!


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.

Adding a File Upload Field to Your Swagger UI With Swashbuckle

If you’re building ASP.NET Core Web APIs, then I hope you’ve heard of Swashbuckle – the tool to generate the Swagger UI automatically for all of your controllers to make manual testing your endpoints visual and simple.

Out of the box, the documentation helps you set up your UI, handle different ways to authenticate (which we will touch on in a later post), and have it all hooked up to your controllers. However, this only handles the most common cases of required requests with query string parameters and HTTP Body content.

In this post, we’ll look at a quick and easy way to also add File upload fields for your API endpoints that consume IFormFile properties to make testing file uploading even easier.

Basic Swagger Setup

First thing’s first, install that puppy:

Package Manager : Install-Package Swashbuckle.AspNetCore
CLI : dotnet add package Swashbuckle.AspNetCore

Let’s first look at a simple swagger setup as our baseline before we add everything for our HTTP Header Field.

Startup.cs

//...
public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddSwaggerGen(config =>
    {
        config.SwaggerDoc("v1", new Info { Title = "My API", Version = "V1" });
    });
    // ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...
    app.UseMvc();

    app.UseSwagger();
    app.UseSwaggerUI(config =>
    {
        config.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
    });
    // ...
}
//...

This setup gives us all we need for our basic UI and wireup to our controllers!
Running this gives us our basic swagger at /swagger:
Swagger no header

Adding a File Upload Field

What we have to do now is add an OperationFilter to our swagger generation. These OperationFilters can do a whole lot and enable us to customize the swagger document created which is what drives the fields and info on the UI.

Let’s start by creating our FormFileSwaggerFilter class.

FormFileSwaggerFilter.cs

/// <summary>
/// Filter to enable handling file upload in swagger
/// </summary>
public class FormFileSwaggerFilter : IOperationFilter
{
    private const string formDataMimeType = "multipart/form-data";
    private static readonly string[] formFilePropertyNames =
        typeof(IFormFile).GetTypeInfo().DeclaredProperties.Select(p => p.Name).ToArray();

    public void Apply(Operation operation, OperationFilterContext context)
    {
        var parameters = operation.Parameters;
        if (parameters == null || parameters.Count == 0) return;

        var formFileParameterNames = new List<string>();
        var formFileSubParameterNames = new List<string>();

        foreach (var actionParameter in context.ApiDescription.ActionDescriptor.Parameters)
        {
            var properties =
                actionParameter.ParameterType.GetProperties()
                    .Where(p => p.PropertyType == typeof(IFormFile))
                    .Select(p => p.Name)
                    .ToArray();

            if (properties.Length != 0)
            {
                formFileParameterNames.AddRange(properties);
                formFileSubParameterNames.AddRange(properties);
                continue;
            }

            if (actionParameter.ParameterType != typeof(IFormFile)) continue;
            formFileParameterNames.Add(actionParameter.Name);
        }

        if (!formFileParameterNames.Any()) return;

        var consumes = operation.Consumes;
        consumes.Clear();
        consumes.Add(formDataMimeType);

        foreach (var parameter in parameters.ToArray())
        {
            if (!(parameter is NonBodyParameter) || parameter.In != "formData") continue;

            if (formFileSubParameterNames.Any(p => parameter.Name.StartsWith(p + "."))
                || formFilePropertyNames.Contains(parameter.Name))
                parameters.Remove(parameter);
        }

        foreach (var formFileParameter in formFileParameterNames)
        {
            parameters.Add(new NonBodyParameter()
            {
                Name = formFileParameter,
                Type = "file",
                In = "formData"
            });
        }
    }
}

This operation filter takes the operation parameters, then uses reflection to find the type of the field. If the field is an IFormFile, then we add a new file field from the formData section to our parameters. This in turn will update our swagger definition json file, and when rendered adds the field to our UI.

This even works great with endpoints that take a separate HTTP Body, query parameters, and files!

Now we need to reference it in our Startup when we initialize swagger:

Startup.cs

//...
public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddSwaggerGen(config =>
    {
        config.SwaggerDoc("v1", new Info { Title = "My API", Version = "V1" });

        config.OperationFilter<FormFileSwaggerFilter>();
    });
    // ...
}

Here’s an example controller with an endpoint that uses the file upload:

FileUploadController

[Route("api/[controller]")]
public class FileUploadController : Controller
{
    [HttpPost]
    public async Task<IActionResult> CreateMediaItem(string name, [FromForm]IFormFile file)
    {
        // Do something with the file
        return Ok();
    }
}

This controller ends up rendering in our Swagger UI as:
Fileupload_swagger

And using this, we can now submit our request with an uploaded file and all our other parameters!


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.

Adding a Required HTTP Header to Your Swagger UI With Swashbuckle

If you’re building ASP.NET Core Web APIs, then I hope you’ve heard of Swashbuckle – the tool to generate the Swagger UI automatically for all of your controllers to make manual testing your endpoints visual and simple.

Out of the box, the documentation helps you set up your UI, handle different ways to authenticate (which we will touch on in a later post), and have it all hooked up to your controllers. However, this only handles the most common cases of required requests with query string parameters and HTTP Body content.

In this post, we’ll look at a quick and easy way to also add fields for your custom HTTP Request Headers so you can fill them out while testing without having to do some funky stuff in the console.

Basic Swagger Setup

First thing’s first, install that puppy:

Package Manager : Install-Package Swashbuckle.AspNetCore
CLI : dotnet add package Swashbuckle.AspNetCore

Let’s first look at a simple swagger setup as our baseline before we add everything for our HTTP Header Field.

Startup.cs

//...
public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddSwaggerGen(config =>
    {
        config.SwaggerDoc("v1", new Info { Title = "My API", Version = "V1" });
    });
    // ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...
    app.UseMvc();

    app.UseSwagger();
    app.UseSwaggerUI(config =>
    {
        config.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
    });
    // ...
}
//...

This setup gives us all we need for our basic UI and wireup to our controllers!
Running this gives us our basic swagger at /swagger:
Swagger no header

Adding Custom Headers

What we have to do now is add an OperationFilter to our swagger generation. These OperationFilters can do a whole lot and enable us to customize the swagger document created which is what drives the fields and info on the UI.

Let’s create a MyHeaderFilter and then add it to the AddSwaggerGen call.

MyHeaderFilter.cs

/// <summary>
/// Operation filter to add the requirement of the custom header
/// </summary>
public class MyHeaderFilter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        if (operation.Parameters == null)
            operation.Parameters = new List<IParameter>();

        operation.Parameters.Add(new NonBodyParameter
        {
            Name = "MY-HEADER",
            In = "header",
            Type = "string",
            Required = true // set to false if this is optional
        });
    }
}

And now we need to update our Startup class:

Startup.cs

//...
public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddSwaggerGen(config =>
    {
        config.SwaggerDoc("v1", new Info { Title = "My API", Version = "V1" });

        config.OperationFilter<MyHeaderFilter>();
    });
    // ...
}

Now when we run and navigate to our /swagger url, we can see:
Swagger with header

When we fill out that field, we can now pull the value off the request header in our controller!

ContentController.cs

[Route("api/[controller]")]
public class ContentController : Controller
{
    public void Search([FromBody]InputModel model)
    {
        Console.WriteLine(Request.Headers["MY-HEADER"]);        
    }
}

In further posts, we’ll talk about adding Bearer Authentication, XML Comments, and More šŸ™‚


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 – No Bounce ScrollView in Xamarin.Forms

So on iOS, UIScrollView bounces by default so in Xamarin.Forms the ScrollView natrually bounces. But what if you don’t want your ScrollView to bounce? Android doesn’t bounce, so we won’t worry about it.

Let’s solve this problem with a custom renderer and control called NoBounceScrollView. This could also be done with an Effect but I like to have a custom control with the verbosity and flexibility with a renderer.

Let’s start by building a simple control in our Xamarin.Forms project:

NoBounceScrollView.cs

public class NoBounceScrollView : ScrollView { }

We don’t need anything in it since we are just going to assume it should never bounce and doesn’t affect the ScrollView in any other way. If you want, you can add a bindable property here to set Bouncable or something like that to true or false. Note: if you don’t want your ScrollView to bounce ever, then you don’t need this. Instead just have your renderer replace the default ScrollView renderer.

So we have our Xamarin.Forms control to build the renderer for, so now let’s create the iOS renderer:

NoBounceScrollViewRenderer.cs

public class NoBounceScrollViewRenderer : ScrollViewRenderer
{

    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        UpdateScrollView();
    }

    private void UpdateScrollView()
    {
        ContentInset = new UIKit.UIEdgeInsets(0, 0, 0, 0);
        if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
            ContentInsetAdjustmentBehavior = UIKit.UIScrollViewContentInsetAdjustmentBehavior.Never;
        Bounces = false;
        ScrollIndicatorInsets = new UIKit.UIEdgeInsets(0, 0, 0, 0);
    }
}

What this is doing is setting the content insets to 0 so we don’t have empty space on top or bottom that the bouncing adds, and we also set Bounces to false. Note that the ContentInsetAdjustmentBehavior is only available on iOS 11 and higher, so for that extra step, we need to check the current iOS version.

Lastly, be sure to register your renderer:

[assembly: ExportRenderer(typeof(NoBounceScrollView), typeof(NoBounceScrollViewRenderer))]

or if you are replacing all ScrollViews:

[assembly: ExportRenderer(typeof(ScrollView), typeof(NoBounceScrollViewRenderer))]

Now we have everything we need! So let’s use it in our page:

MainPage.xaml

<ContentPage ...>
    <components:NoBounceScrollView>
        <StackLayout>
            ...
        </StackLayout>
    </components:NoBounceScrollView>
</ContentPage>

Check out the difference!

You can see this control in action with the EF Go Ahead Tours Companion App


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.

Add Model Validation to Your ASP.NET Core Web API

If you’re used to creating traditional Web APIs in ASP.NET, you’ve probably become accustom to using System.ComponentModel.DataAnnotations such as the [Required] or [StringLength] data attributes. Using these in your models that are parameters to your API project would return the proper 400 response code when the input didn’t match the requirement.

In ASP.NET Core 2.0, this is no longer built in right out of the box, but setting it up is super easy! So if you don’t want to abstract your model validation in your business logic layer and want to get it out of the way right away when the request comes in, check this out.

There are only two steps to doing this:

  • Create an implementation of `IActionFilter`
  • Register this Filter class in your startup

Let’s start by creating a ValidatorActionFilter class:

ValidatorActionFilter.cs

/// <summary>
/// Filter for returning a result if the given model to a controller does not pass validation
/// </summary>
public class ValidatorActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.ModelState.IsValid)
        {
            filterContext.Result = new BadRequestObjectResult(filterContext.ModelState);
        }
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {

    }
}

What this does is check the current HttpContext.ModelState to make sure it is valid. This allows us to ensure we don’t have to make this check in each endpoint. Note, we don’t have to add anything in the OnActionExecuted method since this filter is used exclusively before the action is called.

In the OnActionExecuting call, if the model state is NOT valid, we need to return the 400 – Bad Request response as the result.

Now, let’s register this in our startup. We do this by applying it in the ConfigureServices call and specifically in the options of the AddMvc call:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(ValidatorActionFilter));
    });
}

And that’s it! So now your endpoints will return the proper response such as:

MyInputModel.cs

public class MyInputModel
{
    [Required]
    public string SomeRequiredValue { get; set; }
    public string SomeNotRequiredValue { get; set; }
}

MyController.cs

public class MyController : Controller
{
    public ActionResult DoSomeAction([FromBody]MyInputModel input)
    {
         return Ok("You did it"!)
    }
}

The request of:

{
    "someNotRequiredValue": "Hey"
}

Will return 400 - Bad Request - "The SomeRequiredValue field is required"

And the request of:

{
    "someRequiredValue": "Yo"
    "someNotRequiredValue": "Hey"
}

Will return 200 - Ok - "You did it!"

So go forth and validate those input models with ease!


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 – Create an Initials Circle View

A while back, I put up a helpful Xamarin.Tip on how to create a simple CircleView in Xamarin.Forms for a BadgeView control – Xamarin.Controls – BadgeView

Since then, I’ve used this simple concept to create some more useful controls that are unique and dynamic in their own way. In this post, we will look at using the CircleView to create an InitialsCircleView. This view is a simple Xamarin.Forms control to show a person’s initials. It is extremely useful in place of user avatars or profile pictures and can add a nice touch to your User Interface without much work at all.

Let’s start by re-iterating how to build a simple CircleView:

In your Xamarin.Forms project, create a new class:

CircleView.cs

public class CircleView : BoxView
{
    public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(CircleView), 0.0);

    public double CornerRadius
    {
        get { return (double)GetValue(CornerRadiusProperty); }
        set { SetValue(CornerRadiusProperty, value); }
    }

    public CircleView()
    {
    }
}

And create your native renderers:

AndroidCircleViewRenderer.cs

public class CircleViewRenderer : BoxRenderer
{
    private float _cornerRadius;
    private RectF _bounds;
    private Path _path;

    public CircleViewRenderer(Context context)
        : base(context)
    {

    }

    protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
    {
        base.OnElementChanged(e);

        if (Element == null)
        {
            return;
        }
        var element = (CircleView)Element;

        _cornerRadius = TypedValue.ApplyDimension(ComplexUnitType.Dip, (float)element.CornerRadius, Context.Resources.DisplayMetrics);

    }

    protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
    {
        base.OnSizeChanged(w, h, oldw, oldh);
        if ((w != oldw && h != oldh) || _bounds == null)
        {
            _bounds = new RectF(0, 0, w, h);
        }

        _path = new Path();
        _path.Reset();
        _path.AddRoundRect(_bounds, _cornerRadius, _cornerRadius, Path.Direction.Cw);
        _path.Close();
    }

    public override void Draw(Canvas canvas)
    {
        canvas.Save();
        canvas.ClipPath(_path);
        base.Draw(canvas);
        canvas.Restore();
    }
}

iOSCircleViewRenderer.cs

public class CircleViewRenderer : BoxRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
    {
        base.OnElementChanged(e);

        if (Element == null)
            return;

        Layer.MasksToBounds = true;
        Layer.CornerRadius = (float)((CircleView)Element).CornerRadius / 2.0f;
    }
}

Okay, cool we can draw circles in Xamarin.Forms easily with

<suave:CircleView .../>

So now let’s build a Xamarin.Forms component on top of this for our InitialsCircleView. The key pieces to making this view unique and cool is:

  • Circle color
  • Font size
  • Font color
  • Font family
  • Circle Radius (in case you don’t really want a circle)

Let’s write the ContentView in XAML, but you could easily do it in C# too:

InitialsCircleView.xaml

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:components="clr-namespace:SuaveControls.Components"
    x:Class="SuaveControls.Components.InitialsCircleView">
    <ContentView.Content>
        <Grid x:Name="Container">
            <components:CircleView x:Name="Circle" Margin="16" HorizontalOptions="Fill" VerticalOptions="Fill" Color="{Binding CircleColor}" CornerRadius="{Binding CornerRadius}"/>
            <Label x:Name="InitialsLabel" VerticalOptions="Center" HorizontalOptions="Center" HorizontalTextAlignment="Center" TextColor="{Binding TextColor}" Font="{Binding Font}" FontSize="{Binding FontSize}" />
        </Grid>
    </ContentView.Content>
</ContentView>

So now we have the layout of our InitialsCirlceView, let’s look at the code behind to see how we apply all these different properties and bind them to this internal view:

InitialsCircleView.xaml.cs

public partial class InitialsCircleView : ContentView
{
    public static BindableProperty CircleColorProperty = BindableProperty.Create(nameof(CircleColor), typeof(Color), typeof(InitialsCircleView), Color.White);
    public static BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(InitialsCircleView), Color.White);
    public static BindableProperty FontSizeProperty = BindableProperty.Create(nameof(FontSize), typeof(int), typeof(InitialsCircleView), 14);
    public static BindableProperty FontProperty = BindableProperty.Create(nameof(Font), typeof(Font), typeof(InitialsCircleView), Font.Default);
    public static BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(InitialsCircleView), 0.0, propertyChanged: (bindable, oldVal, newVal) =>
    {
        var initialsView = bindable as InitialsCircleView;
        if (initialsView != null)
            initialsView.UpdateCornerRadius((double)newVal);
    });
    public static BindableProperty NameProperty = BindableProperty.Create(nameof(Name), typeof(string), typeof(InitialsCircleView), string.Empty, 
    propertyChanged: (bindable, oldVal, newVal) =>
    {
        var initialsView = bindable as InitialsCircleView;
        if (initialsView != null)
            initialsView.UpdateTextWithName(newVal?.ToString());
    });

    public double CornerRadius
    {
        get
        {
            return (double)GetValue(CornerRadiusProperty);
        }
        set
        {
            SetValue(CornerRadiusProperty, value);
        }
    }
    public string Name
    {
        get
        {
            return (string)GetValue(NameProperty);
        }
        set
        {
            SetValue(NameProperty, value);
        }
    }
    public int FontSize
    {
        get
        {
            return (int)GetValue(FontSizeProperty);
        }
        set
        {
            SetValue(FontSizeProperty, value);
        }
    }
    public Font Font
    {
        get
        {
            return (Font)GetValue(FontProperty);
        }
        set
        {
            SetValue(FontProperty, value);
        }
    }
    public Color CircleColor
    {
        get
        {
            return (Color)GetValue(CircleColorProperty);
        }
        set
        {
            SetValue(CircleColorProperty, value);
        }
    }
    public Color TextColor
    {
        get
        {
            return (Color)GetValue(TextColorProperty);
        }
        set
        {
            SetValue(TextColorProperty, value);
        }
    }


    public InitialsCircleView()
    {
        InitializeComponent();
        Container.BindingContext = this;
    }

    /// <summary>
    /// Updates the name of the text with.
    /// </summary>
    /// <param name="name">Name.</param>
    private void UpdateTextWithName(string name)
    {
        if (string.IsNullOrEmpty(name))
            return;

        var separateWords = name.Split(' ');
        if(separateWords.Length > 0)
        {
            var initialsArray = separateWords.Select(word => word[0].ToString().ToUpper()).ToArray(); // array of string of initials upper cased
            if(initialsArray.Length > 1)
            {
                // grab the first and last
                initialsArray = new string[2] { initialsArray[0], initialsArray[initialsArray.Length - 1] };
            }
            var initialsString = string.Join(string.Empty, initialsArray);
            InitialsLabel.Text = initialsString;
        }
        else
        {
            InitialsLabel.Text = string.Empty;
        }
    }

    /// <summary>
    /// Updates the corner radius.
    /// </summary>
    /// <param name="radius">Radius.</param>
    private void UpdateCornerRadius(double radius)
    {
        Circle.CornerRadius = radius;
    }
}

We have a few properties that are directly bound to the subviews in the XAML which is facilitated by applying the Container.BindingContext = this;.

We also have a property for Name which invokes the internal UpdateTextWithName method. This in turn takes the name of a person, grabs the initials, and sets the text of the Label to it. So we can then use it by just passing a person or thing’s name and let it figure out the initials naturally. So if we say:

var initialsCirle = new InitialsCirlceView
{
    Name = "Alex Dunn"
}

The output will be “AD” in the cirlce.

Here’s how you can now use it in your XAML:

MainPage.xaml

...
<components:InitialsCircleView 
    CircleColor="Red"
    FontSize="32" 
    Name="Alex Dunn" 
    VerticalOptions="Center" 
    HorizontalOptions="Center" 
    TextColor="White" 
    CornerRadius="90" 
    WidthRequest="120" 
    HeightRequest="120"/>

...

Here’s what it looks like!
InitialsCircle

You can also create XAML Styles for it instead of managing all the colors and font options everywhere you use it.


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