Unity SafeArea is inconsistent between different starting rotations

I’m trying to make a script that adjusts the SafeArea of my Canvas when the device is rotated. I’ve got it almost working and it looks great on-launch, but I’m getting a weird result after rotating that I can’t understand: the SafeArea is different depending on whether the app launched in Portrait or Landscape mode.

When I log out the value of Screen.safeArea on an iPhone 12 Pro, for example, I get the following values:

  • Portrait on launch: (x:0.00, y:102.00, width:1170.00, height:2289.00)
  • Portrait rotated to Landscape: (x:0.00, y:102.00, width:2532.00, height:927.00)
  • Landscape on launch: (x:141.00, y:63.00, width:2250.00, height:1107.00)
  • Landscape rotated to Portrait: (x:141.00, y:63.00, width:888.00, height:2469.00)

The Width/Height values are changing on rotation, which makes sense, but the X/Y offsets are not. Given the particular nature of the iPhone 12, that means there is a big empty space at the top when launching in Portrait and rotating to Landscape but overflowing the notch to the side, and big empty spaces on both sides when launching in Landscape and rotating to Portrait but overflowing the notch to the top.

Is this the expected behavior? I would have thought the “launching in Portrait” and “rotated to Portrait” safeAreas should be the same, right? Is there a way to force-refresh the safeArea or something, to get the proper X/Y values? Or am I misunderstanding how this is supposed to work?

This is indeed a bug that we are aware of, but alas we dont know how to fix this (yet, hopefully ;-))

Thanks for the reply! Are there any recommended workarounds for now, or best practices, etc?

Hi, I have a similar problem, the safe screen is not correct anymore, I even downgraded unity to a version which used to work and it has the same problem, so I don’t know how I can update my app anymore since I cannot find the cause. It seems like an issue with iOS15

Any news on this? @Alexey

Noticed that this problem started happening after I’ve updated Xcode to version 13. I would recommend downloading Xcode 12.5.1 or some older one and then making a build. As I got the problem is in Xcode, not in iOS15 or Unity.

You can find older Xcode versions here:

I hope this will help! Cheers!

Ran right into this yesterday. Any updates?

I tried Xcode 12.5.1, I have the same problem, Screen.Safearea returns wrong values for iPhone 12 Pro in Landscape mode (all 4 margins are wrong), basically left and right margins are missing completely, the top and bottom are there but wrong. The Landscape values are correct if I start the application in Landscape, but then the Portrait values are wrong when I flip the phone and vice-versa. I temporarily solved the first problem by getting the safeArea with a native script, I limited this case to only iPhone and Landscape.

I ran into this issue today. It is related to iOS15, not Xcode. Safe area is correct on iOS14.8 compiled with Xcode13.1 and Unity2018.4.36.

@jj1991 Can you share your native script?

Yeah sure here, not sure if it works correctly on every device but better than the bug and the problem with starting the app in landscape and then turning to portrait is not solved

public class iOSSafeArea : MonoBehaviour
{
    [DllImport("__Internal")]
    public static extern string getSafeArea();

