Recently on the blog, Pierce introduced Platform-Specifics, which allow you to consume functionality that’s only available on a specific platform without having to implementing custom renderers or effects. With the use of a simple Fluent API, you can make platform-specific tweaks from shared code in Xamarin.Forms.
At the time of writing, Xamarin.Forms includes the following Platform-Specifics:
- Blur support for any
VisualElement
on iOS. - A translucent navigation bar on iOS.
- The ability to set the operating mode for a soft keyboard input area on Android.
- Toolbar placement options on Windows.
- A partially collapsible
MasterDetailPage
navigation bar on Windows.
In addition, vendors can create their own Platform-Specifics with Effects. An Effect provides the desired functionality, which is then exposed through a Platform-Specific. The result is an Effect that can more easily be consumed through XAML and a fluent code API.
In this blog post, I’m going to demonstrate how to expose an Effect through a Platform-Specific that adds a shadow to the text displayed by a Label
. All the code can be found in a sample application. However, the blog post will focus on the implementation for the iOS platform.
Creating a Platform-Specific
The process for creating a Platform-Specific is:
- Implement the desired functionality as an Effect.
- Create a Platform-Specific class that will expose the Effect.
- Add an attached property to the Platform-Specific class to allow the Platform-Specific to be consumed through XAML.
- Add extension methods in the Platform-Specific class to allow it to be consumed through a fluent code API.
- 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.
Each item will now be considered in turn.
Implement the Desired Functionality as an Effect
In the sample application, the iOS project provides the LabelShadowEffect
class, which adds a shadow to a Label
by setting the following properties:
Control.Layer.CornerRadius = 5; Control.Layer.ShadowColor = UIColor.Black.CGColor; Control.Layer.ShadowOffset = new CGSize(5, 5); Control.Layer.ShadowOpacity = 1.0f;
For more information about creating this Effect, see Creating an Effect and Passing Effect Parameters as Attached Properties.
Create a Platform-Specific Class
A Platform-Specific is created as a public static
class:
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"; ... 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 AttachEffect
and DetachEffect
methods will be used to add or remove the Effect, and will be invoked by additional code that’s added to the Shadow
class.
Add an Attached Property
An attached property must be added to the Platform-Specific class to allow consumption through XAML:
public static class Shadow { ... 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); } } ... }
The IsShadowed
attached property is used to add the effect to, and remove it from, the control that the Shadow
class is attached to. The 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.
Add Extension Methods
Extension methods must be added to the Platform-Specific to allow consumption through a fluent code API:
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 only be invoked on Label
instances from iOS.
Modify the Effect Implementation
The Effect implementation must be modified so that it can only be invoked from the same platform the Platform-Specific is implemented for:
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; }
A shadow is added to the Label
text 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.
Consuming the Platform-Specific
The Shadow
Platform-Specific can be 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, the Shadow
platform-specific can be consumed through the fluent code API:
using Xamarin.Forms.PlatformConfiguration; using MyCompany.Forms.PlatformConfiguration.iOS; ... shadowLabel.On<iOS>().SetIsShadowed(true);
This results in a shadow being applied to the text displayed by a Label
:
Wrapping Up
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.
For more information about consuming Platform-Specifics, see Consuming Platform-Specifics. For more information about creating Platform-Specifics, see Creating Platform-Specifics.
0 comments