Search Unity

CanvasHelper resizes a RectTransform to iPhone X's safe area

Discussion in 'iOS and tvOS' started by _Adriaan, Mar 9, 2018.

  1. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    Hey everyone,

    I though it would be nice to share this script I wrote called CanvasHelper! The script helps your UI adhere to the safe areas of any device using Unity's built-in Screen.safeArea. Properly integrating safe areas in your UI makes your game feel native to the hardware, so I recommend using CanvasHelper for all your games.

    Even though this post's title says 'iPhone X', CanvasHelper will work on iOS and Android (plenty of Android phones now have safe areas too!) and will work on multiple platforms like phones, tablets (eg. iPad and Android Tablets), and even TV's (eg. Apple TV).

    CanvasHelper needs to be added to every Canvas GameObject in your game, and the script will resize a child RectTransform named "SafeArea" (no space!) to fit Screen.safeArea.



    Make sure to set the SafeArea's anchors to Min(0,0) and Max(1,1)!



    You may also register events to CanvasHelper.OnResolutionOrOrientationChanged, as Unity itself doesn't have a way for you register to those events.

    Code (CSharp):
    1. //source: https://forum.unity.com/threads/canvashelper-resizes-a-recttransform-to-iphone-xs-safe-area.521107
    2.  
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Events;
    6.  
    7. [RequireComponent(typeof(Canvas))]
    8. public class CanvasHelper : MonoBehaviour
    9. {
    10.     private static List<CanvasHelper> helpers = new List<CanvasHelper>();
    11.  
    12.     public static UnityEvent OnResolutionOrOrientationChanged = new UnityEvent();
    13.  
    14.     private static bool screenChangeVarsInitialized = false;
    15.     private static ScreenOrientation lastOrientation = ScreenOrientation.Landscape;
    16.     private static Vector2 lastResolution = Vector2.zero;
    17.     private static Rect lastSafeArea = Rect.zero;
    18.  
    19.     private Canvas canvas;
    20.     private RectTransform rectTransform;
    21.     private RectTransform safeAreaTransform;
    22.  
    23.     void Awake()
    24.     {
    25.         if(!helpers.Contains(this))
    26.             helpers.Add(this);
    27.  
    28.         canvas = GetComponent<Canvas>();
    29.         rectTransform = GetComponent<RectTransform>();
    30.  
    31.         safeAreaTransform = transform.Find("SafeArea") as RectTransform;
    32.  
    33.         if(!screenChangeVarsInitialized)
    34.         {
    35.             lastOrientation = Screen.orientation;
    36.             lastResolution.x = Screen.width;
    37.             lastResolution.y = Screen.height;
    38.             lastSafeArea = Screen.safeArea;
    39.  
    40.             screenChangeVarsInitialized = true;
    41.         }
    42.  
    43.         ApplySafeArea();
    44.     }
    45.  
    46.     void Update()
    47.     {
    48.         if(helpers[0] != this)
    49.             return;
    50.  
    51.         if(Application.isMobilePlatform && Screen.orientation != lastOrientation)
    52.             OrientationChanged();
    53.  
    54.         if(Screen.safeArea != lastSafeArea)
    55.             SafeAreaChanged();
    56.  
    57.         if(Screen.width != lastResolution.x || Screen.height != lastResolution.y)
    58.             ResolutionChanged();
    59.     }
    60.  
    61.     void ApplySafeArea()
    62.     {
    63.         if(safeAreaTransform == null)
    64.             return;
    65.  
    66.         var safeArea = Screen.safeArea;
    67.  
    68.         var anchorMin = safeArea.position;
    69.         var anchorMax = safeArea.position + safeArea.size;
    70.         anchorMin.x /= canvas.pixelRect.width;
    71.         anchorMin.y /= canvas.pixelRect.height;
    72.         anchorMax.x /= canvas.pixelRect.width;
    73.         anchorMax.y /= canvas.pixelRect.height;
    74.  
    75.         safeAreaTransform.anchorMin = anchorMin;
    76.         safeAreaTransform.anchorMax = anchorMax;
    77.     }
    78.  
    79.     void OnDestroy()
    80.     {
    81.         if(helpers != null && helpers.Contains(this))
    82.             helpers.Remove(this);
    83.     }
    84.  
    85.     private static void OrientationChanged()
    86.     {
    87.         //Debug.Log("Orientation changed from " + lastOrientation + " to " + Screen.orientation + " at " + Time.time);
    88.  
    89.         lastOrientation = Screen.orientation;
    90.         lastResolution.x = Screen.width;
    91.         lastResolution.y = Screen.height;
    92.  
    93.         OnResolutionOrOrientationChanged.Invoke();
    94.     }
    95.  
    96.     private static void ResolutionChanged()
    97.     {
    98.         //Debug.Log("Resolution changed from " + lastResolution + " to (" + Screen.width + ", " + Screen.height + ") at " + Time.time);
    99.  
    100.         lastResolution.x = Screen.width;
    101.         lastResolution.y = Screen.height;
    102.  
    103.         OnResolutionOrOrientationChanged.Invoke();
    104.     }
    105.  
    106.     private static void SafeAreaChanged()
    107.     {
    108.         // Debug.Log("Safe Area changed from " + lastSafeArea + " to " + Screen.safeArea.size + " at " + Time.time);
    109.  
    110.         lastSafeArea = Screen.safeArea;
    111.  
    112.         for (int i = 0; i < helpers.Count; i++)
    113.         {
    114.             helpers[i].ApplySafeArea();
    115.         }
    116.     }
    117. }
     
    Last edited: May 10, 2022
  2. turnipinrut

    turnipinrut

    Joined:
    Oct 27, 2014
    Posts:
    13
    yeahhhhh boiiiii, you just saved me some hours, thanks!
     
  3. Cadir

    Cadir

    Joined:
    Dec 18, 2016
    Posts:
    1

    My unity not recognized screen.safeArea.

    Please... Help me
     
  4. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    Your version of Unity is too old. You need at least 2017.3 I believe.
     
    MaxMittelstaedt likes this.
  5. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    (I just updated the script to be much more elaborate! Enjoy, everyone.)
     
  6. Nikesh78

    Nikesh78

    Joined:
    Mar 9, 2017
    Posts:
    2
    Hey,

    I want to run my game on full screen of IphoneX, by default it is running in safe area. Can you help me with this?

    Thanks
     
    mohaned1001 likes this.
  7. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    Anything you put in under the SafeArea transform will adhere to the safearea. Anything under the root of the Canvas will still be fullscreen!
     
    waisoserious and GameDevBryant like this.
  8. turnipinrut

    turnipinrut

    Joined:
    Oct 27, 2014
    Posts:
    13
    you need to update xcode so that you have the latest ios sdk, that will run unity at full screen on the iphone x
     
  9. squarepixel

    squarepixel

    Joined:
    Jan 29, 2016
    Posts:
    2
    hey ,
    Adriaan
    i implemented the script in the game object i am using unity 2017.3 version and xcode 9.2 . but the still getting the same result of full screen not in safe area
     
  10. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    - put the script on the GameObject called 'Canvas' (or the GameObject with the Canvas script)
    - add a child RectTransform to that Canvas that is called 'SafeArea', and exactly that.
    - add anything you want to adhere to the safe area as a child to the SafeArea object
     
    dregan likes this.
  11. ATMEthan

    ATMEthan

    Joined:
    Dec 27, 2012
    Posts:
    54
    Last edited: Jun 19, 2018
  12. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    @ATMEthan I use wantedReferenceResolution to scale my interface (depending on the screen size, in other code). You can remove it from the code if you don't want it.

    As for your second comment; the whole purpose of this script is to account for those pixel insets. Unity added Screen.safeArea to their API, and this script sets the size and position of a child object named 'SafeArea' to that safe area.
     
    dregan likes this.
  13. ATMEthan

    ATMEthan

    Joined:
    Dec 27, 2012
    Posts:
    54
    Thanks for the explanation on the reference resolution. That makes sense. And I figured out how to add margins to the SafeArea; during the anchor min and max calculations. I find it strange that the SafeArea for the iPhone X doesn't include the left and right margins but the layout guidelines warn you not to put content in the margins.(At least content that's meaningful)
     
  14. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    What Unity's API returns for Screen.safeArea is exactly the safe area (or 'margin' in your words) you must adhere to according to Apple. No need to add more margins. Just don't put any buttons and whatnot outside the SafeArea rect.
     
    dregan likes this.
  15. nickculbertson

    nickculbertson

    Joined:
    Oct 22, 2013
    Posts:
    7
    Works like a charm, thanks _Adriaan!
     
  16. NateReese77

    NateReese77

    Joined:
    Jun 14, 2017
    Posts:
    26
    Super bad ass! thanks for this.

    I was running into a small issue where the UI out of the SafeArea was getting scaled down, but that was fixed by updating wantedReferenceResolution to match our UI Resolution (we've been working in 1080 x 1920).
     
  17. ATMEthan

    ATMEthan

    Joined:
    Dec 27, 2012
    Posts:
    54
    Yes, you are right. My iPhone X simulator was giving me incorrect safe area values which is why I asked about the margins. Now that I have a physical iPhone X out of the box this scrip worked. Thanks for it!
     
  18. mannyvw

    mannyvw

    Joined:
    Jun 29, 2015
    Posts:
    3
    Great work Adriaan, annoyingly seems script is not working for me.

    I added the child RectTransform and added my controls to it ran it up, all good controls appear as they should (albiet with iPhoneX cutout still in the way), but just wanted to make sure i had added them correctly.

    Added the script into base ui canvas. run the app, the controls disappear completely. I renabled your debug line and got the following :-

    ApplySafeArea:

    Screen.orientation: Landscape
    Device.generation: iPhoneX
    Screen.safeArea.position: (132.0, 63.0)
    Screen.safeArea.size: (2172.0, 1062.0)
    Screen.width / height: (2436, 1125)
    canvas.pixelRect.size: (2436.0, 1125.0)
    anchorMin: (0.1, 0.1)
    anchorMax: (0.9, 1.0)
    CanvasHelper:ApplySafeArea()


    Quite new to unity am just trying to fix an existing app to allow for iPhoneX.

    Any ideas ?

    thanks

    Neil

    Untitled.png
     
  19. kgiannak

    kgiannak

    Joined:
    Apr 15, 2018
    Posts:
    1
    This looks promising, but can I combine it with a Canvas Scaler? When I add it, I get a message:

    "Non-root Canvases won't be scaled."
     
  20. adrian-taylor09

    adrian-taylor09

    Joined:
    Dec 22, 2016
    Posts:
    63
    @mannyvw Make your SafeArea rect transform set to "Stretch" in both directions, and make sure the top, left, right and bottom values are all 0... (screenshot attached)
     

    Attached Files:

  21. FlashyGoblin

    FlashyGoblin

    Joined:
    Apr 1, 2017
    Posts:
    23
    This works great, thanks! Is there also a method for getting the entire world to respect the SafeArea? Not just the UI?
     
  22. jdgauchat

    jdgauchat

    Joined:
    Jun 24, 2017
    Posts:
    18
    You thought right! :) Thank you!
     
  23. chandu1994

    chandu1994

    Joined:
    May 24, 2018
    Posts:
    2
    Hello,

    Touch not working when i implement this script and also not set proper resolution in iPad devices.

    How can i resolve this issue??
     
  24. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    My component has absolutely nothing to do with touch input so that's something you need to resolve yourself. As for iPads; those do not have safe areas yet, so... what is the issue exactly?
     
  25. Firemaw

    Firemaw

    Joined:
    Aug 24, 2015
    Posts:
    63
    Last edited: Oct 15, 2018
  26. InspiredSquare

    InspiredSquare

    Joined:
    Nov 16, 2015
    Posts:
    20
    Hi,
    Is there a way to run whole app under safe area or any app level setting to to force app to run under safe area so i do not need to change every scene.
     
  27. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    If by 'whole app' you mean every canvas in your game, then no: if you created a canvas in every scene of your game, you probably didn't build your game's structure right. That makes your question a bit off-topic regarding my original post, but to give you a quick answer: you'll want to load your interface 'additive' so that you can have 1 scene with all your interface. See the docs on loading scenes.
     
  28. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    Hey all: I updated the script by removing the resolution scaling and canvas camera functionality. I guess that stuff is weirdly specific and probably not what any of you were hoping to find in this script ;)
     
  29. rsherzod

    rsherzod

    Joined:
    Oct 30, 2016
    Posts:
    5
    Hey Adrian hi, I am working on UI and I am trying to scale canvas with the screen, when try to scale it from iphone 8(tall-750x1334) to iphone x(tall-1125x2436) resolution, I am having empty spot on top of canvas it is not filling whole screen. I think it is because aspect ratio is different on new x models of iphone, do you have any solution for this.Thank you in advance
     
  30. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    This is the whole point of the script. Anything under the 'SafeArea' transform will adhere to the safe area, which essentially means that the interface won't be under a notch.

    If you want, for instance, a background image to be fullscreen - even outside the safe area - then put it under the Canvas object, and not the SafeArea object.
     
  31. Nieles_GH

    Nieles_GH

    Joined:
    Jun 26, 2017
    Posts:
    57
    Thanks for the script. I found however that the script was a little over complicated and the safe area wasn't updated when rotating the phone because you compared safeArea.size, which stays the same when you rotate the phone. So instead I compare the entire rect.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(Canvas))]
    6. public class CanvasSafeArea : MonoBehaviour
    7. {
    8.     public RectTransform SafeAreaRect;
    9.  
    10.     private Rect lastSafeArea = Rect.zero;
    11.     private Canvas canvas;
    12.  
    13.     private void Awake()
    14.     {
    15.         canvas = GetComponent<Canvas>();
    16.     }
    17.  
    18.     private void Update()
    19.     {
    20.         if (lastSafeArea != Screen.safeArea)
    21.         {
    22.             lastSafeArea = Screen.safeArea;
    23.             ApplySafeArea();
    24.         }
    25.     }
    26.    
    27.     void Start()
    28.     {
    29.         lastSafeArea = Screen.safeArea;
    30.         ApplySafeArea();
    31.     }
    32.  
    33.     void ApplySafeArea()
    34.     {
    35.         if (SafeAreaRect == null)
    36.         {
    37.             return;
    38.         }
    39.  
    40.         Rect safeArea = Screen.safeArea;
    41.  
    42.         Vector2 anchorMin = safeArea.position;
    43.         Vector2 anchorMax = safeArea.position + safeArea.size;
    44.         anchorMin.x /= canvas.pixelRect.width;
    45.         anchorMin.y /= canvas.pixelRect.height;
    46.         anchorMax.x /= canvas.pixelRect.width;
    47.         anchorMax.y /= canvas.pixelRect.height;
    48.  
    49.         SafeAreaRect.anchorMin = anchorMin;
    50.         SafeAreaRect.anchorMax = anchorMax;
    51.     }
    52. }
    53.  
     
    CunningFox146 and AIStudio like this.
  32. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    Good point, I'll update the script.

    The 'complicated' code mostly makes sure you can have multiple canvases, which is pretty common for most games I've seen or worked on.
     
    Last edited: Mar 10, 2019
  33. Ben-BearFish

    Ben-BearFish

    Joined:
    Sep 6, 2011
    Posts:
    1,204
    @_Adriaan If I wanted to update the Camera.pixelRect to render to the Screen.safeArea, do you know how to do that?
     
  34. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    I wouldn't know this, sorry!
     
  35. Ben-BearFish

    Ben-BearFish

    Joined:
    Sep 6, 2011
    Posts:
    1,204
    No problem. Just been going through all the Screen.safeArea threads and asking around to see if anyone had any info. Thanks for the response.
     
  36. AM-Dev

    AM-Dev

    Joined:
    Aug 5, 2015
    Posts:
    31
    Hi @_Adriaan,
    thanks for sharing the script. Nobody asked, but I wanted to make sure that it works on all newer iPhones. I've got an iPhoneX, but not the iPhone XR, XS and XS Max. Does the safe area work on all devices?
    Thanks!
     
  37. FinalDaniel

    FinalDaniel

    Joined:
    Mar 29, 2017
    Posts:
    18
    Hi @_Adriaan,

    I am pretty sure there is a serious bug in this code - specifically, the y-axis is inverted. I think the safe area rect unity returns has the (x,y) in the top left of the rect, but this layout code assumes it is in the bottom left. ( for example, on and android device with a notch at the top of the screen, a debug log shows the safe area to be (0, 100, 1080, 1820 )

    I had built a safe area UI system based on this and a few samples found online, but found that when I tested it on an android device with a notch at the top of the screen it placed the 'gap' for the notch at the bottom of the screen.

    After a bit more testing on the xCode emulator, it turned out the same thing on the iPhoneX, but it is almost impossible to tell by eye as the iPhone X has spaces on the top and bottom of the screen that are very similar in size.

    I already have some of this code live in a few apps, and I needed a quick fix so I am adding this to the safe area rect I am getting back from Unity:

    // #TEMP_FIX - Invert Y-Axis of Safe Area Rect
    float newY = Screen.height - safeArea.height - safeArea.y;
    safeArea.y = newY;

    I will eventually refactor my code to work off the safe area properly, but I needed a fix ASAP and this was quick...
     
  38. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    Seems like this would work:
    Code (CSharp):
    1. camera.pixelRect = Screen.safeArea;
     
  39. Ben-BearFish

    Ben-BearFish

    Joined:
    Sep 6, 2011
    Posts:
    1,204
    I already tried that. It doesn't work on iPhone X. So not sure if something's off or wrong with Unity's Screen.safeArea for iPhone X, but I found a more native Objective-C solution.
     
  40. geganadiradze

    geganadiradze

    Joined:
    Sep 23, 2017
    Posts:
    8
    I use Xiaomi mi a2 lite and get similar results, I'm able to fix it by inverting y axis too.

    EDIT:
    Turns out it's bug in 2018.3
     
    Last edited: Apr 25, 2019
  41. JustAnotherDude

    JustAnotherDude

    Joined:
    Oct 28, 2013
    Posts:
    279
    Hi @_Adriaan, I didn't want to derail the other thread, but yours is exactly the script I was talking about, that changing quality breaks it during gameplay because Unity returns 0 width and 0 height when changing quality settings...

    On your update method I had to change this :

    Code (CSharp):
    1.             if (ScreenSafeArea.size != lastSafeArea)
    2.             {
    3.                 SafeAreaChanged();
    4.             }
    to this:

    Code (CSharp):
    1.             if (ScreenSafeArea.size != lastSafeArea && ScreenSafeArea.size.x > 1f && ScreenSafeArea.size.y > 1f)
    2.             {
    3.                 SafeAreaChanged();
    4.             }
    ScreenSafeArea being just a property I have :

    Code (CSharp):
    1.     public static Rect ScreenSafeArea
    2.     {
    3.         get
    4.         {
    5. #if UNITY_EDITOR
    6.             if (emulateIPhoneX)
    7.             {
    8.                 return new Rect(132, 63, 2172, 1062);
    9.             }
    10. #endif
    11.  
    12.             return Screen.safeArea;
    13.         }
    14.     }
    Tested on a Samsung J5 and Unity 2019.1.1f1.

    I attached my test project, this only happened when running on a Android device (didn't happen on my ipad).

    Before you test it comment this part of the test on CanvasHelper.cs line 98 :
    Code (CSharp):
    1. && ScreenSafeArea.size.x > 1f && ScreenSafeArea.size.y > 1f
    Then just build and run the project and click to change between medium, high and very high and it should randomly disappear.

    I really don't understand why, but it completely breaks the canvas here.

    Uncomment the piece of code above and it no longer disappears.
     

    Attached Files:

  42. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    This is very clearly a Unity bug, and not a bug from my script. Best submit a bug report to Unity to get this fixed ASAP.
     
  43. danthewahl

    danthewahl

    Joined:
    Jul 23, 2017
    Posts:
    1
    Hey Adriaan. I am having an issue with the script where it seems like it is positioning the buttons correctly, but they are expanding in size and overlapping each other for iPhone X. Do you know a workaround for this? I'll attach a pic on iPhone 8 (the way it should look) and the overlapping on iPhone X.

    Image 6-13-19 at 11.59 PM.jpg

    Image 6-14-19 at 12.01 AM.jpg
     

    Attached Files:

  44. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    This is beside the point of my script, but it probably has something to do with the way the canvas scaler scales your UI elements up or down. There are thousands of ways to avoid it or deal with it, so play around with it!
     
  45. Quartsik

    Quartsik

    Joined:
    Aug 9, 2015
    Posts:
    2
    Good job, thank you!
     
  46. Gurosoft

    Gurosoft

    Joined:
    Sep 14, 2017
    Posts:
    7
    Hello I with all testing I have the same problems.
    When nothc is TOP position safeArea be wrong
    because if I have screen like (1080,1920)
    And Top notch my safe area rect will be (0,0,1080,1728)
    Then Start top and but canvas to top!

    Same with bottom notch...but for bottom "it is ok" because this problem.

    Any solve to move right this safe area rect?
    upload_2019-9-5_11-41-3.png
     
  47. shanecelis

    shanecelis

    Joined:
    Mar 26, 2014
    Posts:
    22
    This code was very helpful and workable. I found an open source project[1] that provides similar functionality with some extra functionality like preview cutouts in the editor.

    [1]: https://github.com/5argon/NotchSolution
     
    Aranda and niknakgames like this.
  48. chrisrcisme

    chrisrcisme

    Joined:
    Jul 7, 2014
    Posts:
    16
    Great script by the way! :)
    Also, I'm not sure if its been point out yet but when using Galaxy A70 the resolution changes from 2324 on x to 2400 or the other way round from some testing I have done last night so the ResolutionChanged() function should include some specific mobile makes and models.
     
  49. drorriov

    drorriov

    Joined:
    Jun 7, 2014
    Posts:
    43
    Thanks for sharing this script!
    Since I use the same project for android and ios with the same canvas I used #if UNITY_IOS since I don't need it for android.
    The only thing left is to test it works for iPhone X/XS etc.. Is there a way to check it within the editor?
     
  50. _Adriaan

    _Adriaan

    Joined:
    Nov 12, 2009
    Posts:
    481
    1. Android also has phones with notches and safe areas, so you shouldn't only need this for iOS.
    2. Unity recently gave a talk about a preview thing they're making that will give you a nice device preview. Video here:



    (also includes a Unity employee using CanvasHelper!)
     
    Last edited: Dec 8, 2019
    tolosaoldfan and LouisMareau like this.