    public static Rect _getSafeArea()
    {
        float scaleFactor = DeviceDisplay.scaleFactor;
        string[] margins = getSafeArea().Split(':');
        float top = float.Parse(margins[0])* scaleFactor;
        float bottom = float.Parse(margins[1]) * scaleFactor;
        float right = float.Parse(margins[2]) * scaleFactor;
        float left = float.Parse(margins[3]) * scaleFactor;

        Rect rect = new Rect(); //new Rect(right, top,(Screen.width- left), (Screen.height-bottom));

   
        rect.xMin = left;
        rect.yMin = bottom;
        rect.xMax = Screen.width - right;
        rect.yMax = Screen.height;

        return rect;

    }
#import <AVFoundation/AVFoundation.h>

extern "C" {

char* convertNSStringToCString(const NSString* nsString)
{
    if (nsString == NULL)
        return NULL;

    const char* nsStringUtf8 = [nsString UTF8String];
    //create a null terminated C string on the heap so that our string's memory isn't wiped out right after method's return
    char* cString = (char*)malloc(strlen(nsStringUtf8) + 1);
    strcpy(cString, nsStringUtf8);

    return cString;
}

char* getSafeArea() {
    UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
       CGFloat topPadding = window.safeAreaInsets.top;
       CGFloat bottomPadding = window.safeAreaInsets.bottom;
       CGFloat rightPadding = window.safeAreaInsets.right;
       CGFloat leftPadding = window.safeAreaInsets.left;
  
    NSString *str = [NSString stringWithFormat:@"%f:%f:%f:%f", topPadding, bottomPadding,rightPadding,leftPadding];
  
    return convertNSStringToCString(str);
}

}

Any news on this being fixed any time soon? Seeing this issue on the iPhone 13 running iOS 15.

I don’t think its fixed yet:

Search for the file UnityView.mm from the iOS build project.
Then modify the function ComputeSafeArea(UIView* view).

CGRect ComputeSafeArea(UIView* view)
{
    UIWindow *window = UnityGetMainWindow();
    CGRect screenRect = window.bounds;
    float scale = window.screen.scale;
   
    UIEdgeInsets insets = window.safeAreaInsets;
   
//    CGSize screenSize = view.bounds.size;
//    CGRect screenRect = CGRectMake(0, 0, screenSize.width, screenSize.height);
//
//    UIEdgeInsets insets = [view safeAreaInsets];
   
    screenRect.origin.x += insets.left;
    screenRect.origin.y += insets.bottom; // Unity uses bottom left as the origin
    screenRect.size.width -= insets.left + insets.right;
    screenRect.size.height -= insets.top + insets.bottom;
   
//    float scale = view.contentScaleFactor;
   
    // Truncate safe area size because in some cases (for example when Display zoom is turned on)
    // it might become larger than Screen.width/height which are returned as ints.
    screenRect.origin.x = (unsigned)(screenRect.origin.x * scale);
    screenRect.origin.y = (unsigned)(screenRect.origin.y * scale);
    screenRect.size.width = (unsigned)(screenRect.size.width * scale);
    screenRect.size.height = (unsigned)(screenRect.size.height * scale);
    return screenRect;
}
1 Like

Note that safeAreaInsets of UIWindow does not return the correct value in some models without a home bar.

Since the size of UIWindow is different from that of Unity, it is better to get only safeAreaInsets in the plugin and create safeArea in Unity side.
The approach of jj1991 is better.

Qiita article about SafeArea.

we also run into the same problem. It’s only appearing on iOS 15 devices. Nothing to do with different Xcode versions or iPhone models. Same iPhone with iOS 14.4 works fine, iPhone with 15.1 delivers wrong safeArea values after rotation from portrait to landscape.

Digging deeper we found out, that the problem seems to be related to both, XCode 13 and iOS 15. An XCode 13 build runs on iOS 14 device with correct safeArea after rotation. Same build on iOS13 device has the safeArea Bug after rotation. A build with XCode 12.5.1 run on iOS14 and iOS15 devices as expected.

We have apps built with Unity 2018.4.34. A fix for Unity 2018 LTS would be awesome!

1 Like

I have a solution that fixes all these issues. We couldn’t wait for Unity to backport the fix to Unity 2020 LTS. According to their own bug report, Unity fixed the issues in Unity 2022.1.0.a14. So I installed the latest 2022.1.0b2 and compared the Obj-C source code in PlaybackEngines/iOSSupport/Trampoline/Classes folders between 2022.1.0 and 2020.3.25f1 LTS that we’re using. If you do the same, you can see the fixes they made.

Copy the following files to Unity 2020 and then it works:

UI/SplashScreen.mm
UI/UnityAppController+ViewHandling.mm
UI/UnityView.mm
UI/UnityViewControllerBase.*
UI/UnityViewControllerBase+iOS.*
Unity/DeviceSettings.mm
Unity/DisplayManager.mm

2 Likes

After experimenting for hours, I found the cause of this issue. In UI/UnityAppController+ViewHandling.mm

  • (void)showGameUI,
169     // UI hierarchy
170     [_window addSubview: _rootView];
171     _window.rootViewController = _rootController;                                                    
172     [_window bringSubviewToFront: _rootView];

This code is quite unusual. Content view will be set in rootViewController setter and it is a common use. Why to addSubView manually? On iOS 15, it makes the UnityView’s safe area can’t update with UIWindow.

Comment out the addSubview line and the safeAreaInsets/LayoutGuide will be corrent.

You can edit that line in /Applications/Unity/Hub/Editor/2019.4.34f1/PlaybackEngines/iOSSupport/Trampoline/Classes/UI/UnityAppController+ViewHandling.mm to fix this issue for all future builds.

And this issue also causes a bug in Admob banner placement which depends on the safe area properties of the UnityView.