Creating a good Xamarin Forms Control — Part 2 — UI Day 3

Benjamin Mayrargue
Mar 11 · 7 min read

The art of creating a good control

In the previous article I explained what makes a good control and where to find some. In this article i propose the foundations of a win-win architecture for a good Xamarin Forms control.

Anatomy of the foundation of a powerful xamarin forms control project

The old fashioned way to create a control is by using the wizard and choosing to create a xamarin forms control solution. It will add 4 projects: a netstandard library project, an ios library project, an android library project, an UWP library project, and 3 classes: a xamarin forms control and 3 native control renderers.

The new way to create a control is by creating a netstandard library project, adding the Xamarin.Forms nuget, adding the xamarin forms control class, adding 3 folders for iOS, Android and UWP platform renderers and helper classes, and modifying the csproj to activate the multi targeting feature natively supported by visual studio UI. Let’s see how all of this can be done.

Create a netstandard project

Again, you will need Visual Studio 2019 with the latests updates. There is a community (free) version which is full featured. Please let go away Visual Studio 2017 and beyond. You won’t be able to use this method on these old versions.

Steps:

  • Create an empty solution
  • Add a netstandard 2+ project (2.0 or 2.1 or up, depending of the apps you targets)
  • Add the Xamarin.Forms nuget
  • Convert the project to a multi targeting project

I will detail only the last step, as the previous ones are straightforwards.

What is a multi targeting project ?

When you build a normal project, it compiles the source files and dependencies into assemblies, all put into a target folder, in the hidden “Debug” subfolder when the Debug config is selected, or “Release” subfolder when the Release config is selected.

A “normal” project targets one configuration + platform, all defined in the Configuration Manager of Visual Studio. In the following picture, the selected target is configuration:Release and platform:x86. It also shows you how to access the Configuration Manager window.

When you build a multi targeting project, it will build all selected targets at once, each in its own subfolder (of the Debug or Release folder). You specify the targets manually, in the .csproj file. Let’s see this in action.

❗ A multi targeting project is exclusively for a library project, not for an application project. A library project has no entry point, and can be referenced in both library projects or application projects. An application project has an entry point and all metadata required to bundle and deploy it.

Convert the netstandard project into a multi targeting project

Replace:

<Project Sdk="Microsoft.NET.Sdk">

By:

<Project Sdk="MSBuild.Sdk.Extras/2.0.54">

And replace:

<TargetFramework>netstandard2.0</TargetFramework>

By: note: make sure you don’t forget the S at the end of TargetFrameworks.

<TargetFrameworks>netstandard2.0;Xamarin.iOS10;MonoAndroid81;MonoAndroid90;MonoAndroid10.0;uap10.0.17763</TargetFrameworks>

Remove assemblyinfo.cs if it exists. Add these lines below TargetFrameworks:

<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DebugType>portable</DebugType>

The magic starts here. Create an ItemGroup for the code and libraries shared between all platforms, and an item group with a condition for each target framework:

<ItemGroup>
<!-- Shared by all platforms -->
<PackageReference Include="Xamarin.Forms" Version="4.4.0.991640" />
<Compile Include="Shared\**\*.cs" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('MonoAndroid')) ">
<!-- Specific to Android -->
<Compile Include="Android\**\.cs" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
<!-- Specific to iOS -->
<Compile Include="Ios\**\.cs" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('uap')) ">
<!-- Specific to UWP -->
<Compile Include="Uap\**\*.cs" />
</ItemGroup>

Then create the folders: Shared, Android, Ios and Uap.

The folder structure

💡 If Visual Studio displays a ⚠symbol on the “Dependencies” node, restart Visual Studio! Reloading the project is not sufficient.

A note about the ** syntax (“Android\**\.cs”). It means “this folder or any subfolders below”. So <Compile Include=”Android\**\.cs” /> means compile all .cs files found inside the Android folder, any subfolders, and any subfolders of these subfolders, and any subfolders of …

The nuget Xamarin Essentials uses this technique, but slightly differently. Instead of having all android files in the android folder, they decided to append .android. to the file name. Ie: someclass.android.cs, someclass.ios.cs, and so on. You can use their technique if you prefer, it’s more a convention than a marble thing. Because of the specific goal of their library, it makes sense. Check their csproj file to see the syntax to obtain this behavior:

<ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
<Compile Include="**\*.ios.cs" />
<Compile Include="**\*.ios.*.cs" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('MonoAndroid')) ">
<Compile Include="**\*.android.cs" />
<Compile Include="**\*.android.*.cs" />
</ItemGroup>

Add source code, nugets, and references

You can add existing or new source code files to any folder of this project. Make sure nothing gets added to the csproj file. Visual Studio likes to add Compile or None tags. When you open a source file, you should see a new drop down at the top left of the editor, from which a target context can be selected. When selected, “go to definition”, “show usages” and other editor helpers will execute in this specific context.

