Xamarin.Forms setting Image Source in shared project

279 views Asked by At

I have a Xamarin.Forms solution with Android and iOS projects.
I want the same images to be in both applications.
I know I can add the same image to both projects code seperately, but that doesn't sound very elegant.
I want to have only one image referred to by both projects, from the code in the shared project.

My images are in the shared project, and are Embedded Resource

SharedProject
|
- Resources
  |
  - icon192.png

My XAML (shared project)

<ContentPage xmlns:myExtensions="clr-namespace:LibraryApp.Extensions;assembly=App">

    <Image x:Name="image2"
       VerticalOptions="Center" HeightRequest="50" Aspect="AspectFit"  />

    <Image Source="{myExtensions:ImageResourceExtension Resources.icon192.png}"
       VerticalOptions="Center" HeightRequest="50" Aspect="AspectFit"  />

</ContentPage>

My code-behind (shared project)

public partial class AboutPage : ContentPage
{
    public AboutPage()
    {
        InitializeComponent();

        image2.Source = ImageSource.FromResource("LibraryApp.AndroidClient.Resources.icon192.png");
    }
}

The first Image, image2 works perfectly when set from code-behind.
But I don't want to have to deal with images in code-behind, because they are a presentation detail, not implementation.

So my extension method to try and solve this

namespace LibraryApp.Extensions
{
    [Preserve(AllMembers = true)]
    [ContentProperty(nameof(Source))]
    public class ImageResourceExtension : IMarkupExtension
    {
        public string Source { get; set; }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Source == null) return null;

            // do some work to get the name of the correct assembly/project, here
            // var stream = Assembly.GetCallingAssembly().GetManifestResourceStream(Source);

            // just for testing, return something we know that works
            return ImageSource.FromResource("LibraryApp.AndroidClient.Resources.icon192.png");
        }
    }
}

This extension method does get called from the other image (with the correct Source parameter).
Even though the code is effectively exactly the same, I cannot get an image using the extension method.

I get a FileImageSourceHandler: Could not find image or image file was invalid.

How would you solve this problem?

1

There are 1 answers

0
DefenestrationDay On

Typical, after spending literally a day on this, I figure it out 20m after asking the question...

One solution is to add an IValueConverter

public class ResourceConverter : IValueConverter
{

    public Object Convert(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is not String filename) throw new ArgumentNullException(nameof(value));

        var nameOfAssembly = Assembly.GetExecutingAssembly().GetName().Name;
        return ImageSource.FromResource($"{nameOfAssembly}.{filename}");
    }

    public Object ConvertBack(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}

And then in the XAML

<ContentPage xmlns:converter="clr-namespace:GPS4Flight.Converter">
    <ContentPage.Resources>
        <converter:ResourceConverter x:Key="ResourceConverter" />
    </ContentPage.Resources>

    <Image Source="{Binding Source=Resources.icon192.png, Converter={StaticResource ResourceConverter}}"
           VerticalOptions="Center" HeightRequest="50" Aspect="AspectFit"  />
</ContentPage>

Notice I needed {Binding Source=[nameOfFile] for it to work.