In Xamarin.Forms, an Effect
can be attached to a View
. In my case, the View is displaying an Image
. And the effect is making a colored "glow" around the visible pixels of the image. XAML:
<Image Source="{Binding LogoImage}" ...>
<Image.Effects>
<effects:GlowEffect Radius="5" Color="White" />
</Image.Effects>
</Image>
The effect is implemented as a subclass of RoutingEffect
:
public class GlowEffect : Xamarin.Forms.RoutingEffect
{
public GlowEffect() : base("Core.ImageGlowEffect")
{
}
...
}
On each platform, there is a PlatformEffect
to implement the effect. For iOS:
using ...
[assembly: ExportEffect(typeof(Core.Effects.ImageGlowEffect), "ImageGlowEffect")]
namespace Core.Effects
{
public class ImageGlowEffect : PlatformEffect
{
protected override void OnAttached()
{
ApplyGlow();
}
protected override void OnElementPropertyChanged( PropertyChangedEventArgs e )
{
base.OnElementPropertyChanged( e );
if (e.PropertyName == "Source") {
ApplyGlow();
}
}
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
var effect = (GlowEffect)Element.Effects.FirstOrDefault(e => e is GlowEffect);
if (effect != null) {
CGRect outSize = AVFoundation.AVUtilities.WithAspectRatio( new CGRect( new CGPoint(), imageView.Image.Size ), imageView.Frame.Size );
...
}
}
...
}
}
The above code works if Source
is changed dynamically: at the time Source
changes, the UIImageView
(Bounds
or Frame
) has a size. BUT if the Source is set statically in XAML, that logic does not run: so the only call to ApplyGlow
is during OnAttach
. UNFORTUNATELY, during OnAttach
, the UIImageView
has size (0, 0)
.
How get this effect to work on iOS, with a static Source
?
NOTE: The equivalent Android effect works via a handler attached to ImageView.ViewTreeObserver.PreDraw
- at which time the size is known. So if there is an iOS equivalent event, that would be one way to solve.
More Details:
The original implementation used the original image size (imageView.Image.Size) - which is available during OnAttach
. This can be made to "work", but is not satisfactory: the glow is applied to the full size image. If the image is significantly larger than the view area, the glow becomes much too small a radius (iOS shrinks the image+glow as it renders): it does not have the desired appearance.
ApplyGlow
has an option to apply a tint color to the image. That tint color is different than the glow color. I mention this because it restricts the possible solutions: AFAIK, can't just set options on an image and let iOS figure out how to render it - need to explicitly resize the image and draw the resized tinted image on top of a blurred version (the glow). This code all works - if imageView.Bounds.Size
(or imageView.Frame.Size
) is available (and non-zero).
With a breakpoint in OnElementPropertyChanged
, I've checked to see if imageView size is known for any property that is always set. No; if no properties are dynamically set, the property changes all occur before imageView has a size.
Maybe it's a workaround and I don't know if it is acceptable to you.
Add a little delay before calling ApplyGlow();
in OnAttached
. After the delay, you will get the imageView.Frame.Size
or imageView.Bounds.Size
.
protected override async void OnAttached()
{
await Task.Delay(TimeSpan.FromSeconds(0.3));// it can be 0.2s,0.1s, depending on you
ApplyGlow();
}
And if you have set WidthRequest
, HeightRequest
, you can get there without the delay:
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
Console.WriteLine(imageView.Image.Size.Width);
Console.WriteLine(imageView.Image.Size.Height);
CoreGraphics.CGSize rect = imageView.Bounds.Size;
CoreGraphics.CGSize rect2 = imageView.Frame.Size;
Console.WriteLine(rect);
Console.WriteLine(rect2);
double width = (double)Element.GetValue(VisualElement.WidthRequestProperty);
double height = (double)Element.GetValue(VisualElement.HeightRequestProperty);
Console.WriteLine(width);
Console.WriteLine(height);
double width2 = (double)Element.GetValue(VisualElement.WidthProperty);
double height2 = (double)Element.GetValue(VisualElement.HeightProperty);
Console.WriteLine(width2);
Console.WriteLine(height2);
}