Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Getting Started with Xamarin.Forms and Platform Specific Resources using OnPlatform

Previous posts in this sequence on Getting Started with Xamarin.Forms:
Multi-Targeting in Visual Studio, SDK Versions, MvvmCross, Authenticating with ADAL
Refactoring MvvmCross and Services, Effects, Navigation with MvvmCross
Presentation Attributes in MvvmCross, Resources and Styles, Resource Dictionaries

In previous posts we’ve looked at using resources and then styles and resource dictionaries in order to manage the style of elements throughout our application. Whilst Xamarin.Forms provides an out-of-the-box cross platform experience, the reality is that if you don’t tweak the layout a bit for each platform, your application is going to look very generic, and not in a good way. In this post we’ll look at some of the different ways that you can adjust layout based on which target platform the application is running on.

The starting point is to look at an individual element, such as a Label, and how an individual property can be adjusted for different platforms using an OnPlatform element.

<Label Text="Hello World!">
     <Label.FontAttributes>
         <OnPlatform x:TypeArguments="FontAttributes">
             <On Platform="Android" Value="Italics"/>
             <On Platform="iOS" Value="None"/>
             <On Platform="UWP" Value="Bold"/>
         </OnPlatform>

     </Label.FontAttributes>
</Label>

In this example instead of supplying a value for the FontAttributes attribute we’ve expanded into long form using a Label.FontAttributes element. Nested within this element we create an OnPlatform element, supplying the TypeArguments attribute to indicate what Type of value the OnPlatform is going to produce. Within the OnPlatform element we then have an On element for each platform we want to specify a value for.

Since we’re not a big fan of having literals specified for an individual element, and even more so when doing per-platform styling, we can also define resources using the OnPlatform syntax:

<OnPlatform x:Key ="FeatureColor" x:TypeArguments="Color">
     <On Platform="Android" Value="Red"/>
     <On Platform="iOS" Value="Green"/>
     <On Platform="UWP"  Value="Blue"/>
</OnPlatform>

This defines a resource, FeatureColor, within a ResourceDictionary, with different colors defined for each of the three platforms.

As you can see, defining values for each platform can start to add a lot of bloat to your XAML. A work around for this is to use a similar technique that we’ve discussed previously where we include files on a per platform basis. For resources this requires a bit of fiddling, so there are a few steps involved. We need to start with the csproj for the UI project and adjust the first ItemsGroup to remove all XAML files that are within the Platforms folder. Next we need to selectively add back the XAML files based on what the target platform is. The bold lines in the following XAML from the csproj file indicate these changes.

<ItemGroup>
   <Compile Remove="Platforms\**\*.cs" />
   <None Include="Platforms\**\*.cs" />
   <EmbeddedResource Remove="Platforms\**\*.xaml" />
   <None Include="Platforms\**\*.xaml" />
</ItemGroup>

<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) ">
   <Compile Include="Platforms\Netstandard\**\*.cs" />
   <EmbeddedResource Include="Platforms\Netstandard\**\*.xaml" />
</ItemGroup>

<ItemGroup Condition=" $(TargetFramework.StartsWith('uap')) ">
   <Compile Include="Platforms\Uap\**\*.cs" />
   <EmbeddedResource Include="Platforms\Uap\**\*.xaml" />
</ItemGroup>

<ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
   <Compile Include="Platforms\Ios\**\*.cs" />
   <EmbeddedResource Include="Platforms\Ios\**\*.xaml" />
</ItemGroup>

<ItemGroup Condition=" $(TargetFramework.StartsWith('MonoAndroid')) ">
   <Compile Include="Platforms\Android\**\*.cs" />
   <EmbeddedResource Include="Platforms\Android\**\*.xaml" />
</ItemGroup>

Next we need to create platform specific XAML files. I simply copied the LiteralResources.xaml and LiteralResources.xaml.cs into each of the Platform folders and renamed both the files and the class name to PlatformLiteralResources. In my case the project structure looks like the following:

image

You’ll notice that the PlatformLiteralResources.xaml is only showing under the Netstandard – this appears to be a bit of a bug in Visual Studio as the file exists and should appear in the project structure for all the platforms. The only thing left to do is define some platform resources and to link the PlatformLiteralResources class into the resource dictionary hierarchy:

PlatformLiteralResources

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="
http://xamarin.com/schemas/2014/forms"
                     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                     xmlns:res="clr-namespace:StrataPark.UI.Resources"
                      x:Class="StrataPark.UI.Resources.PlatformLiteralResources">
     <ResourceDictionary.MergedDictionaries>
         <res:LiteralResources />
     </ResourceDictionary.MergedDictionaries>
    <x:Double x:Key="DefaultLabelFontSize">24</x:Double>
</ResourceDictionary>

StyleAndTemplateResources

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="
http://xamarin.com/schemas/2014/forms"
                     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                     xmlns:res="clr-namespace:StrataPark.UI.Resources"
                     x:Class="StrataPark.UI.Resources.StyleAndTemplateResources">
     <ResourceDictionary.MergedDictionaries>
         <res:PlatformLiteralResources />
     </ResourceDictionary.MergedDictionaries>

    <!-- Other resources -->
</ResourceDictionary>

Note that the hierarchy is now App.xaml >> StylesAndTemplateResources >> PlatformLiteralResources >> LiteralResources.

And there you have it, you can now define platform specific literals in the PlatformLiteralResources xaml file without worrying about using the OnPlatform element.