Switch Device Orientation at Runtime in Xamarin.Forms iOS and Android

I recently ran into a situation where an existing Forms app was written with UWP Desktop in mind, without much thought given to the later requirements of handheld devices running iOS and Android. The client wanted to see the app finished, then up and running on iOS and Android in short order, but the UWP app’s look and feel could not be affected by any changes required to make it all work.

Luckily, most of this could be accomplished with some minor touch-ups in the UI and by making sure all of the image assets and resources were prepared for the new platforms.  There was one glaring problem,  however: several of the views required screen real-estate wider than is available on most Android phones or iPhones.

Long story short, the existing architecture was a challenge and with little time to polish it up I had to work with what was handed to me. Rather than try to rewrite a number of views and controls, I chose to force the affected views into Landscape mode at runtime as needed.  Both iOS and Android require a different approach to make this happen, so here’s how I handled it.

Android

For Android we’re going to leverage the “poor man’s decoupler”, the MessagingCenter.  Your code will subscribe to the messages in Android’s MainActivity.cs, and send from the code-behind of whatever page requires it.

First you need to specify that configuration changes for ScreenSizeand Orientation are permissible, and you also need to set the default orientation.  This can all be done in MainActivity.cs using the Activityattribute:

MainActivity.cs

[Activity(Label = "WSBestAppEver", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.Portrait)]

Then you just need to subscribe to the messages in your code page OnCreate() override:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    global::Xamarin.Forms.Forms.Init(this, bundle);
    MessagingCenter.Subscribe<MyPage>(this, "setLandscape", sender =>
    {
        RequestedOrientation = ScreenOrientation.Landscape;
    });
    MessagingCenter.Subscribe<MyPage>(this, "setPortrait", sender =>
    {
        RequestedOrientation = ScreenOrientation.Portrait;
    });
}

Finally, you need to send out the messages from the code-behind of any pages which need to be displayed in an alternate orientation. You’ll set the new orientation in your OnAppearing() override, and don’t forget to set it back in the OnDisappearing() override. In the case of the OnAppearing() method, we’re only sending out the message when Device.Idiom is a phone:

MyPage.xaml.cs

protected override void OnAppearing()
{
    base.OnAppearing();
    if (Device.Idiom == TargetIdiom.Phone)
    {
        MessagingCenter.Send(this, "setLandscape");
    }
}
protected override void OnDisappearing()
{
    base.OnDisappearing();
    MessagingCenter.Send(this, "setPortrait");
}

iOS

The iOS solution is somewhat annoying in my opinion, and could become quite tedious if you have multiple views that require this behavior. First, we need to insert some custom handling code in AppDelegate.cs. Then, we need to create a custom renderer for every page that needs to be displayed in the alternate orientation.

In AppDelegate.cs, we override UIInterfaceOrientationMask GetSupportedInterfaceOrientations() and include our custom code there. Note that we are checking if the Last() item on the NavigationStack is of type MyPage, and also if the UIDevice.CurrentDevice.UserInterfaceIdiom is a phone:

AppDelegate.cs

public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, UIWindow forWindow)
{
    var mainPage = Xamarin.Forms.Application.Current.MainPage;
    if (mainPage.Navigation.NavigationStack.Last() is MyPage
        && UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone)
    {
        return UIInterfaceOrientationMask.Landscape;
    }
    return UIInterfaceOrientationMask.Portrait;
}

At this point, the iOS app will switch to Landscape whenever we open the page. However, the app will not switch back to portrait yet, which is why we need the custom renderer for each page. Luckily, the custom renderer we need for this is relatively straightforward to create:

MainActivity.cs

using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using PFApp;
using MyForm.iOS;
[assembly: ExportRenderer(typeof(MyPage), typeof(MyPageRenderer))]
namespace MyForm.iOS
{
    public class MyPageRenderer : PageRenderer
    {
        public override void ViewWillDisappear(bool animated)
        {
            base.ViewWillDisappear(animated);
            UIDevice.CurrentDevice.SetValueForKey(NSNumber.FromNInt((int)(UIInterfaceOrientation.Portrait)), new NSString("orientation"));
        }
    }
}

And that’s it. Unfortunately Xamarin.Forms doesn’t provide a baked-in solution for this yet, but using this pattern you can force your Android and iPhone apps to switch to Landscape (or any other orientation) on demand. Also, by removing or re-applying the Device.Idiom enum or the UIDevice.CurrentDevice.UserInterfaceIdiom code, you can also target specific device types beyond phones.

Improvements

This code has some obvious room for improvement.  For example, we could create a base class for each of the pages that require the alternate orientation and inherit from those.  Then our single MessagingCenter subscription in Android could handle all of our custom pages. Similarly, the AppDelegate.cs code in iOS could type check against the single base class, eliminating the need for additional ifblocks.

If you have further suggestions for how to improve this, or if you see something I missed, leave a comment !

Scroll to Top