Platform-Specifics
Platform-specifics allow you to consume functionality that's only available on a specific platform, without implementing custom renderers or effects.
The process for consuming a platform-specific through XAML, or through the fluent code API is as follows:
- Add a
xmlns
declaration orusing
directive for theXamarin.Forms.PlatformConfiguration
namespace. - Add a
xmlns
declaration orusing
directive for the namespace that contains the platform-specific functionality:- On iOS, this is the
Xamarin.Forms.PlatformConfiguration.iOSSpecific
namespace. - On Android, this is the
Xamarin.Forms.PlatformConfiguration.AndroidSpecific
namespace. For Android AppCompat, this is theXamarin.Forms.PlatformConfiguration.AndroidSpecific.AppCompat
namespace. - On the Universal Windows Platform, this is the
Xamarin.Forms.PlatformConfiguration.WindowsSpecific
namespace.
- On iOS, this is the
- Apply the platform-specific from XAML, or from code with the
On<T>
fluent API. The value ofT
can be theiOS
,Android
, orWindows
types from theXamarin.Forms.PlatformConfiguration
namespace.
Note
Note that attempting to consume a platform-specific on a platform where it is unavailable will not result in an error. Instead, the code will execute without the platform-specific being applied.
Platform-specifics consumed through the On<T>
fluent code API return IPlatformElementConfiguration
objects. This allows multiple platform-specifics to be invoked on the same object with method cascading.
For more information about the platform-specifics provided by Xamarin.Forms, see iOS Platform-Specifics, Android Platform-Specifics, and Windows Platform-Specifics.
Creating platform-specifics
Vendors can create their own platform-specifics with Effects. An Effect provides the specific functionality, which is then exposed through a platform-specific. The result is an Effect that can be more easily consumed through XAML, and through a fluent code API.
The process for creating a platform-specific is as follows:
- Implement the specific functionality as an Effect. For more information, see Creating an Effect.
- Create a platform-specific class that will expose the Effect. For more information, see Creating a Platform-Specific Class.
- In the platform-specific class, implement an attached property to allow the platform-specific to be consumed through XAML. For more information, see Adding an Attached Property.
- In the platform-specific class, implement extension methods to allow the platform-specific to be consumed through a fluent code API. For more information, see Adding Extension Methods.
- Modify the Effect implementation so that the Effect is only applied if the platform-specific has been invoked on the same platform as the Effect. For more information, see Creating the Effect.
The result of exposing an Effect as a platform-specific is that the Effect can be more easily consumed through XAML and through a fluent code API.
Note
It's envisaged that vendors will use this technique to create their own platform-specifics, for ease of consumption by users. While users may choose to create their own platform-specifics, it should be noted that it requires more code than creating and consuming an Effect.
The sample application demonstrates a Shadow
platform-specific that adds a shadow to the text displayed by a Label
control:
The sample application implements the Shadow
platform-specific on each platform, for ease of understanding. However, aside from each platform-specific Effect implementation, the implementation of the Shadow class is largely identical for each platform. Therefore, this guide focusses on the implementation of the Shadow class and associated Effect on a single platform.
For more information about Effects, see Customizing Controls with Effects.
Creating a platform-specific class
A platform-specific is created as a public static
class:
namespace MyCompany.Forms.PlatformConfiguration.iOS
{
public static Shadow
{
...
}
}
The following sections discuss the implementation of the Shadow
platform-specific and associated Effect.
Adding an attached property
An attached property must be added to the Shadow
platform-specific to allow consumption through XAML:
namespace MyCompany.Forms.PlatformConfiguration.iOS
{
using System.Linq;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using FormsElement = Xamarin.Forms.Label;
public static class Shadow
{
const string EffectName = "MyCompany.LabelShadowEffect";
public static readonly BindableProperty IsShadowedProperty =
BindableProperty.CreateAttached("IsShadowed",
typeof(bool),
typeof(Shadow),
false,
propertyChanged: OnIsShadowedPropertyChanged);
public static bool GetIsShadowed(BindableObject element)
{
return (bool)element.GetValue(IsShadowedProperty);
}
public static void SetIsShadowed(BindableObject element, bool value)
{
element.SetValue(IsShadowedProperty, value);
}
...
static void OnIsShadowedPropertyChanged(BindableObject element, object oldValue, object newValue)
{
if ((bool)newValue)
{
AttachEffect(element as FormsElement);
}
else
{
DetachEffect(element as FormsElement);
}
}
static void AttachEffect(FormsElement element)
{
IElementController controller = element;
if (controller == null || controller.EffectIsAttached(EffectName))
{
return;
}
element.Effects.Add(Effect.Resolve(EffectName));
}
static void DetachEffect(FormsElement element)
{
IElementController controller = element;
if (controller == null || !controller.EffectIsAttached(EffectName))
{
return;
}
var toRemove = element.Effects.FirstOrDefault(e => e.ResolveId == Effect.Resolve(EffectName).ResolveId);
if (toRemove != null)
{
element.Effects.Remove(toRemove);
}
}
}
}
The IsShadowed
attached property is used to add the MyCompany.LabelShadowEffect
Effect to, and remove it from, the control that the Shadow
class is attached to. This attached property registers the OnIsShadowedPropertyChanged
method that will be executed when the value of the property changes. In turn, this method calls the AttachEffect
or DetachEffect
method to add or remove the effect based on the value of the IsShadowed
attached property. The Effect is added to or removed from the control by modifying the control's Effects
collection.
Note
Note that the Effect is resolved by specifying a value that's a concatenation of the resolution group name and unique identifier that's specified on the Effect implementation. For more information, see Creating an Effect.
For more information about attached properties, see Attached Properties.
Adding Extension Methods
Extension methods must be added to the Shadow
platform-specific to allow consumption through a fluent code API:
namespace MyCompany.Forms.PlatformConfiguration.iOS
{
using System.Linq;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using FormsElement = Xamarin.Forms.Label;
public static class Shadow
{
...
public static bool IsShadowed(this IPlatformElementConfiguration<iOS, FormsElement> config)
{
return GetIsShadowed(config.Element);
}
public static IPlatformElementConfiguration<iOS, FormsElement> SetIsShadowed(this IPlatformElementConfiguration<iOS, FormsElement> config, bool value)
{
SetIsShadowed(config.Element, value);
return config;
}
...
}
}
The IsShadowed
and SetIsShadowed
extension methods invoke the get and set accessors for the IsShadowed
attached property, respectively. Each extension method operates on the IPlatformElementConfiguration<iOS, FormsElement>
type, which specifies that the platform-specific can be invoked on Label
instances from iOS.
Creating the effect
The Shadow
platform-specific adds the MyCompany.LabelShadowEffect
to a Label
, and removes it. The following code example shows the LabelShadowEffect
implementation for the iOS project:
[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace ShadowPlatformSpecific.iOS
{
public class LabelShadowEffect : PlatformEffect
{
protected override void OnAttached()
{
UpdateShadow();
}
protected override void OnDetached()
{
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
if (args.PropertyName == Shadow.IsShadowedProperty.PropertyName)
{
UpdateShadow();
}
}
void UpdateShadow()
{
try
{
if (((Label)Element).OnThisPlatform().IsShadowed())
{
Control.Layer.CornerRadius = 5;
Control.Layer.ShadowColor = UIColor.Black.CGColor;
Control.Layer.ShadowOffset = new CGSize(5, 5);
Control.Layer.ShadowOpacity = 1.0f;
}
else if (!((Label)Element).OnThisPlatform().IsShadowed())
{
Control.Layer.ShadowOpacity = 0;
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}
}
}
The UpdateShadow
method sets Control.Layer
properties to create the shadow, provided that the IsShadowed
attached property is set to true
, and provided that the Shadow
platform-specific has been invoked on the same platform that the Effect is implemented for. This check is performed with the OnThisPlatform
method.
If the Shadow.IsShadowed
attached property value changes at runtime, the Effect needs to respond by removing the shadow. Therefore, an overridden version of the OnElementPropertyChanged
method is used to respond to the bindable property change by calling the UpdateShadow
method.
For more information about creating an effect, see Creating an Effect and Passing Effect Parameters as Attached Properties.
Consuming the platform-specific
The Shadow
platform-specific is consumed in XAML by setting the Shadow.IsShadowed
attached property to a boolean
value:
<ContentPage xmlns:ios="clr-namespace:MyCompany.Forms.PlatformConfiguration.iOS" ...>
...
<Label Text="Label Shadow Effect" ios:Shadow.IsShadowed="true" ... />
...
</ContentPage>
Alternatively, it can be consumed from C# using the fluent API:
using Xamarin.Forms.PlatformConfiguration;
using MyCompany.Forms.PlatformConfiguration.iOS;
...
shadowLabel.On<iOS>().SetIsShadowed(true);