Search code examples
xamlmaui

.NET MAUI: can we also have platform-specific XAML?


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.


Solution

  • 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

    XAML-only using <OnPlatform>

    You can also do this in XAML, but beware that this approach will include all views of all platforms in the app bundle, e.g.:

    <ContentView>
        <OnPlatform x:TypeArguments="View">
            <On Platform="Android">
                <android:ViewAndroid />
            </On>
            <On Platform="iOS">
                <ios:ViewiOS />
            </On>
        </OnPlatform>
    </ContentView>
    

    The same works with <OnIdiom> and other markup.

    Multi-targeting

    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.