Native Embedding

Adding Platform-Specific Controls to a Xamarin.Forms Layout

PDF for offline use:
Sample Code:

Let us know how you feel about this.


0/250
Thanks for the feedback!

last updated: 2016-04

Platform-specific controls can be directly added to a Xamarin.Forms layout. This article demonstrates how to add platform-specific controls to a Xamarin.Forms layout, and how to override the layout of custom controls in order to correct their measurement API usage.

Overview

Any Xamarin.Forms control that allows Content to to be set, or that has a Children collection, can add platform-specific controls. For example, an iOS UILabel can be directly added to the ContentView.Content property, or to the StackLayout.Children collection. However, note that this functionality requires the use of #if defines in Xamarin.Forms Shared Project solutions, and isn't available from Xamarin.Forms Portable Class Library (PCL) solutions.

The following screenshots demonstrate platform-specific controls having been added to a Xamarin.Forms StackLayout:

The ability to add platform-specific controls to a Xamarin.Forms layout is enabled by two extension methods on each platform:

  • Add – adds a platform-specific control to the Children collection of a layout.
  • ToView – takes a platform-specific control and wraps it as a Xamarin.Forms View that can be set as the Content property of a control.

Using these methods in a Xamarin.Forms shared project requires importing the appropriate platform-specific Xamarin.Forms namespace:

  • iOS – Xamarin.Forms.Platform.iOS
  • Android – Xamarin.Forms.Platform.Android
  • Windows Runtime – Xamarin.Forms.Platform.WinRT
  • Universal Windows Platform (UWP) – Xamarin.Forms.Platform.UWP

Adding Platform-Specific Controls on Each Platform

The following sections demonstrate how to add platform-specific controls to a Xamarin.Forms layout on each platform.

iOS

The following code example demonstrates how to add a UILabel to a StackLayout and a ContentView:

var uiLabel = new UILabel {
  MinimumFontSize = 14f,
  Lines = 0,
  LineBreakMode = UILineBreakMode.WordWrap,
  Text = originalText,
};
stackLayout.Children.Add (uiLabel);
contentView.Content = uiLabel.ToView();

The example assumes that the stackLayout and contentView instances have previously been created in XAML or C#.

Android

The following code example demonstrates how to add a TextView to a StackLayout and a ContentView:

var textView = new TextView (Forms.Context) { Text = originalText, TextSize = 14 };
stackLayout.Children.Add (textView);
contentView.Content = textView.ToView();

The example assumes that the stackLayout and contentView instances have previously been created in XAML or C#.

Windows Runtime and Universal Windows Platform

The following code example demonstrates how to add a TextBlock to a StackLayout and a ContentView:

var textBlock = new TextBlock
{
    Text = originalText,
    FontSize = 14,
    FontFamily = new FontFamily("HelveticaNeue"),
    TextWrapping = TextWrapping.Wrap
};
stackLayout.Children.Add(textBlock);
contentView.Content = textBlock.ToView();

The example assumes that the stackLayout and contentView instances have previously been created in XAML or C#.

Overriding Platform Measurements for Custom Controls

Custom controls on each platform often only correctly implement measurement for the layout scenario for which they were designed. For example, a custom control may have been designed to only occupy half of the available width of the device. However, after being shared with other users, the custom control may be required to occupy the full available width of the device. Therefore, it can be necessary to override a custom controls measurement implementation when being reused in a Xamarin.Forms layout. For that reason, the Add and ToView extension methods provide overrides that allow measurement delegates to be specified, which can override the custom control layout when it's added to a Xamarin.Forms layout.

The following sections demonstrate how to override the layout of custom controls, in order to correct their measurement API usage.

iOS

The following code example shows the CustomControl class, which inherits from UILabel:

public class CustomControl : UILabel
{
  public override string Text {
    get { return base.Text; }
    set { base.Text = value.ToUpper (); }
  }

  public override CGSize SizeThatFits (CGSize size)
  {
    return new CGSize (size.Width, 150);
  }
}

An instance of this control is added to a StackLayout, as demonstrated in the following code example:

var customControl = new CustomControl {
  MinimumFontSize = 14,
  Lines = 0,
  LineBreakMode = UILineBreakMode.WordWrap,
  Text = "This control has incorrect sizing - there's empty space above and below it."
};
stackLayout.Children.Add (customControl);

However, because the CustomControl.SizeThatFits override always returns a height of 150, the control will be displayed with empty space above and below it, as shown in the following screenshot:

A solution to this problem is to provide a GetDesiredSizeDelegate implementation, as demonstrated in the following code example:

SizeRequest? FixSize (NativeViewWrapperRenderer renderer, double width, double height)
{
  var uiView = renderer.Control;

  if (uiView == null) {
    return null;
  }

  var constraint = new CGSize (width, height);

  // Let the CustomControl determine its size (which will be wrong)
  var badRect = uiView.SizeThatFits (constraint);

  // Use the width and substitute the height
  return new SizeRequest (new Size (badRect.Width, 70));
}

This method uses the width provided by the CustomControl.SizeThatFits method, but substitutes the height of 150 for a height of 70. When the CustomControl instance is added to the StackLayout, the FixSize method can be specified as the GetDesiredSizeDelegate in order to fix the bad measurement provided by the CustomControl class:

