Search code examples
c#xamlxamarin.formscustom-renderer

iOS custom render not apply corner radius


I've created a custom render based on some googling that will allow a Frame to have full control over all of its corner radii (left, top, right, bottom). However, even though it is set to be 60, 60, 0, 0 I don't see it respecting those criteria. On Android it works as expected.

iOS Screen:

iOS pin screen

Android:

enter image description here

Whilst debugging I can clearly see that the custom render appropriately gets the data, that is the corner radii are passed down accordingly. Here is the custom renderer code:

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

   UpdateCornerRadius();
   UpdateShadow();
}

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

   if (e.NewElement == null)
       return;

   UpdateShadow();
   UpdateCornerRadius();
}

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

   if (e.PropertyName == nameof(MultiCornerFrame.CornerRadius) || e.PropertyName == nameof(MultiCornerFrame))
   {
       UpdateCornerRadius();
   }

   if(e.PropertyName == nameof(MultiCornerFrame.Elevation))
   {
       UpdateShadow();
   }
}

private void UpdateShadow()
{

   var materialFrame = (MultiCornerFrame) Element;

   // Update shadow to match better material design standards of elevation
   Layer.ShadowRadius = materialFrame.Elevation;
   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;

}

// A very basic way of retrieving same one value for all of the corners
private double RetrieveCommonCornerRadius(CornerRadius cornerRadius)
{
   var commonCornerRadius = cornerRadius.TopLeft;
   if (commonCornerRadius <= 0)
   {
       commonCornerRadius = cornerRadius.TopRight;
       if (commonCornerRadius <= 0)
       {
           commonCornerRadius = cornerRadius.BottomLeft;
           if (commonCornerRadius <= 0)
           {
               commonCornerRadius = cornerRadius.BottomRight;
           }
       }
   }

   return commonCornerRadius;
}

private UIRectCorner RetrieveRoundedCorners(CornerRadius cornerRadius)
{
   var roundedCorners = default(UIRectCorner);

   if (cornerRadius.TopLeft > 0)
   {
       roundedCorners |= UIRectCorner.TopLeft;
   }

   if (cornerRadius.TopRight > 0)
   {
       roundedCorners |= UIRectCorner.TopRight;
   }

   if (cornerRadius.BottomLeft > 0)
   {
       roundedCorners |= UIRectCorner.BottomLeft;
   }

   if (cornerRadius.BottomRight > 0)
   {
       roundedCorners |= UIRectCorner.BottomRight;
   }

   return roundedCorners;
}

private void UpdateCornerRadius()
{
   var cornerRadius = (Element as MultiCornerFrame)?.CornerRadius;
   if (!cornerRadius.HasValue)
   {
       return;
   }

   var roundedCornerRadius = RetrieveCommonCornerRadius(cornerRadius.Value);
   if (roundedCornerRadius <= 0)
   {
       return;
   }

   var roundedCorners = RetrieveRoundedCorners(cornerRadius.Value);

   var path = UIBezierPath.FromRoundedRect(Bounds, roundedCorners, new CGSize(roundedCornerRadius, roundedCornerRadius));
   var mask = new CAShapeLayer { Path = path.CGPath};
   NativeView.Layer.Mask = mask;

}

The XAML where it is called from looks as follows:

<customcontrols:MultiCornerFrame Elevation="48" CornerRadius="60, 60, 0, 0"  Grid.Row="1" BackgroundColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">

Let me know if I need to post more code


Solution

  • After checking that , not updating CornerRadius in OnElementChanged method . I think this changes will be invoked before Frame Renderer created , then this will have no effect .You can have a try do that in LayoutSublayersOfLayer method .

    I tested that by creating a empty CustomFrame :

    public class CustomFrame : Frame{}
    

    In Xaml :

    <StackLayout Padding="20">
        <!-- Place new controls here -->
        <Label Text="Welcome to Xamarin.Forms!" 
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" />
        <appframerenderer:CustomFrame BackgroundColor="Accent"
                                      HorizontalOptions="FillAndExpand"
                                      VerticalOptions="FillAndExpand"/>
    </StackLayout>
    

    Finally , CustomFrameRenderer coding as follow :

    [assembly: ExportRenderer(typeof(CustomFrame), typeof(CustomFrameRenderer))]
    namespace AppFrameRenderer.iOS
    {
        public class CustomFrameRenderer : FrameRenderer
        {
            protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
            {
                base.OnElementChanged(e);
            }
    
            public override void LayoutSublayersOfLayer(CALayer layer)
            {
                base.LayoutSublayersOfLayer(layer);
                var path = UIBezierPath.FromRoundedRect(Bounds, UIRectCorner.TopLeft | UIRectCorner.TopRight, new CGSize(50, 50));
                var maskLayer = new CAShapeLayer { Frame = Bounds, Path = path.CGPath };
                layer.Mask = maskLayer;
            }
        }
    }
    

    The effect as expected :

    enter image description here

    Therefore , here you can move UpdateShadow(); UpdateCornerRadius(); to LayoutSublayersOfLayer method to have a try .