Home > front end >  .NET MAUI: can we also have platform-specific XAML?
.NET MAUI: can we also have platform-specific XAML?

Time:12-18

I read, that we can use platform-specific C# in MAUI projects, but I have the need to use a platform-specific XAML file. Couldn't find anything in the docs.

Background: On Android a SwipeView inside a ListView throws a Java error when removing an item from the bound ObservableCollection ("The specified child already has a parent. You must call removeView() on the child's parent first."), so I use a CollectionView here (no error). On iOS, the CollectionView does extend outside the lower end of the page, when used together with other elements on a page (it seems, on iOS a CollectionView does want to take the full screen height), so I have to use ListView here. On iOS though, the above mentioned error does not occur.

CodePudding user response:

You can achieve this with separate XAML files for each platform. Just write two different versions of your XAML, one for iOS and one for Android. Then, during runtime, instantiate the right one based on the system your application is running on.

Pages

For entire pages, you could just create one version of the page for Android and one for iOS (and other ones for the remaining platforms respectively, if needed).

Android Page

Let's call the Android page HelloFromAndroid.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSamples.Views.Platform.HelloFromAndroid"
             Title="HelloFromAndroid"
             Shell.PresentationMode="Modal">
    <VerticalStackLayout>
        <Label 
            Text="Hello from Android!"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>

iOS Page

The iOS version will be called HelloFromiOS.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSamples.Views.Platform.HelloFromiOS"
             Title="HelloFromiOS"
             Shell.PresentationMode="Modal">
    <VerticalStackLayout>
        <Label 
            Text="Hello from iOS!"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentPage>

Opening the platform-specific pages

You can either decide which version to open during runtime, or you can use multi-targeting/conditional compilation. You do this from your code behind, e.g. the MainPage.xaml.cs:

Runtime decision

private async void OpenHelloView(object sender, EventArgs e)
{
    if (DeviceInfo.Platform == DevicePlatform.Android)
    {
        await Navigation.PushAsync(new HelloFromAndroid());
    }

    if (DeviceInfo.Platform == DevicePlatform.iOS)
    {
        await Navigation.PushAsync(new HelloFromiOS());
    }
}

Conditional compilation

private async void OpenHelloView(object sender, EventArgs e)
{
#if ANDROID
    await Navigation.PushAsync(new HelloFromAndroid());
#elif IOS
    await Navigation.PushAsync(new HelloFromiOS());
#endif
}

This version is the preferred way, because the compiled code only contains the required platform-specific call.

Views

For Views, this is a little more complicated, but also possible.

If you need a platform-specific View, you cannot add it to your page's XAML and instead need to instantiate it dynamically during runtime from the code-behind and add it as a child to another View or a Layout.

Android View

Let's create a View called ViewAndroid:

XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSamples.Views.Platform.ViewAndroid">

  <Label
    Text="Hello from Android!"
    VerticalOptions="Center" 
    HorizontalOptions="Center" />

</ContentView>

iOS View

And let's also create a View called ViewiOS:

XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSamples.Views.Platform.ViewiOS">

  <Label
    Text="Hello from iOS!"
    VerticalOptions="Center" 
    HorizontalOptions="Center" />

</ContentView>

Page with platform-specific View

Now, let's create a Page that consumes the platform-specific Views. First, we create some XAML. For simplicity, I'm using an empty VerticalStackLayout that I give a name using the x:Name property:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSamples.Views.Platform.PageWithPlatformSpecificView"
             Title="PageWithPlatformSpecificView"
             Shell.PresentationMode="Modal">
    <VerticalStackLayout x:Name="VerticalLayout" />
</ContentPage>

Now, I can access the VerticalLayout field from my code-behind and add child elements to it. This can again be done dynamically either via a runtime decision or via conditional compilation.

Runtime decision

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

        if (DeviceInfo.Platform == DevicePlatform.Android)
        {
            VerticalLayout.Add(new ViewAndroid());
        }
        
        if (DeviceInfo.Platform == DevicePlatform.iOS)
        {
            VerticalLayout.Add(new ViewiOS());
        }
    }
}

Conditional compilation

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

#if ANDROID
        VerticalLayout.Add(new ViewAndroid());
#elif IOS
        VerticalLayout.Add(new ViewiOS());
#endif
    }
}

Again, the conditional compilation is the preferred way.

Alternative approaches

Technically, you could also use filename-based multi-targeting to only include the right version of the XAML in the final build.

However, it requires a complex setup in the .csproj file to configure multi-targeting for XAML files, which is not officially supported. I have written an answer on this for a similar problem that deals with platform-specific resource dictionaries here: https://stackoverflow.com/a/74338355/4308455

Summary

As you can see, there are different approaches and possibilities to accomplish this with platform-specific XAML. It's not exactly the same as platform-specific C# code, but it can be useful for certain scenarios. I hope this helps you and anyone else with similar requirements.

I've tried this myself and added the code to my MAUI samples repository, if you're interested.

  • Related