Custom controls

Custom renderers

In Xamarin.Forms, custom renderers define how a control is rendered on the UI, and are specific to a platform (iOS, Android, etc.). For example, the default iOSButtonRenderer will render a Xamarin.Forms.Button as a UIKit.UIButton and will handle all the properties such as Text, Color, etc.

Custom renderers are renderers you create to either change the default behavior of an existing renderer or create a completely new one.

With Fabulous for Xamarin.Forms, you can still use custom renderers like you would in plain Xamarin.Forms.

Please read the custom renderer official documentation to learn more about them.

Here’s an example of a custom renderer for displaying a Label with underlined text, which is not supported by Xamarin.Forms but the native platforms can do it:

Custom controls

In Xamarin.Forms, custom controls are controls that not part of the default controls of Xamarin.Forms. They can either be extended controls (like the UnderlinedLabel above) or brand new ones.

Those custom controls necessarily have custom renderers attached to them, because Xamarin.Forms doesn’t know how to render them by default.

When using with Fabulous, you’ll need to both create the control class and its wrapper for Fabulous.

Here’s an example where we extend the Xamarin.Forms Entry control to add a BorderColor property:

type BorderedEntry() =
    inherit Xamarin.Forms.Entry()

    static let borderColorProperty =
        BindableProperty.Create("BorderColor", typeof<Color>, typeof<BorderedEntry>, Color.Default)

    member this.BorderColor
        with get () =
            this.GetValue(borderColorProperty) :?> Color
        and set (value) =
            this.SetValue(borderColorProperty, value)

Along with this control, we create the wrapper (like defined in the View Extensions documentation) so we can use it in our Fabulous application:

module FabulousBorderedEntry =
    let BorderedEntryBorderColorAttributeKey = AttributeKey<_> "BorderedEntry_BorderColor"

    type Fabulous.XamarinForms.View with
        static member inline BorderedEntry(?borderColor: Color, ?placeholder, ?text, ?textChanged, ?keyboard) =
            let attribCount = match borderColor with None -> 0 | Some _ -> 1
            let attribs =
                                        ?placeholder = placeholder,
                                        ?text = text,
                                        ?textChanged = textChanged,
                                        ?keyboard = keyboard)

            match borderColor with None -> () | Some v -> attribs.Add(BorderedEntryBorderColorAttributeKey, v)

            let update (prevOpt: ViewElement voption) (source: ViewElement) (target: BorderedEntry) =
                ViewBuilders.UpdateEntry(prevOpt, source, target)
                source.UpdatePrimitive(prevOpt, target, BorderedEntryBorderColorAttributeKey, (fun target v -> target.BorderColor <- v))

            let updateAttachedProperties propertyKey prevOpt source target =
                ViewBuilders.UpdateEntryAttachedProperties(propertyKey, prevOpt, source, target)

            ViewElement.Create(BorderedEntry, update, updateAttachedProperties, attribs)

Once this is done, we’ll need a custom renderer per platform for that control to handle the new BorderColor property.

Here’s the example for iOS:

type BorderedEntryRenderer() =
    inherit EntryRenderer()

    member this.BorderedEntry
        with get() =
            this.Element :?> FabulousContacts.Controls.BorderedEntry

    override this.OnElementChanged(e) =

        if (e.NewElement <> null) then
            this.Control.Layer.BorderColor <- this.BorderedEntry.BorderColor.ToCGColor()
            this.Control.Layer.BorderWidth <- nfloat 1.
            this.Control.Layer.CornerRadius <- nfloat 5.

    override this.OnElementPropertyChanged(_, e) =
        if e.PropertyName = "BorderColor" then
            this.Control.Layer.BorderColor <- this.BorderedEntry.BorderColor.ToCGColor()
            this.Control.Layer.BorderWidth <- nfloat 1.
            this.Control.Layer.CornerRadius <- nfloat 5.

/// This dummy module is required by F# to be able to use [<assembly:ExportRendererAttribute>]
/// so Xamarin.Forms can find your custom renderer
module Dummy_BorderedEntryRenderer =
    [<assembly: Xamarin.Forms.ExportRenderer(typeof<FabulousContacts.Controls.BorderedEntry>, typeof<BorderedEntryRenderer>)>]
    do ()F#

Last updated