3

To customize my app's appearance, I've implemented a custom renderer for buttons, setting a gradient background image on them. So far, that's working fine.

However, when I set a corner radius, the edges appear very blurry. I thought it might be due to the difference between screen coordinates and actual resolution, but when I apply a factor (2 or 3) to accommodate for that it just completely screws up the shape (while still being blurred).

Here's a screenshot, taken on an iPhone X (note the resolution of entry and images):

enter image description here

Here's the code generating the background:

private UIImage CreateGradientBackground(Color startColor, Color endColor)
{
    var gradientLayer = new CAGradientLayer();
    if (Control == null)
        return null;
    gradientLayer.Bounds = Control.Bounds;
    gradientLayer.CornerRadius = (Control.Bounds.Width < Control.Bounds.Height) ? 
        Control.Bounds.Width / 2 : 
        Control.Bounds.Height / 2;
    gradientLayer.Colors = new CGColor[] { startColor.ToCGColor(), endColor.ToCGColor() };
    gradientLayer.StartPoint = new CGPoint(0, 0.5);
    gradientLayer.EndPoint = new CGPoint(1, 0.5);

    UIGraphics.BeginImageContext(gradientLayer.Bounds.Size);
    if (UIGraphics.GetCurrentContext() == null)
        return null;
    gradientLayer.RenderInContext(UIGraphics.GetCurrentContext());
    UIImage image = UIGraphics.GetImageFromCurrentImageContext();
    UIGraphics.EndImageContext();

    return image;
}

This is how it's applied (for all the various states):

Control.SetBackgroundImage(gradientBackground, UIControlState.Normal);

Does anyone have an idea how to fix this?

Thanks a lot!


SOLUTION (thanks to NickSpag!)

1: set the appropriate contents scale on the gradient layer:

gradientLayer.ContentsScale = UIScreen.MainScreen.Scale;

2: get the correct image context:

UIGraphics.BeginImageContextWithOptions(gradientLayer.Bounds.Size, false, UIScreen.MainScreen.Scale);
japhwil
  • 299
  • 4
  • 16

1 Answers1

2

I believe it's because your gradientLayer's ContentsScale is defaulting to 1.0, as is the 'UIGraphics' image context's scale. As Ken Thomases eloquently says in an answer that goes in to CALayer drawing behavior more in depth: "There's an implicit transform in the context that converts from user space (points, more or less) to device space (pixels)."

With CALayer's you exclusively manage, you have to also manage that transformation if its anything besides the default (1.0), per Apple's documentation. Currently, your gradient is being calculated in points, but displayed on the iPhone X's pixels, in a 1:1 fashion, which of curse looks blurry on that fancy screen.

After you finish setting gradientLayer.EndPoint I would add:

gradientLayer.ContentsScale = UIScreen.MainScreen.Scale;

And specify the scale for the UIGraphics image context like so:

UIGraphics.BeginImageContextWithOptions(gradientLayer.Bounds.Size, false, UIScreen.MainScreen.Scale);

For Xamarin.Mac or macOS posterity, its the below. Don't believe you need to specify a context other than current:

gradientLayer.ContentsScale = NSScreen.MainScreen.BackingScaleFactor;
NickSpag
  • 581
  • 4
  • 11
  • Thanks for the answer! You were right about the ContentsScale being 1 by default, but setting it to the proper value (3 in this case) didn't fix the blurriness. I tried setting it after gradientLayer.EndPoint as you suggested and right after initializing the layer. – japhwil Jan 26 '18 at 23:41
  • 1
    Ah! Its probably not transferring to the `UIGraphics` image context? Lets try changing it to `'UIGraphics.BeginImageContextWithOptions(gradientLayer.Bounds.Size, false, UIScreen.MainScreen.Scale);'` ? – NickSpag Jan 27 '18 at 19:02
  • That did it! Sorry it took me so long to answer, I didn't have access to my Mac for the past few days. – japhwil Jan 31 '18 at 21:28
  • No problem, glad it worked! Thanks for marking it as the answer, I'll update the answer to include that portion. – NickSpag Feb 01 '18 at 19:25