Serengeti logo BLACK white bg w slogan
Menu

Porting ios control to Xamarin.iOS and then to Xamarin.Forms (with SkiaSharp)

Yuriy Holembyovskyy, Lead Xamarin developer
24.01.2020.

When we need to use some custom control from ios and use it in our Xamarin.ios application we have two options to do this. Make bindings or rewrite it with C#. You may wonder: “Why may I ever need to rewrite control instead of just making bindings?” The first and most obvious answer is “you need to extend this control with your own properties and functionality”. The second answers is “you will need it to port to other platforms”.

Let’s start

For this article, I took this nice progress bar implemented by Sohei Miyakura.

Progress bar

I translated Swift to C# code hence everything is pretty much straightforward. It is so much easier to convert from Swift then from Objective-C :). ConfigureView() and ConfigureProgressView() methods were a bit modified according to C# syntax.

private void ConfigureProgressView()
{
progressView.BackgroundColor = BarColor;
progressView.Frame = new CGRect(progressView.Frame.Location.X, Bounds.Location.Y , 0, PGHeight); progressView.Layer.CornerRadius = PGHeight / 2;
}
private void ConfigureView()
{
SetGradientBackground();
BackgroundColor = BGColor;
Layer.BorderWidth = FrameBold;
Layer.BorderColor = FrameColor.CGColor;
Frame = new CGRect(Frame.Location, new CGSize(PGWidth, GHeight));
Layer.CornerRadius = PGHeight / 2;
}

After porting this control to C# I got two nice progress bars: vertical and horizontal. Here you can check converted source code with a sample.

Ported controls with animation

Gradient Background

Now let’s extend these controls with properties that allow us to make a gradient background. For this purpose, I have added two UIColor properties: StartColor and EndColor and SetGradientBackground() method

private void SetGradientBackground()
{
 if (StartColor != UIColor.Clear && EndColor != UIColor.Clear)
 {
 var gradient = new CAGradientLayer
 {
 Frame = Bounds,
 Colors = new CGColor[]{ StartColor.CGColor, EndColor.CGColor},
 CornerRadius = PGHeight / 2
 };
 Layer.InsertSublayer(gradient, 0);
 }
}
Gradient background

Xamarin.Forms and other platforms

What if we need to use this control on other platforms like Android and UWP in our cross-platform application. Let’s do this with Skia.Sharp. Our toolbox consists of: Skia.Sharp.Forms library for Xamarin.Forms, Visual Studio and SkiaSharp Fiddle. I think we can also port it to other platforms available with Xamarin.Forms and SkiaSharp like WPF and mac OS.

Structure

We can split the structure of our custom control into three main parts:

  1. Bindable properties
  2. OnPaintSurface() method — where all the magic happens
  3. OnPropertyChanged() method — needed to invalidate UI when our properties have changed

First, we will map all necessary properties from ported Xamarin.iOS control.

public static readonly BindableProperty BGColorProperty =
BindableProperty.Create(nameof(BGColor), typeof(Color), typeof(VerticalProgressBar), Color.White);public Color BGColor
{
get { return (Color)GetValue(BGColorProperty); }
set { SetValue(BGColorProperty, value); }
}

Next, let’s override OnPropertyChanged() method

protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);

if (propertyName == BGColorProperty.PropertyName
|| propertyName == BarColorProperty.PropertyName
|| propertyName == FrameColorProperty.PropertyName
|| propertyName == FrameBoldProperty.PropertyName
|| propertyName == PGHeightProperty.PropertyName
|| propertyName == PGWidthProperty.PropertyName
|| propertyName == ProgressProperty.PropertyName
|| propertyName == StartColorProperty.PropertyName
|| propertyName == EndColorProperty.PropertyName)
{
InvalidateSurface();
}
}

The Control

Our control is just three rectangles that are overlapped: Frame, Background and Bar

Let’s add three SkPaint’s for each rectangle:

var frameColorPaint = new SKPaint
{
Color = FrameColor.ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = 2
};
var bgColorPaint = new SKPaint
{
Color = BGColor.ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeWidth = 0
};var barColorPaint = new SKPaint
{
Color = BarColor.ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeWidth = 0
};

Frame paint has SKPaintStyle.Stroke cause we do not need to fill the whole areaTo draw a rectangle with rounded corners we will use the DrawRoundRect method.

canvas.DrawRoundRect(0, 0, PGWidth, PGHeight, PGWidth / 2, PGWidth / 2, bgColorPaint);

Also, we need to add one more method to make our control look properly.

canvas.ClipRoundRect(skRoundRect, SKClipOperation.Intersect);

Gradients

To add a Linear gradient we will use CreateLinearGradient() method

barColorPaint.Shader = SKShader.CreateLinearGradient(
new SKPoint(skrect.Left, skrect.Top),
new SKPoint(skrect.Right, skrect.Bottom),
new SKColor[] { StartColor.ToSKColor(), EndColor.ToSKColor() },
new float[] { 0, 1 },
SKShaderTileMode.Repeat);

Animation

Let’s bring some life into our controls 🙂 Animating our ProgressBar is basically redrawing the bar at various data intervals. I took this code from my previous article 🙂

async Task AnimateProgress(float progress)
{
if (progress == VGaugeControl.Progress)
{
return;
}
if (progress <= VGaugeControl.Progress)
{
for (int i = VGaugeControl.Progress; i >= progress; i--)
{
VGaugeControl.Progress = i;
await Task.Delay(1);
}
}
else
{
for (int i = VGaugeControl.Progress; i <= progress; i ++)
{
VGaugeControl.Progress = i;
await Task.Delay(1);
}
}
}

Let’s see what we got

iOS, Android and UWP animation running

Now we have our fancy control on all three platforms.

Bonus

If we have added our controls in Xaml we can use the magic of HotReload 🙂

HotReload in action

Wrapping Up

As you can see with the help of Xamarin and SkiasSharp we can port native controls to multiple platforms and boost our work with SkiaSharp Fiddle.

You can download the full sample source code here.

Let's do business

The project was co-financed by the European Union from the European Regional Development Fund. The content of the site is the sole responsibility of Serengeti ltd.
cross