Native Views in C#
Native views from iOS, Android, and UWP can be directly referenced from Xamarin.Forms pages created using C#. This article demonstrates how to add native views to a Xamarin.Forms layout created using C#, and how to override the layout of custom views to correct their measurement API usage.
Overview
Any Xamarin.Forms control that allows Content
to be set, or that has a Children
collection, can add platform-specific views. 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 .NET Standard library solutions.
The following screenshots demonstrate platform-specific views having been added to a Xamarin.Forms StackLayout
:
The ability to add platform-specific views to a Xamarin.Forms layout is enabled by two extension methods on each platform:
Add
– adds a platform-specific view to theChildren
collection of a layout.ToView
– takes a platform-specific view and wraps it as a Xamarin.FormsView
that can be set as theContent
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
- Universal Windows Platform (UWP) – Xamarin.Forms.Platform.UWP
Adding Platform-Specific Views on Each Platform
The following sections demonstrate how to add platform-specific views 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 (MainActivity.Instance) { 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#.
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 Views
Custom views on each platform often only correctly implement measurement for the layout scenario for which they were designed. For example, a custom view may have been designed to only occupy half of the available width of the device. However, after being shared with other users, the custom view may be required to occupy the full available width of the device. Therefore, it can be necessary to override a custom views 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 view layout when it's added to a Xamarin.Forms layout.
The following sections demonstrate how to override the layout of custom views, 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 view 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 view 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
to fix the bad measurement provided by the CustomControl
class:
stackLayout.Children.Add (customControl, FixSize);
This results in the custom view 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 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 view is added to a StackLayout
, as demonstrated in the following code example:
var customControl = new CustomControl (MainActivity.Instance) {
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 view 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
to fix the bad measurement provided by the CustomControl
class:
stackLayout.Children.Add (customControl, FixSize);
This results in the custom view being displayed correctly, occupying the width of the device, as shown in the following screenshot:
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 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 view 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 view 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 view 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 view being displayed correctly, occupying the width of the device, as shown in the following screenshot:
Summary
This article explained how to add native views to a Xamarin.Forms layout created using C#, and how to override the layout of custom views to correct their measurement API usage.