Xamarin.Tips – Making Your iOS Frame Shadows More Material

If you’ve used the Xamarin.Forms Frame element on iOS and have HasShadow set to true, then you might notice that on iOS, the shadow is quite abrasive and overwhelming.

We can update just the iOS FrameRenderer to create some better shadows, so we can go from this:

DefaultiOSFrameShadow

to this:

iOSMaterialShadow

In order to override all Frame elements, we will need to create our custom renderer and also set it to export to the default Frame. If you do not want it to apply to all Frames, then you can create a custom control that inherits from frame and then apply the renderer to that new element type. We’ll example both here:

MaterialFrameRenderer.cs


[assembly: ExportRenderer(typeof(Frame), typeof(MaterialFrameRenderer))]
namespace YOU_IOS_NAMESPACE
{
    /// <summary>
    /// Renderer to update all frames with better shadows matching material design standards
    /// </summary>

    public class MaterialFrameRenderer : FrameRenderer
    {
        public override void Draw(CGRect rect)
        {
            base.Draw(rect);

            // Update shadow to match better material design standards of elevation
            Layer.ShadowRadius = 2.0f;
            Layer.ShadowColor = UIColor.Gray.CGColor;
            Layer.ShadowOffset = new CGSize(2, 2);
            Layer.ShadowOpacity = 0.80f;
            Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
            Layer.MasksToBounds = false;
        }
    }
}

That’s all you need in your iOS project in order to apply it everywhere. Now, if you want to apply it to a new custom Element, we can create it and apply it like so:

MaterialFrame.xaml.cs


namespace YOUR_PCL_NAMESPACE
{
    public class MaterialFrame : Frame
    {
        // no other code needs to go here unless you want more customizable properties.
    }
}

Then create your renderer and export it for you new Element:

MaterialFrameRenderer.cs


[assembly: ExportRenderer(typeof(MaterialFrame), typeof(MaterialFrameRenderer))]
namespace YOU_IOS_NAMESPACE
{
    /// <summary>
    /// Renderer to update all frames with better shadows matching material design standards
    /// </summary>

    public class MaterialFrameRenderer : FrameRenderer
    {
        public override void Draw(CGRect rect)
        {
            base.Draw(rect);

            // Update shadow to match better material design standards of elevation
            Layer.ShadowRadius = 2.0f;
            Layer.ShadowColor = UIColor.Gray.CGColor;
            Layer.ShadowOffset = new CGSize(2, 2);
            Layer.ShadowOpacity = 0.80f;
            Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
            Layer.MasksToBounds = false;
        }
    }
}

And now your frames can look much nicer~

iOSMaterialShadow

 

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

17 thoughts on “Xamarin.Tips – Making Your iOS Frame Shadows More Material”

  1. Awesome post! One thing I would like to mention is that this can cause the background color to bleed over the frame’s rounded corners.

    To fix this I created a new Xamarin Forms Control derived from Frame and added an InnerBackground property. Assign your desired background color to the new InnerBackground property and set the Frame’s actual BackgroundColor to transparent. Then in the custom renderer, assign Frame.InnerBackground to Layer.BackgroundColor.

    Like

    1. For Frames that have rounded corners I changed the implementation a bit. First of all I did not override Draw method but the OnElementChanged. And I’m not sure what exactly the ShadowPath line does but I removed it. My code looks like this:
      [assembly: ExportRenderer(typeof(MaterialFrame), typeof(MaterialFrameRenderer))]
      namespace MY_IOS_NAMESPACE
      {
      public class MaterialFrameRenderer : FrameRenderer
      {
      protected override void OnElementChanged(ElementChangedEventArgs e)
      {
      base.OnElementChanged(e);
      // Update shadow to match better material design standards of elevation
      Layer.ShadowRadius = 2.0f;
      Layer.ShadowColor = UIColor.LightGray.CGColor;
      Layer.ShadowOffset = new CGSize(2, 2);
      Layer.ShadowOpacity = 0.80f;
      Layer.MasksToBounds = false;
      }
      }
      }
      And that’s it, your frame with rounded corners will have a nice rounded shadow as well.

      Like

      1. I know what ShadowPath does. it caused to me a lot of headache. Cutting it off resolved my problems.

        Like

  2. Hi,

    Thanks for the solution! I’ve been successfully implemented this in my source code. Unfortunately, this code has one little problem… If I use this on a continuous-loading list view the shadow will get thicker the further I scroll down the list. I suspect the renderer redraws every shadow on all of the CardView whenever the list view loads new item. Can you help me with this issue?

    Regards,
    Sangadji

    Like

  3. Very well. Should check HasShadow attribute:

    if (Element.HasShadow)
    {
    ……

    Or when overriding OnElementChanged:

    if (e.NewElement != null && e.NewElement.HasShadow)

    Like

  4. Amazing solution, thank you very much.
    If someone had problem like me, that shadow was not rendered again when frame view changed size, then just move that code to function LayoutSubviews.
    IE.

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

    Layer.ShadowRadius = 2.0f;
    Layer.ShadowColor = UIColor.Gray.CGColor;
    Layer.ShadowOffset = new CGSize(2, 2);
    Layer.ShadowOpacity = 0.80f;
    Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
    Layer.MasksToBounds = false;
    }

    Like

  5. If you find your shadows are still thick then add this before setting your shadow settings.
    foreach(var v in Subviews) {
    v.Layer.ShadowOpacity = 0;
    }

    Like

Leave a comment