Search code examples
xamllayoutmauicontent-pagescontentview

In .NET MAUI is there a way to choose a different XAML view based upon whether the device is in Landscape or Portrait


I am using .NET MAUI and I have a particular view that is rather complicated and I would rather have a different layout if the device orientation is in Portrait vs if the device orientation is in landscape.

I tinkered around with Android programming a long time ago and for Android Studio there was a way to choose a XAML file when the device was in landscape and a different XAML file when the device was in portrait.

Is this possible with MAUI? If not what is the best practice in regards to this?

Here is my layout and in landscape mode I can fit 3 major sections in one row but this won't work in portrait and in portrait I would like the middle major element to be on the next row.

Here are examples of my portrait vs landscape mockup I created on Photoshop:

enter image description here

UPDATE WITH SOLUTION*************** I'm attempting the solution that FreakyAli posted and have a mostly working prototype, so anyone who is wanting to use a different XAML layout based upon the screen orientation can use this approach.

I created a new folder called "ContentViews" in my solution. I added 3 new ContentViews (the XAML with the code behind):

  1. HomePageLandscape
  2. HomePagePortrait
  3. HomePageOrientationViewLoader

The HomePageOrientationViewLoader will get loaded directly into the HomePage.xaml file later on. This is the control that will load either the HomePagePortrait ContentView when in portrait mode or HomePageLandscape ContentView when in landscape mode.

namespace ScoreKeepersBoard.ContentViews;

public partial class HomePageOrientationViewLoader : ContentView
{

    public ContentView homePagePortraitContentView;
    public ContentView homePageLandscapeContentView;

    public HomePageOrientationViewLoader()
    {
        InitializeComponent();

        homePagePortraitContentView = new HomePagePortrait();
        homePageLandscapeContentView = new HomePageLandscape();
        this.Content = homePageLandscapeContentView;

        DeviceDisplay.Current.MainDisplayInfoChanged += Current_MainDisplayInfoChanged;
        this.Content = DeviceDisplay.Current.MainDisplayInfo.Orientation == DisplayOrientation.Portrait ? homePagePortraitContentView : homePageLandscapeContentView;

    }

    private void Current_MainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
    {

        if (e.DisplayInfo.Orientation == DisplayOrientation.Landscape)
        {
           // if (this.Content.GetType() is not typeof(HomePageLandscape))
           // {
                this.Content = homePageLandscapeContentView;
           // }
        }
        else if (e.DisplayInfo.Orientation == DisplayOrientation.Portrait)
        {
            // if (this.Content.GetType() is not typeof(HomePagePortrait))
            // {
            this.Content = homePagePortraitContentView;
            // }
        }
        else
        {
            //Whatever you would like to do if the orientation is unknown.
        }

    }
}

The HomePageOrientationViewLoader.xaml file:

<?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="ScoreKeepersBoard.ContentViews.HomePageOrientationViewLoader">
    <VerticalStackLayout>
        <Label 
            Text="Welcome to .NET MAUI!"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentView>

Here is the HomePagePortrait.xaml.cs file:

namespace ScoreKeepersBoard.ContentViews;

public partial class HomePagePortrait : ContentView
{
    public HomePagePortrait()
    {
        InitializeComponent();
    }
}

Here is the HomePagePortrait.xaml file:

<?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="ScoreKeepersBoard.ContentViews.HomePagePortrait">
    <VerticalStackLayout>
        <Label 
            Text="Welcome to .NET MAUI portrait"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentView>

Here is the HomePageLandscape.xaml.cs file:

namespace ScoreKeepersBoard.ContentViews;

public partial class HomePageLandscape : ContentView
{
    public HomePageLandscape()
    {
        InitializeComponent();
    }
}

Here is the HomePageLandscape.xaml file:

<?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="ScoreKeepersBoard.ContentViews.HomePageLandscape">
    <VerticalStackLayout>
        <Label 
            Text="Welcome to .NET MAUI landscape"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </VerticalStackLayout>
</ContentView>

My project had an initial home Content Page called HomePage. We are loading the HomePageOrientationViewLoader ContentView into the xaml of HomePage Content Page as a custom control. Note that I had to define the namespace that the ContentViews were located in and use that when defining the control in the xaml file:

<?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"
             xmlns:controls="clr-namespace:ScoreKeepersBoard.ContentViews"
             x:Class="ScoreKeepersBoard.Views.HomePage"
             Title="HomePage">
    <VerticalStackLayout>
        <Label 
            Text="Welcome to .NET MAUI Home Page Content Page"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />

       <controls:HomePageOrientationViewLoader></controls:HomePageOrientationViewLoader>
        
    </VerticalStackLayout>
</ContentPage>

Here is the code behind for the home page

namespace ScoreKeepersBoard.Views;

public partial class HomePage : ContentPage
{
    public HomePage(HomeViewModel homeViewModel)
    {
        
        InitializeComponent();
    }         
}

and when the project runs on my iphone simulator in portrait mode:

enter image description here

You will see the second label shown says "Welcome to .NET MAUI portrait" which is the view from the portrait content view and when I switch to landscape:

enter image description here

You will see the second label shown says "Welcome to .NET MAUI landscape" which is the view from the landscape content view.

ISSUES

  1. This works on my iPhone simulator but when I switch to my Android pixel 5 simulator and toggle my switch phone orientation it doesn't work and putting in line breaks the code defined in HomePageOrientationViewLoader is not triggered. NEW NOTE: I tried this on a physical Android phone and it is working so it must have just been the emulator.

  2. I will need to use this for a non trivial example that has a view model that will be holding data on a sports game score, timing, etc. I guess I will just need to inject a singleton of the view model into each and they will just share and if the orientation switches the other Content View will load and the view model will bind to the appropriate controls?

  3. The initial suggested code by FreakyAli had this check:

     if (e.DisplayInfo.Orientation == DisplayOrientation.Landscape)
     {
         if (this.Content.GetType() is not typeof(HomePageLandscape))
         {
             this.Content = homePageLandscapeContentView;
         }
     }
    

but the part "typeof(HomePageLandscape) gives me an error and says a constant is expected.

Other than that the different views for different orientations is working and I thank FreakyAli mightily! I am sure I will figure out why the Android emulator is not triggering the orientation switch code, but suggestions would be awesome.


Solution

  • Ideally this is how I would handle such a scenario:

    In my constructor, I would get the DisplayInfoChanged event which notifies me if this info changes and I would also assign my current ContentView based on the current Orientation:

    DeviceDisplay.Current.MainDisplayInfoChanged += Current_MainDisplayInfoChanged;
    this.Content = DeviceDisplay.Current.MainDisplayInfo.Orientation == DisplayOrientation.Portrait ? potraitView : landscapeView;
    

    Here PortraitView is a ContentView that is the View I would display when my device is in Portrait and Vice Versa.

    And then handle the runtime change of the orientation as follows:

    private void Current_MainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
    {
            if(e.DisplayInfo.Orientation==DisplayOrientation.Landscape)
            {
                if(this.Content.GetType() is not typeof(LandscapeView))
                {
                    this.Content = landscapeView;
                }
            }
            else if (e.DisplayInfo.Orientation == DisplayOrientation.Portrait)
            {
                if (this.Content.GetType() is not typeof(PortraitView))
                {
                    this.Content = portraitView;
                }
            }
            else
            {
                //Whatever you would like to do if the orientation is unknown.
            }
    }