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.
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.
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); } }
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.
We can split the structure of our custom control into three main parts:
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();
}
}
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 area. To 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);
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);
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);
}
}
}
Now we have our fancy control on all three platforms.
If we have added our controls in Xaml we can use the magic of HotReload 🙂
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.