Notifications
Article
Updating your GUI for the iPhone X and other “Notched” Devices
Updated 4 days ago
690
0
Learn how to make your GUI compliant with Safe Areas on “notched” mobile devices such as the iPhone X and Pixel 3 XL
When the iPhone X was released in 3 Nov 2017, it brought with it a completely new interface paradigm - the sensor housing (or “notch” as most people call it) and the Home indicator. With these two interface elements, Apple had changed the face of the mobile GUI design forever. Ever since then, the advent of the iPhone Xs, iPhone Xs Max, Google Pixel 3 XL and other notched Android phones have proven that the notch is most definitely here to stay.
If you have ever held an iPhone X in your hand, it certainly is a wonder to behold. It’s a truly beautiful and innovative device, with its full end-to-end screen and smooth, curved edges. However, with this beauty and innovation comes a price: added development time...
What does this mean for you as a mobile developer? Well, it means you’ll have to adapt your GUI to deal with it in future game releases and updates to existing games. Apple has already set recommendations in their submission guidelines and Google will no doubt follow suit once notches (they call them “cutouts”) are officially supported by the SDK in Android 9 (Pie).
In addition to the notch, many new phones are skewing the traditional aspect ratio of 16:9 for a smartphone and 4:3 for a tablet. The iPhone X, for example, has an aspect ratio of approximately 19.5:9, and similar notched Android phones may have anything between 18:9 and 19:9!
I will cover how to deal with all of these factors in this article, using Unity.

Screen Safe

At the core of the solution is the “screen safe” area. Unity 2017.2.1 and above provides an interface to query this area at runtime - Screen.safeArea. This returns a Rect (x, y, width, height) of the safe area’s bounds in device pixels. Note that the values returned will be device-specific and orientation specific. So, it needs to be queried and handled on-the-fly and account for device rotation at run-time (between portrait and landscape).
We can write a MonoBehaviour script for a GUI element that constantly checks if the safe area has changed and applies any changes to the attached UI panel if so (via its RectTransform).
Note: A link to download all scripts and resources from this article is available at the bottom of the page.
public class SafeArea : MonoBehaviour { RectTransform Panel; Rect LastSafeArea = new Rect (0, 0, 0, 0); void Awake () { Panel = GetComponent<RectTransform> (); Refresh (); } void Update () { Refresh (); } void Refresh () { Rect safeArea = GetSafeArea (); if (safeArea != LastSafeArea) ApplySafeArea (safeArea); } Rect GetSafeArea () { return Screen.safeArea; } void ApplySafeArea (Rect r) { LastSafeArea = r; // Convert safe area rectangle from absolute pixels to normalised anchor coordinates Vector2 anchorMin = r.position; Vector2 anchorMax = r.position + r.size; anchorMin.x /= Screen.width; anchorMin.y /= Screen.height; anchorMax.x /= Screen.width; anchorMax.y /= Screen.height; Panel.anchorMin = anchorMin; Panel.anchorMax = anchorMax; Debug.LogFormat ("New safe area applied to {0}: x={1}, y={2}, w={3}, h={4} on full extents w={5}, h={6}", name, r.x, r.y, r.width, r.height, Screen.width, Screen.height); } }
To give you a solid understanding of what’s going on here, it’s best to demonstrate this in a test scene. We can create a base UI Canvas with the following properties:
  • Canvas/Render Mode = “Screen Space - Overlay”
  • Canvas Scaler/UI Scale Mode = “Scale With Screen Size”
  • Canvas Scaler/Match (Width-Height) = 0.5
This allows us to have a generic canvas that will scale evenly on both axes and work with any device resolution and aspect ratio.
Inside the Canvas, we can create some basic shapes to help us visualize the safe area:
  1. A full screen background image for visibility purposes (Anchor 0,0 to 1,1; Pivot 0.5,0.5)
  2. As a child of the background, a safe screen image with the safe area script attached (same anchor and pivot)
  3. As children of the safe area image, four children that anchor to each corner, with different colors
For testing in the Editor, we will also need to create two new aspect ratios that emulate the iPhone X:
  • 19.5:9 (or 195:90)
  • 9:19.5 (or 90:195)
The following images show us what it looks like on an iPhone X with no safe area versus having a safe area applied:
As you can see, on the iPhone X, the safe area pushes the GUI into the top and bottom in portrait mode, and pushes into the left, bottom and right sides in landscape mode.
Note: There is no Portrait Upside Down mode on the iPhone X. If you physically rotate the device upside down, it remains in Portrait Up mode. This is an intended feature of the device.
Note: Unity 2017.3 and above includes an option in the iOS Player Settings to “Hide home button on iPhone X.” This does not hide the Home indicator permanently, but rather performs an “auto-hide” function - whenever a tap is detected anywhere on the screen, the Home indicator will re-appear, then fade out after a few moments. This can be very distracting while playing a game, so it is not recommended. It is only recommended for video-player like applications. Also, regardless of whether this option is turned on or off, the safe area will remain the same (the bottom margin does not change).

Case Study 1: No Background

For our first case study, we’ll look at a full screen main menu interface with buttons anchored to various edges of the screen. Note that these buttons all float in the scene with no full screen GUI background. Here is what is looks like without screen safe applied.
As you can see, we have several problems that violate the Apple user interface guidelines:
1. Portrait Mode:
  • Critical GUI elements at the top of screen are partially hidden by the notch
  • Critical GUI elements at the bottom of screen are overlapping the Home indicator
2. Landscape Mode:
  • Critical GUI elements on the left are partially hidden by the notch
  • Critical GUI elements on the right are outside the screen safe area
  • Critical GUI elements on the bottom are overlapping the Home indicator
In this case, “critical GUI elements” refer to buttons or important text information that are required to operate the game.
This is the easiest case to fix. We simply drag the SafeArea.cs script onto the top-level Main Menu panel. Here is the result:

Case Study 2: Full Screen Background

In this case we have a submenu - an options screen - that contains a full screen background image. If we apply the safe area script to the top-level panel as we did in Case Study 1, here is what it looks like:
Our buttons and text are correctly within the safe area; however, we also have the light blue background conforming to the screen safe area, which doesn’t look nice at all. We want the background to fill the whole screen, ignoring the safe area.
To fix this, we need to create an intermediate panel between the background image and its children, then attach the safe area script to this panel. Here is what the hierarchy looks like:
Here is the result:

Case Study 3: Complex XY Layout

What happens when we have a more complex layout like this?
In this case, we want the horizontal stripes in the middle to stretch to full width, but have their buttons and text conform to screen safe. In addition, we want the bottom row of buttons to conform to screen safe. How do we do this?
We need to add an option on our safe area script to conform to the X- or Y-axis independently. On the child of each horizontal stripe (PnlIncentive), we add the safe area script and only check Conform X. On the parent panel of the bottom row (PnlSafe), we add another instance of the safe area script with both Conform X and Y checked. Here is what the hierarchy looks like in the Editor:
Here is what it looks like in the game:
Using the Conform X/Y technique in different combinations will allow us to cover any other special cases we encounter. Sometimes it just takes a little planning and head-scratching before you figure it out ;)

Editor Simulation

Now for the juicy part. When rapidly iterating and testing an interface, we certainly don’t want to be building to the device or simulator every time, which can take minutes or more depending on the size of your project. We want to be able to test things out quickly and efficiently in the Editor. To do this, we can extend the script to simulate a safe area, and add a hotkey to toggle the safe area on or off at runtime.
Using the safe area pixel coordinates returned from the iPhone X, we can normalize and save these values into the script for simulation.
public enum SimDevice { None, iPhoneX } public static SimDevice Sim = SimDevice.None; Rect[] NSA_iPhoneX = new Rect[] { new Rect (0f, 102f / 2436f, 1f, 2202f / 2436f), // Portrait new Rect (132f / 2436f, 63f / 1125f, 2172f / 2436f, 1062f / 1125f) // Landscape };
We can use the same technique for any Android phone and simply add it to the SimDevice list in the same way. This allows us to add an unlimited number of virtual test devices within the Editor.
Using these normalized coordinates, we can extend the GetSafeArea() method to include simulated devices when in Editor mode only. This multiplies out the normalized safe area by the runtime screen width and height, so that it will reactively work with any screen pixel size and aspect ratio we are running in the Editor’s Game Window.
Rect GetSafeArea () { Rect safeArea = Screen.safeArea; if (Application.isEditor && Sim != SimDevice.None) { Rect nsa = new Rect (0, 0, Screen.width, Screen.height); switch (Sim) { case SimDevice.iPhoneX: if (Screen.height > Screen.width) // Portrait nsa = NSA_iPhoneX[0]; else // Landscape nsa = NSA_iPhoneX[1]; break; default: break; } safeArea = new Rect (Screen.width * nsa.x, Screen.height * nsa.y, Screen.width * nsa.width, Screen.height * nsa.height); } return safeArea; }
To toggle between these sim devices at runtime, we can create a toggle script with a customizable hotkey. Attach this script to an object in any scene to enable the behavior. In any non-Editor build environment, it will destroy itself.
public class SafeAreaDemo : MonoBehaviour { [SerializeField] KeyCode KeySafeArea = KeyCode.A; SafeArea.SimDevice[] Sims; int SimIdx; void Awake () { if (!Application.isEditor) Destroy (gameObject); Sims = (SafeArea.SimDevice[])Enum.GetValues (typeof (SafeArea.SimDevice)); } void Update () { if (Input.GetKeyDown (KeySafeArea)) ToggleSafeArea (); } /// <summary> /// Toggle the safe area simulation device. /// </summary> void ToggleSafeArea () { SimIdx++; if (SimIdx >= Sims.Length) SimIdx = 0; SafeArea.Sim = Sims[SimIdx]; Debug.LogFormat ("Switched to sim device {0} with debug key '{1}'", Sims[SimIdx], KeySafeArea); } }

Conclusion

We have created a generic way to conform to mobile safe areas on multiple devices and orientations. In addition, we have an extensible system to test and iterate quickly within the Editor using device simulation.
The scripts and test scene used here are available free for download on the Unity Asset Store.
To see Oopstacles in all its iPhone X reactive glory, visit our Facebook page for a download link, and please throw us a LIKE while you are there ;)
Thanks for reading.

Crystal Pug
3
Comments