dsakpal
dsakpal

Reputation: 126

Xamarin Forms Gradient Renderer not working on iOS

I'm trying to use a Gradient Renderer for which I have written a class in PCL and written a renderer for both Android and iOS. Android renderer is working but iOS renderer is not showing the gradient colour at all.

I'm using this Gradient code from XLabs. I'm not sure what's broken. A hint in the right direction would help.

PCL Code:

using Xamarin.Forms;

namespace gradient
{
    public enum GradientOrientation
    {
        Vertical,
        Horizontal
    }

    public class GradientContentView : ContentView
    {
        public GradientOrientation Orientation
        {
            get { return (GradientOrientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        public static readonly BindableProperty OrientationProperty =
            BindableProperty.Create<GradientContentView, GradientOrientation>(x => x.Orientation, GradientOrientation.Vertical, BindingMode.OneWay);

        public Color StartColor
        {
            get { return (Color)GetValue(StartColorProperty); }
            set { SetValue(StartColorProperty, value); }
        }

        public static readonly BindableProperty StartColorProperty =
            BindableProperty.Create<GradientContentView, Color>(x => x.StartColor, Color.White, BindingMode.OneWay);

        public Color EndColor
        {
            get { return (Color)GetValue(EndColorProperty); }
            set { SetValue(EndColorProperty, value); }
        }

        public static readonly BindableProperty EndColorProperty =
            BindableProperty.Create<GradientContentView, Color>(x => x.EndColor, Color.Black, BindingMode.OneWay);
    }
}

iOS Renderer code:

using CoreAnimation;
using CoreGraphics;
using gradient;
using gradient.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(GradientContentView), typeof(GradientContentViewRenderer))]
namespace gradient.iOS
{
    class GradientContentViewRenderer : VisualElementRenderer<ContentView>
    {
        private GradientContentView GradientContentView
        {
            get { return (GradientContentView)Element; }
        }

        protected CAGradientLayer GradientLayer { get; set; }

        protected override void OnElementChanged(ElementChangedEventArgs<ContentView> e)
        {
            base.OnElementChanged(e);

            if (GradientContentView != null &&
                NativeView != null)
            {
                // Create a gradient layer and add it to the 
                // underlying UIView
                GradientLayer = new CAGradientLayer();
                GradientLayer.Frame = NativeView.Bounds;
                GradientLayer.Colors = new CGColor[]
                {
                    GradientContentView.StartColor.ToCGColor(),
                    GradientContentView.EndColor.ToCGColor()
                };

                SetOrientation();

                NativeView.Layer.InsertSublayer(GradientLayer, 0);
            }
        }

        protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (GradientLayer != null && GradientContentView != null)
            {
                // Turn off Animations
                CATransaction.Begin();
                CATransaction.DisableActions = true;

                if (e.PropertyName == GradientContentView.StartColorProperty.PropertyName)
                    GradientLayer.Colors[0] = GradientContentView.StartColor.ToCGColor();

                if (e.PropertyName == GradientContentView.EndColorProperty.PropertyName)
                    GradientLayer.Colors[1] = GradientContentView.EndColor.ToCGColor();

                if (e.PropertyName == VisualElement.WidthProperty.PropertyName ||
                    e.PropertyName == VisualElement.HeightProperty.PropertyName)
                    GradientLayer.Frame = NativeView.Bounds;

                if (e.PropertyName == GradientContentView.OrientationProperty.PropertyName)
                    SetOrientation();

                CATransaction.Commit();
            }
        }
        void SetOrientation()
        {
            if (GradientContentView.Orientation == GradientOrientation.Horizontal)
            {
                GradientLayer.StartPoint = new CGPoint(0, 0.5);
                GradientLayer.EndPoint = new CGPoint(1, 0.5);
            }
            else
            {
                GradientLayer.StartPoint = new CGPoint(0.5, 0);
                GradientLayer.EndPoint = new CGPoint(0.5, 1);
            }
        }

    }
}

Upvotes: 2

Views: 704

Answers (2)

Evan Mulawski
Evan Mulawski

Reputation: 55334

You must update the layer's size in VisualElementRenderer.LayoutSubviews:

public override void LayoutSubviews()
{
    base.LayoutSubviews();

    CATransaction.Begin();
    CATransaction.DisableActions = true;

    GradientLayer.Frame = NativeView.Bounds;

    CATransaction.Commit();
}

Upvotes: 0

Markus Michel
Markus Michel

Reputation: 2299

This is my code for rendering a gradient background, i am not using orientation, but maybe it helps.

protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);

        if (e.OldElement == null) // perform initial setup
        {
            ModernOrderCalendar page = e.NewElement as ModernOrderCalendar;
            var gradientLayer = new CAGradientLayer();
            gradientLayer.Name = "gradient";
            CGRect rect = View.Bounds;          
            gradientLayer.Frame = rect;
            gradientLayer.Colors = new CGColor[] { page.StartColor.ToCGColor(), page.EndColor.ToCGColor() };
            View.Layer.InsertSublayer(gradientLayer, 0);
        }
    }

    public override void ViewWillLayoutSubviews()
    {
        base.ViewWillLayoutSubviews();
        if (Xamarin.Forms.Device.Idiom == TargetIdiom.Tablet)
        {
            var gradientLayer = View.Layer.Sublayers.FirstOrDefault(l => l.Name == "gradient");
            gradientLayer.Frame = View.Bounds;
            View.Layer.Sublayers[0] = gradientLayer;
            CGRect frame = View.Bounds;
            View.Bounds = frame;
        }
    }

The main difference I see is that you don't seem to be overriding the ViewWillLayoutSubviews method. I had the same issue, which caused the gradient layer to be created with no height and width during the creation of the window (at that point the View has not layouted, yet).

Therefore I adapt the gradientlayer width and height when layouting the subviews, because at that point width and height of the view are definitely known.

Upvotes: 1

Related Questions