To add a reference or nuget to all platforms, right clic the Dependencies item. To add a reference or nuget to a specific platform, expand this platform like below and right clic on Packages. You can also manually add items to the csproj file.

If you need to add native Android resources, create a Resource folder at the root of the project and add the following line to the Android item group. Don’t try to move the Resource folder under the Android folder as it will break Xamarin compilation. This is a limitation in the Xamarin tooling as of march 2020:

<ItemGroup Condition=" $(TargetFramework.StartsWith('MonoAndroid')) ">
<!-- Specific to Android -->
<Compile Include="Android\**\.cs" />
<AndroidResource Include="Resources\**\*.xml" />
</ItemGroup>

An important side note: when adding a new code file, Visual Studio will add a new Remove tag referencing this file in the .csproj. Make sure to remove it.

Build

Make sure you select “Debug” and “Any CPU”. Build. If you press the “Show All Files” button of the Solution Explorer, you will see the hidden folders with the compiled assemblies for each target:

Pack the nuget

Projects like this are called “MSBuild.Sdk.Extras” projects. MSBuild.Sdk.Extras projects supports creating automatically a nuget package containing all the target platforms, and including all required dependencies. The task generates the .nuget file from the msbuild properties, and use it to create a .nupkg file.

The MsBuild.Sdk.Extra task exposes new tags that maps to exising nuspec tags. They are all well documented on the github project.

Start by editing the .csproj file we created earlier, and add the following lines. I have separated msbuild properties that are overridable from msbuild properties that should be edited directly.

<!--  nuget configurable properties -->
<PropertyGroup>
<Version>0.0.0-pre1</Version>
<VersionSuffix></VersionSuffix>
<DefineConstants>$(DefineConstants);</DefineConstants>
<NugetPackageId></NugetPackageId>
</PropertyGroup>
<!-- nuget properties -->
<PropertyGroup>
<PackageId>Vapolia.XamSvg.Forms</PackageId>
<!-- PackageIcon>icon.png</PackageIcon -->
<!--<PackageIconUrl><https://raw.githubusercontent.com/xamarin/Essentials/master/Assets/xamarin.essentials_128x128.png></PackageIconUrl>-->
<Summary>Use SVG images in Xamarin Forms on iOS, Android and UWP apps (full C#)</Summary>
<PackageTags>xamarin forms, xamarin, ios, android, uwp, svg, image, vector, .svg</PackageTags>
<Title>Svg vector image support for Xamarin.Forms (ios, android, UWP)</Title>
<Description>Add SVG images to Xamarin Forms apps. It's a Full .NET native implementation (not a binding and has no dependency).</Description>
<Product>$(AssemblyName) ($(TargetFramework))</Product>
<VersionSuffix></VersionSuffix>
<PackageVersion>$(Version)$(VersionSuffix)</PackageVersion>
<Authors>Benjamin Mayrargue</Authors>
<Owners>Benjamin Mayrargue,Vapolia</Owners>
<NeutralLanguage>en</NeutralLanguage>
<Copyright>© Vapolia. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/softlion/XamSvg-Samples</RepositoryUrl>
<UseFullSemVerForNuGet>false</UseFullSemVerForNuGet>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://vapolia.eu</PackageProjectUrl>
<PackageReleaseNotes>
TODO
</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>
<None Include="..\\..\\LICENSE" Pack="true" PackagePath=""/>
</ItemGroup>

The license file is mandatory. In this example it should be an extensionless markdown text file. Its path is relative to the .csproj file path. In the PackageLicenseFile tag, the path must not be specified.

To create the nuget package, run this command in the solution folder :

Using a command prompt:

msbuild /restore /p:Configuration=Release /p:Platform="Any CPU" /p:Version="1.2.3" /p:VersionSuffix="" /p:Deterministic=false /t:Clean;Build;Pack

Or using a PowerShell script:

$version="1.2.3"
$versionSuffix=""
msbuild /restore /p:Configuration=Release /p:Platform="Any CPU" /p:Version="$version" /p:VersionSuffix="$versionSuffix" /p:Deterministic=false /p:PackageOutputPath="$PSScriptRoot" --% /t:Clean;Build;Pack

Note the trick of using the — % powershell operator which prevents it from interpreting “;” as a command separator. The output path will be the current powershell script path.

There is also a way of making msbuid create the nuget on build from visual studio, but i prefer not to use it, as a clean release build is much more preferable.

Final words

I hope you enjoyed this article. If so please 💕it and share it!

In the next article, i will propose the full code of a demo control using automatic registration.

References

https://github.com/novotnyllc/MSBuildSdkExtras

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade