Connect(); 2016

Volume 31 Number 12

[Mobile]

Embedding Native Views in Your Xamarin.Forms Apps

By Charles Petzold

When Xamarin.Forms debuted less than three years ago, application programmers immediately recognized it as a powerful and versatile solution for cross-platform mobile development. You can create a Xamarin.Forms solution in Visual Studio and write an entire mobile app in C#, with or without XAML, that you can compile for iOS, Android and the Universal Windows Platform (UWP). On the macOS you can use Xamarin Studio to target iOS and Android.

Xamarin.Forms includes some 20 controls, such as Button, Slider and Entry, that are often referred to as views because they derive from the Xamarin.Forms.View class. These are rendered on the various platforms using native views, or widgets, or controls, or elements as they’re called in various platforms. For example, the Xamarin.Forms Entryview is mapped to an iOS UITextField, an Android EditText  and a UWP TextBox.

What makes this possible is an extensible infrastructure of platform renderers that encapsulate the native view and expose a uniform collection of properties and events that correspond to the API of the corresponding Xamarin.Forms view. You can define your own custom views and support them with your own renderers, but it’s not a trivial job. For that reason, Xamarin.Forms has been enhanced recently to introduce various extensibility shortcuts that avoid the hassle of writing renderers.

One of the most compelling of these shortcuts is called native views, a feature that lets you instantiate native iOS, Android and UWP views alongside the normal Xamarin.Forms views. That’s what this article is all about. The story of native views begins with code, but becomes much more interesting when XAML gets involved.

Platform-Specific Extension Methods

Xamarin.Forms supports native views with several platform-­specific classes. Each of the Xamarin.Forms platforms contains a LayoutExtensions class with an extension method named ToView that you can call on descendants of the following native types:

  • iOS: UIKit.UIView
  • Android: Android.Views.View
  • UWP: Windows.UI.Xaml.FrameworkElement

Each version of the ToView method returns a platform-­specific instance of NativeViewWrapper, which derives from Xamarin.Forms.View. NativeViewWrapper inherits all the public and protected members of Xamarin.Forms.View, and despite being platform-specific, is treated within Xamarin.Forms like a normal View instance. A second extension method is Add, which performs the ToView operation while adding the View object to a layout such as StackLayout.

Each platform’s version of NativeViewWrapper has a corresponding renderer: a class named NativeViewWrapperRenderer that’s simpler than most renderers because it doesn’t need to support any properties, methods or events of the underlying native control. (Xamarin.Forms is open source, so you can examine these and related classes at github.com/xamarin/Xamarin.Forms.)

Let’s see how this works.

Normally a Xamarin.Forms solution contains tiny stub application projects for each platform and a common Portable Class Library (PCL) that contains the bulk of your Xamarin.Forms application. However, when using native views in code, you can’t use a PCL. Instead, you’ll need to put your Xamarin.Forms code in a shared project, which at Xamarin is often called a Shared Asset Project or SAP. In the New Project dialog of Visual Studio select Blank App (Xamarin.Forms Shared) rather than the usual Blank App (Xamarin.Forms Portable). (In Xamarin Studio you select between Portable Class Library or Shared Library with radio buttons.) The code in this shared project is effectively an extension of each application, which means you can use C# conditional compilation directives (#if, #elif and #endif) to delimit the platform-specific code.

Among the downloadable code for this article is the HelloNativeViews program, with an SAP that contains the page class shown in Figure 1. (Note that normal code indentation practices have been altered in some code samples to fit in available space.) This class creates a label for each platform: a UILabel for iOS, a TextView for Android and a TextBlock for the UWP. It then calls ToView to convert each of these objects to a Xamarin.Forms.View object, but which is actually a NativeViewWrapper object. The page can then apply Xamarin.Forms properties such as Vertical Options and Horizontal Options to the View and finally set it to the Content property of the page. Figure 2 shows the program running on all three platforms, each with a font distinctive to that platform.

Figure 1 The HelloNativeViews Class

using System;
using Xamarin.Forms;
#if __IOS__
using Xamarin.Forms.Platform.iOS;
using UIKit;
#elif __ANDROID__
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using Android.Widget;
#elif WINDOWS_UWP
using Xamarin.Forms.Platform.UWP;
using Windows.UI.Text;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
#endif
namespace HelloNativeViews
{
  public class HelloNativeViewsPage : ContentPage
  {
    public HelloNativeViewsPage()
    {
      View view = null;
#if __IOS__
      UILabel label = new UILabel
      {
        Text = "Hello iOS Native!",
        Font = UIFont.FromName("Papyrus", 32f),
      };
      view = label.ToView();
#elif __ANDROID__
      TextView textView = new TextView(Forms.Context)
      {
        Text = "Hello Android Native!",
        Typeface = Typeface.Create("cursive", TypefaceStyle.Normal),
        TextSize = 32f
      };
      view = textView.ToView();
#elif WINDOWS_UWP
      TextBlock textBlock = new TextBlock
      {
        Text = "Hello Windows Native!",
        FontFamily = new FontFamily("Georgia"),
        FontStyle = FontStyle.Italic,
        FontSize = 32
      };
      view = textBlock.ToView();
#endif
      view.HorizontalOptions = LayoutOptions.Center;
      view.VerticalOptions = LayoutOptions.Center;
      Content = view;
    }
  }
}

The HelloNativeViews Program on iOS, Android and Windows 10 Mobile
Figure 2 The HelloNativeViews Program on iOS, Android and Windows 10 Mobile

Of course, you can get the same effect entirely in standard Xamarin.Forms by setting the FontFamily property of a Label to a Device.OnPlatform method call that references the three font family names. But I think you can see how you can expand this technique in a much more sophisticated manner by taking advantage of the specific APIs that each platform supports.

Sometimes you might need to apply custom sizing methods to these views so they behave properly as children of a Xamarin.Forms layout object. Check out the article on the Xamarin developer site at bit.ly/2dhBxDk for more details.

While this is an interesting technique, of course it sure would be much nicer to instantiate these native views directly in XAML.

XAML Native Views

As of Xamarin.Forms 2.3.3 (which is in pre-release state as I write this article) you can indeed embed native views in your Xamarin.Forms XAML files. You can set properties and event handlers on these views. You can include views from multiple platforms side-by-side in the same XAML file, and they can interact with all the other Xamarin.Forms views.

One key to this feature is an extension to the XML namespace (xmlns) declaration for XAML files. Custom XML namespaces in Xamarin.Forms commonly use clr-namespace to denote the Common Language Runtime (CLR) namespace, and assembly for the assembly. The new item is targetPlatform, which indicates to which platform this particular XML namespace applies. You set this item to a member of the Xamarin.Forms TargetPlatform enumeration: iOS, Android or Windows for the UWP.

For example, you can define the following XML namespace:

xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"

You can use this prefix within the XAML file to reference any class or structure in the iOS UIKit namespace, for example:

<ios:UILabel Text="Hello iOS Native!"
             TextColor="{x:Static ios:UIColor.Red}"
             View.VerticalOptions="Center"
             View.HorizontalOptions="Center" \>

Text and TextColor are properties of UILabel, and TextColor is set to a static read-only property of UIColor. However, notice that the VerticalOptions and HorizontalOptions attributes are prefaced by View, and they are indeed properties of the Xamarin.Forms View class. This syntax—a class, dot and property name—is commonly used for attached bindable properties, and here it indicates that these properties are later applied to the View object that results from the conversion of UILabel to a NativeViewWrapper object. You can use this syntax only for properties that are backed by bindable properties.

To reference Android widgets you’ll need an XML namespace something like this (which I’ve shown on three lines here, but which in the XAML file must be on one line without spaces):

xmlns:androidWidget="clr-namespace:Android.Widget;
                       assembly=Mono.Android;
                         targetPlatform=Android"

You can use any name for this namespace, of course, but I avoided using just android because Android is a little trickier than iOS: Widget constructors generally require the Android Context object as an argument. This Context object is available as a public static property of the Forms class in the Xamarin.Forms namespace in the Xamarin.Forms.Platform.Android assembly. To obtain this Context object, you’ll need this XML namespace (which also must be on one line in the XAML file):

xmlns:formsAndroid="clr-namespace:Xamarin.Forms;
                      assembly=Xamarin.Forms.Platform.Android;
                        targetPlatform=Android"

You can then instantiate an Android widget in XAML by passing an argument to the constructor using the x:Arguments attribute with an x:Static markup extension:

<androidWidget:TextView x:Arguments="{x:Static formsAndroid:Forms.Context}"
                        Text="Hello Android Native!" />

The assembly name for the UWP is quite long, specifically: Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime. For Android and the UWP, you’ll probably need multiple XML namespace specifications for the various CLR namespaces used for the various classes, structures and enumerations that tend to be involved in UI markup.

Keep in mind that when the XAML parser encounters one of these native views, it doesn’t have access to type converters commonly used to convert XAML text strings into objects, so the markup tends to be a little more extensive to match property and object types. Often you’ll need to create objects explicitly in XAML using constructors or factory methods, which means that if you’re not familiar with the x:Arguments tag and the x:FactoryMethod element, now is a good time to learn.

This is important: You can’t enable XAML compilation when using XAML native views. The compile-time XAML parser doesn’t have references to these native types. The parsing must be delayed until runtime, and at that point the XAML parser simply ignores anything with an XML namespace prefix that has a targetPlatform that doesn’t match the platform on which the program is running. (I’m told the Xamarin.Forms developers are working on removing this restriction.)

You can’t use styles with native views. Styles can target only properties that are backed by BindableProperty objects, and native views don’t have such properties.

Because these XAML native views are instantiated by the runtime XAML parser, you can include them in a XAML file in either a PCL project or an SAP. However, if you need to refer to a native view from the codebehind file, you must use an SAP and delimit the platform-specific code with C# conditional compilation directives.

Here’s another restriction: With either a PCL or an SAP, you can’t use x:Name on a XAML native view. The problem is that the compile-time XAML parser generates a code file containing these named objects as fields, but if these fields are based on platform-specific types, the generated code can’t be compiled for all the platforms.

The XamlNativeViewDemo program contains the XAML file shown in Figure 3 with three platform-specific red-text strings and three platform-specific buttons. Pressing a button invokes an event handler in the codebehind file that rotates the text in a circle.

Figure 3 The XAML File for the XamlNativeViewDemo Program

<ContentPage xmlns="https://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlNativeViewDemo"
             xmlns:ios="clr-namespace:UIKit; ... "
             xmlns:androidWidget="clr-namespace:Android.Widget; ... "
             xmlns:androidGraphics="clr-namespace:Android.Graphics; ... "
             xmlns:formsAndroid="clr-namespace:Xamarin.Forms; ... "
             xmlns:winui="clr-namespace:Windows.UI; ... "
             xmlns:winText="clr-namespace:Windows.UI.Text; ... "
             xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls; ... "
             xmlns:winMedia="clr-namespace:Windows.UI.Xaml.Media; ... "
             x:Class="XamlNativeViewDemo.XamlNativeViewDemoPage">
  <Grid x:Name="grid">
    <ContentView x:Name="textToRotateParent"
                 Grid.Row="0"
                 VerticalOptions="Center"
                 HorizontalOptions="Center">
      <ios:UILabel Text="Text to Rotate"
                   TextColor="{x:Static ios:UIColor.Red}">
        <ios:UILabel.Font>
          <ios:UIFont x:FactoryMethod="FromName">
            <x:Arguments>
              <x:String>Papyrus</x:String>
              <x:Single>32</x:Single>
            </x:Arguments>
          </ios:UIFont>
        </ios:UILabel.Font>
      </ios:UILabel>
      <androidWidget:TextView x:Arguments=
                            "{x:Static formsAndroid:Forms.Context}"
                              Text="Text to Rotate"
                              TextSize="32">
        <androidWidget:TextView.Typeface>
          <androidGraphics:Typeface x:FactoryMethod="Create">
            <x:Arguments>
              <x:String>cursive</x:String>
              <androidGraphics:
                TypefaceStyle>Normal</androidGraphics:TypefaceStyle>
            </x:Arguments>
          </androidGraphics:Typeface>
        </androidWidget:TextView.Typeface>
      </androidWidget:TextView>
      <winControls:TextBlock Text="Text to Rotate"
                             FontSize="32"
                             FontStyle="{x:Static winText:FontStyle.Italic}">
        <winControls:TextBlock.FontFamily>
          <winMedia:FontFamily>
            <x:Arguments>
              <x:String>Georgia</x:String>
            </x:Arguments>
          </winMedia:FontFamily>
        </winControls:TextBlock.FontFamily>
        <winControls:TextBlock.Foreground>
          <winMedia:SolidColorBrush Color="{x:Static winui:Colors.Red}" />
        </winControls:TextBlock.Foreground>
      </winControls:TextBlock>
    </ContentView>
    <ContentView x:Name="rotateButtonParent"
                 Grid.Row="1"
                 VerticalOptions="Center"
                 HorizontalOptions="Center">
      <ios:UIButton TouchUpInside="OnButtonTap" />
      <androidWidget:Button x:Arguments="{x:Static formsAndroid:Forms.Context}"
                            Text="Rotate the Text"
                            Click="OnButtonTap" />
      <winControls:Button Content="Rotate the Text" />
    </ContentView>
  </Grid>
</ContentPage>

I optimistically began XamlNativeViewDemo as a PCL project, but it soon became apparent that the XAML needed a little help. You can’t even set the text on an iOS UIButton from XAML. You need to call a method, and that requires code. Similarly, you can’t set the text color on an Android TextView with a property, and the Click handler for the UWP Button is of type RoutedEventHandler, which involves a RoutedEventArgs object that doesn’t derive from EventArgs and, hence, requires a platform-specific handler.

These problems implied that the codebehind file needed to compensate for the limitations of the XAML, which further implied that I needed to abandon the PCL and use an SAP instead. Even with an SAP, you can’t use x:Name on the native views, so I put the native views in a ContentView with an x:Name attribute to get access to them in the codebehind file, which is shown in Figure 4. The ContentView is also handy for setting some layout properties (such as VerticalOptions and HorizontalOptions) to avoid a lot of repetition on the native views.

Figure 4 The Codebehind File for the XamlNativeViewDemo Program

using System;
using Xamarin.Forms;
namespace XamlNativeViewDemo
{
  public partial class XamlNativeViewDemoPage : ContentPage
  {
    View viewTextToRotate;
    public XamlNativeViewDemoPage()
    {
      InitializeComponent();
      viewTextToRotate = textToRotateParent.Content;
      View rotateButton = rotateButtonParent.Content;
#if __ANDROID__
      // Set Android text color
      var wrapper =
        (Xamarin.Forms.Platform.Android.NativeViewWrapper)
          viewTextToRotate;
      var textView = (Android.Widget.TextView)wrapper.NativeView;
      textView.SetTextColor(Android.Graphics.Color.Red);
#endif
#if __IOS__
      // Set iOS button text and color
      var wrapper = (Xamarin.Forms.Platform.iOS.NativeViewWrapper)rotateButton;
      var button = (UIKit.UIButton)wrapper.NativeView;
      button.SetTitle("Rotate the Text", UIKit.UIControlState.Normal);
      button.SetTitleColor(UIKit.UIColor.Black, UIKit.UIControlState.Normal);
#endif
#if WINDOWS_UWP
      // Set UWP button Click handler
      var wrapper = (Xamarin.Forms.Platform.UWP.NativeViewWrapper)rotateButton;
      var button = (Windows.UI.Xaml.Controls.Button)wrapper.NativeElement;
      button.Click += (sender, args) => OnButtonTap(sender, EventArgs.Empty);
#endif
    }
    void OnButtonTap(object sender, EventArgs args)
    {
      viewTextToRotate.RelRotateTo(360);
    }
  }
}

I’ve fully qualified all the platform-specific types in the codebehind file for clarity and to avoid a bunch of platform using directives. The key to getting the underlying native view from the NativeViewWrapper is a property named NativeView (for iOS and Android) or NativeElement (for UWP).

The iOS and Android buttons can share the same event handler in the codebehind file because it’s defined as an EventHandler delegate. But the UWP Button must use a separate event handler of type RoutedEvent­Handler, which is implemented by simply calling the handler used for iOS and Android.

Another approach to getting access to the native views in the codebehind file involves enumerating children of layout objects and searching for various types or ids. All three platforms define a Tag property—integer on iOS and object on Android and UWP—that you can use for this purpose.

I find the XamlNativeViewDemo program unsatisfactory because I don’t like using SAP for my Xamarin.Forms apps. I don’t know if you’re as passionate as I am about preferring PCL to SAP, but if you are, you’ll be happy to know that the final two programs in this article are pure PCL.

Data Bindings and MVVM

One of the best ways to avoid code in the codebehind file is to structure your application around the Model-View-ViewModel architecture (MVVM). All the interactions among the views on the page occur within the platform-independent ViewModel. The ViewModel connects to the View (the UI) using Xamarin.Forms data bindings. The data-binding sources are properties of the ViewModel while the data-binding targets are properties of a view.

But wait a minute. Earlier I mentioned that you can’t use a Style for native views because styled properties must be backed by bindable properties. Data bindings have the same restriction: The target property of a data binding—and with MVVM these targets are always views on the page—must also be a property backed by a BindableProperty object. So how can you set bindings on native views?

Here’s the good news: To support data bindings on XAML native views, each platform contains SetBinding extension methods that automatically generate ad hoc BindableProperty objects on the fly. These BindableProperty objects allow the native property values to be changed from the ViewModel.

Now you might be considering another problem: In many cases these data bindings need to go both ways—not only from source to target but from target to source. Changes in the UI view must be reflected in ViewModel properties. The Xamarin.Forms BindableProperty infrastructure supports notifications of property changes through the INotifyPropertyChanged interface, but native views don’t support this interface, so how can the Binding object know when a property of a native view changes value?

The Binding class now has a new property: UpdateSourceEventName. In the binding markup extension in XAML you can set this property to the name of an event in the native view that signals when the target property has changed.

The PlatformRgbSliders program shown in Figure 5 is a simple example. (Some repetitious markup has been replaced with ellipses.) The XAML file contains three sets of platform-specific sliders, which I’ve enhanced by giving them a color corresponding to their function in the program. (It wasn’t possible to do this for the Android SeekBar.) The RgbColorViewModel set to the BindingContext of the page defines Red, Green and Blue properties that it uses to construct a Color property.

Figure 5 The XAML File for the PlatformRgbSliders Program

<ContentPage xmlns="https://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:PlatformRgbSliders"
             xmlns:ios="clr-namespace:UIKit; ... "
             xmlns:androidWidget="clr-namespace:Android.Widget; ... "
             xmlns:formsAndroid="clr-namespace:Xamarin.Forms; ... "
             xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls; ... "
             xmlns:winMedia="clr-namespace:Windows.UI.Xaml.Media; ... "
             xmlns:winui="clr-namespace:Windows.UI; ... "
             x:Class="PlatformRgbSliders.PlatformRgbSlidersPage">
  <ContentPage.Padding>
    <OnPlatform x:TypeArguments="Thickness"
                iOS="0, 20, 0, 0" />
  </ContentPage.Padding>
  <ContentPage.Resources>
    <ResourceDictionary>
      <local:DoubleToSingleConverter x:Key="doubleToSingle" />
      <local:DoubleToIntConverter x:Key="doubleToInt"
                                        Multiplier="256" />
    </ResourceDictionary>
  </ContentPage.Resources>
  <ContentPage.BindingContext>
    <local:RgbColorViewModel Color="Gray" />
  </ContentPage.BindingContext>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="3*" />
      <RowDefinition Height="*" />
      <RowDefinition Height="*" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <BoxView Grid.Row="0"
             Color="{Binding Color}" />
    <ios:UISlider Grid.Row="1"
                  ThumbTintColor="{x:Static ios:UIColor.Red}"
                  MinimumTrackTintColor="{x:Static ios:UIColor.Black}"
                  MaximumTrackTintColor="{x:Static ios:UIColor.Red}"
                  Value="{Binding Red,
                          Mode=TwoWay,
                          UpdateSourceEventName=ValueChanged,
                          Converter={StaticResource doubleToSingle}}"/>
    ...
    <androidWidget:SeekBar x:Arguments=
                           "{x:Static formsAndroid:Forms.Context}"
                           Grid.Row="2"
                           Max="256"
                           Progress="{Binding Green,
                             Mode=TwoWay,
                             UpdateSourceEventName=ProgressChanged,
                             Converter={StaticResource doubleToInt}}" />
    ...
    <winControls:Slider Grid.Row="3"
                        Maximum="1"
                        StepFrequency="0.01"
                        IsThumbToolTipEnabled="True"
                        Value="{Binding Blue,
                                Mode=TwoWay,
                                UpdateSourceEventName=ValueChanged}">
      <winControls:Slider.Foreground>
        <winMedia:SolidColorBrush Color=
          "{x:Static winui:Colors.Blue}" />
      </winControls:Slider.Foreground>
    </winControls:Slider>
  </Grid>
</ContentPage>

The binding on the iOS UISlider requires a value converter to convert the double values in the ViewModel to float values, and the Android SeekBar requires a value converter to convert the double values to integer values. You can see that all the data bindings use the UpdateSourceEventName property so the Binding class can be notified when the user has changed the slider value. The result is shown in Figure 6.

The PlatformRgbSliders Program Running on the Three Platforms
Figure 6 The PlatformRgbSliders Program Running on the Three Platforms

Here’s an interesting experiment: Remove the UpdateSourceEventName items from the Windows Slider bindings. The program still works. This is because Xamarin.Forms is able to use the notification mechanism built into the UWP dependency properties. Also, work is being done to allow the UpdateSourceEventName to be eliminated on iOS views if the view implements key-value observing.

PlatformRgbSliders has no problem-specific code in the codebehind file, so there’s no problem with using a PCL. But, admittedly, PlatformRgbSliders is simple. Will you be able to use PCLs in larger programs?

At first, it doesn’t look promising: Many iOS and Android native views are just not conducive to instantiating in XAML, and there’s really no reason why they should be. The problem can be summarized: There aren’t always enough properties in iOS and Android views for the important options that need to be set and accessed. Instead, there are too many methods.

Too Many Methods

To make iOS and Android more amenable to XAML, you need to replace some of the methods with properties. Obviously the UWP is much better in this regard because it’s designed for XAML, but as you saw, UWP event handlers are often based on a platform-­specific delegates.

The obvious solution to this problem is to subclass the platform-specific views in wrappers that define a more XAML-friendly API that consists of properties and platform-independent events. You might also be starting to see that the real power of embedding native views in XAML comes when you create (or consume) custom iOS, Android and UWP views to use in your Xamarin.Forms application.

But where do you put these classes?

If you’re using an SAP, you could put them in the SAP and surround them with C# conditional compilation directives. But if you want to use a PCL—and you usually want to use a PCL—then you can’t do that. A PCL can only reference another PCL, and by definition a PCL can’t contain any iOS, Android or UWP code. It’s a bit of a puzzle.

At least initially.

Keep in mind that you’re not compiling the XAML. The XAML is parsed at compile time but that’s mostly to generate a code file containing fields corresponding to the x:Name attributes. You’ve already seen that uncompiled XAML can contain references to iOS, Android and UWP classes. These references are resolved at run time rather than compile time, and that makes all the difference in the world. At run time, the XAML parser has access to all the assemblies that comprise the application, and that includes the individual platform start-up projects.

This means that you can put platform-specific classes in the platform application projects, or you can put the code in platform-specific libraries referenced by these application projects. These classes can be referenced from XAML. It might seem weird and unnatural at first for a XAML file in a PCL to reference classes in application assemblies, but it works just fine.

The PlatformSpinners solution demonstrates this technique. The idea is to use the iOS UIPickerView, the Android Spinner and the UWP ComboBox for selecting something from a list, but expectedly the UIPickerView and Spinner have some methods that need to be exposed as properties. In addition, the UIPickerView requires a data model that must be implemented in code.

For this reason, the PlatformSpinners.iOS application project contains a PickerDataModel and a PropertiedUIPickerView that derives from UIPickerView, so called because it adds essential properties to UIPickerView. The PlatformSpinners.Droid project contains a PropertiedSpinner that derives from Spinner.

The PlatformSpinners PCL contains a simple view model that exposes a collection of the Xamarin.Forms color names, and converts these color names to the actual colors. Figure 7 shows the complete XAML file except for the long XML namespaces, and Figure 8 shows it running on the three platforms with the Android Spinner and UWP ComboBox opened to show the options.

Figure 7 The PlatformSpinners XAML File

<ContentPage xmlns="https://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:PlatformSpinners"
             xmlns:iosLocal="clr-namespace:PlatformSpinners.iOS; ... "
             xmlns:androidLocal="clr-namespace:PlatformSpinners.Droid; ... "
             xmlns:formsAndroid="clr-namespace:Xamarin.Forms; ... "
             xmlns:winControls="clr-namespace:Windows.UI.Xaml.Controls; ... "
             x:Class="PlatformSpinners.PlatformSpinnersPage">
  <ContentPage.Padding>
    <OnPlatform x:TypeArguments="Thickness"
                iOS="0, 20, 0, 0" />
  </ContentPage.Padding>
  <ContentPage.BindingContext>
    <local:ColorNameViewModel SelectedColorName="Black" />
  </ContentPage.BindingContext>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <BoxView Grid.Row="0"
             Color="{Binding SelectedColor}" />
    <iosLocal:PropertiedUIPickerView Grid.Row="1"
                                     Items="{Binding ColorNames}"
                                     SelectedItem=
                                       "{Binding SelectedColorName,
                                       Mode=TwoWay,
                                       UpdateSourceEventName=ValueChanged}"/>
    <androidLocal:PropertiedSpinner x:Arguments=
                                      "{x:Static formsAndroid:Forms.Context}"
                                    Grid.Row="1"
                                    View.VerticalOptions="Center"
                                    Items="{Binding ColorNames}"
                                    SelectedObject=
                                      "{Binding SelectedColorName,
                                      Mode=TwoWay,
                                      UpdateSourceEventName=ItemSelected}" />
    <winControls:ComboBox Grid.Row="1"
                          View.VerticalOptions="Center"
                          ItemsSource="{Binding ColorNames}"
                          SelectedItem=
                            "{Binding SelectedColorName,
                            Mode=TwoWay,
                            UpdateSourceEventName=SelectionChanged}"/>
  </Grid>
</ContentPage>

PlatformSpinners Running on the Three Platforms
Figure 8 PlatformSpinners Running on the Three Platforms

This technique of providing additional properties is something you’ll likely want to do when consuming third-party iOS and Android custom views. Subclass the view to make it conducive to XAML and MVVM data bindings, and in general you’ll be home free.

Is It Really Easier?

You’ve seen how Xamarin.Forms now allows you to reference native views—or classes derived from native views—directly in XAML rather than hiding the platform-specific code away in a renderer and defining a platform-independent interface to it.

So, you might ask: Is this really easier than creating renderers?

Yes, it is.


Charles Petzold has written numerous articles for MSDN Magazine and its predecessor, Microsoft Systems Journal, over the past 30 years. He now works in the Xamarin documentation group at Microsoft and is the author of “Creating Mobile Apps with Xamarin.Forms,” a free book available for downloading from the Xamarin Web site.

Thanks to the following Microsoft technical experts for reviewing this article: David Britch, Stephane Delcroix and Rui Marinho
David Britch works in the Xamarin documentation group at Microsoft. He has written for a range of software development publications including books, guidance documentation, reference implementations, whitepapers, videos, and instructor-led training courses.

Stephane Delcroix works on the Xamarin.Forms team at Microsoft on XAML and more.

Rui Marinho is a Portuguese software engineer working on the Xamarin.Forms team at Microsoft, and is an avid coder and open-source contributor.


Discuss this article in the MSDN Magazine forum