stackLayout.Children.Add (customControl, FixSize);

This results in the custom control being displayed correctly, without empty space above and below it, as shown in the following screenshot:

Android

The following code example shows the CustomControl class, which inherits from TextView:

public class CustomControl : TextView
{
  public CustomControl (Context context) : base (context)
  {
  }

  protected override void OnMeasure (int widthMeasureSpec, int heightMeasureSpec)
  {
    int width = MeasureSpec.GetSize (widthMeasureSpec);

    // Force the width to half of what's been requested.
    // This is deliberately wrong in order to demonstrate providing an override to fix it with.
    int widthSpec = MeasureSpec.MakeMeasureSpec (width / 2, MeasureSpec.GetMode (widthMeasureSpec));

    base.OnMeasure (widthSpec, heightMeasureSpec);
  }
}

An instance of this control is added to a StackLayout, as demonstrated in the following code example:

var customControl = new CustomControl (Forms.Context) {
  Text = "This control has incorrect sizing - it doesn't occupy the available width of the device.",
  TextSize = 14
};
stackLayout.Children.Add (customControl);

However, because the CustomControl.OnMeasure override always returns half of the requested width, the control will be displayed occupying only half the available width of the device, as shown in the following screenshot:

A solution to this problem is to provide a GetDesiredSizeDelegate implementation, as demonstrated in the following code example:

SizeRequest? FixSize (NativeViewWrapperRenderer renderer, int widthConstraint, int heightConstraint)
{
  var nativeView = renderer.Control;

  if ((widthConstraint == 0 && heightConstraint == 0) || nativeView == null) {
    return null;
  }

  int width = Android.Views.View.MeasureSpec.GetSize (widthConstraint);
  int widthSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec (
    width * 2, Android.Views.View.MeasureSpec.GetMode (widthConstraint));
  nativeView.Measure (widthSpec, heightConstraint);
  return new SizeRequest (new Size (nativeView.MeasuredWidth, nativeView.MeasuredHeight));
}

This method uses the width provided by the CustomControl.OnMeasure method, but multiplies it by two. When the CustomControl instance is added to the StackLayout, the FixSize method can be specified as the GetDesiredSizeDelegate in order to fix the bad measurement provided by the CustomControl class:

stackLayout.Children.Add (customControl, FixSize);

This results in the custom control being displayed correctly, occupying the width of the device, as shown in the following screenshot:

Windows Runtime and Universal Windows Platform

The following code example shows the CustomControl class, which inherits from Panel:

public class CustomControl : Panel
{
  public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register(
      "Text", typeof(string), typeof(CustomControl), new PropertyMetadata(default(string), OnTextPropertyChanged));

  public string Text
  {
    get { return (string)GetValue(TextProperty); }
    set { SetValue(TextProperty, value.ToUpper()); }
  }

  readonly TextBlock textBlock;

  public CustomControl()
  {
    textBlock = new TextBlock
    {
      MinHeight = 0,
      MaxHeight = double.PositiveInfinity,
      MinWidth = 0,
      MaxWidth = double.PositiveInfinity,
      FontSize = 14,
      TextWrapping = TextWrapping.Wrap,
      VerticalAlignment = VerticalAlignment.Center
    };

    Children.Add(textBlock);
  }

  static void OnTextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
  {
    ((CustomControl)dependencyObject).textBlock.Text = (string)args.NewValue;
  }

  protected override Size ArrangeOverride(Size finalSize)
  {
      // This is deliberately wrong in order to demonstrate providing an override to fix it with.
      textBlock.Arrange(new Rect(0, 0, finalSize.Width/2, finalSize.Height));
      return finalSize;
  }

  protected override Size MeasureOverride(Size availableSize)
  {
      textBlock.Measure(availableSize);
      return new Size(textBlock.DesiredSize.Width, textBlock.DesiredSize.Height);
  }
}

An instance of this control is added to a StackLayout, as demonstrated in the following code example:

var brokenControl = new CustomControl {
  Text = "This control has incorrect sizing - it doesn't occupy the available width of the device."
};
stackLayout.Children.Add(brokenControl);

However, because the CustomControl.ArrangeOverride override always returns half of the requested width, the control will be clipped to half the available width of the device, as shown in the following screenshot:

A solution to this problem is to provide an ArrangeOverrideDelegate implementation, when adding the control to the StackLayout, as demonstrated in the following code example:

stackLayout.Children.Add(fixedControl, arrangeOverrideDelegate: (renderer, finalSize) =>
{
    if (finalSize.Width <= 0 || double.IsInfinity(finalSize.Width))
    {
        return null;
    }
    var frameworkElement = renderer.Control;
    frameworkElement.Arrange(new Rect(0, 0, finalSize.Width * 2, finalSize.Height));
    return finalSize;
});

This method uses the width provided by the CustomControl.ArrangeOverride method, but multiplies it by two. This results in the custom control being displayed correctly, occupying the width of the device, as shown in the following screenshot:

Summary

This article demonstrated how to add platform-specific controls to a Xamarin.Forms layout, and how to override the layout of custom controls in order to correct their measurement API usage.

Xamarin Workbook

If it's not already installed, install the Xamarin Workbooks app first. The workbook file should download automatically, but if it doesn't, just click to start the workbook download manually.