CGAffineTransform breaks safe area insets in iOS 14?

In our app we have a TableViewController that is vertically flipped by setting tableView.transform = CGAffineTransform(scaleX: 1, y: -1) (and the same is applied to all cells).

We noticed in iOS 14, initially the layout respects the safe area as expected, but after rotating the device it does not update properly, but the same safe area insets as before are applied. So for example when opening the view in portrait and then rotating to landscape, the insets left and right are still 0 and the content is rendered outside the safe area.

When looking at tableView.safeAreaInsets, you can see the values are not updated after rotating, and viewSafeAreaInsetsDidChange() does not get called. The bug can be reproduced with any transformation.

This worked perfectly fine in iOS 13. Is this a bug in UIKit? Is there a workaround?
Answered by HolgerDe in 659050022
So I just got a reply from Apple to a bug report I filed for this, and it turns out the propagation of safe area insets to a view with a scale or rotation transform is not supported. You have to apply the transform to a view deeper in the view hierarchy or handle it yourself some other way.

Bummer, but good to know.
Workaround would be to manually adjust the insets in viewWillLayout.

Could you show the code where the transform is ?
Do you call it in an animation ? If so, what is the completion handler ?

The transform is set in viewDidLoad. But I can reproduce the problem by setting the transform afterwards as well.

The problem with just setting the insets myself is that I don't know where to get the correct values from, as the safeAreaInsets are not updated, which is the problem. Where else might I get the correct safe are insets values from?
Could you show the full code of the func where and how you call transform ?
Code Block swift
override func viewDidLoad() {
super.viewDidLoad()
tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
}


That's all it takes. Instead of CGAffineTransform(scaleX: 1, y: -1) you can also just do CGAffineTransform(scaleX: 0.99, y: 0.99), it has the same effect on the safeAreaInsets not updating.
I tested on a sample app.
I did not notice the issue.

I tested the following
Code Block
override func viewDidLoad() {
super.viewDidLoad()
print("at load", tableView.safeAreaInsets)
tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
print("after transform", tableView.safeAreaInsets)
override func viewLayoutMarginsDidChange() {
super.viewLayoutMarginsDidChange()
print("layout changed", tableView.safeAreaInsets)
}


I get the following prints (viewLayoutMarginsDidChange called twice)
At load
at load UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
after transform UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
layout changed UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
layout changed UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

After rotation
layout changed UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
layout changed UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
After rotating back
layout changed UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
layout changed UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)


Could you do the same test and report results ?

On what device did you test this? The bug is only relevant on iPhones with notch, i.e. without a home button. I should have clarified that, sorry. The following numbers are from an iPhone 12 emulator, with iOS 14.3.

So the behavior is interesting. When the app starts with the UITableViewController as initial view controller, I also get 0 all around for the safe area insets:

At load:

at load UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
after transform UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
layout changed UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)

after rotation: no output, viewLayoutMarginsDidChange is not called.

However, if I push to another view controller and then return, upon return:

layout changed UIEdgeInsets(top: 143.0, left: 0.0, bottom: 34.0, right: 0.0)

after rotation: again, no output

I just double checked this on a real device (iPhone XS) and get the same results.
Accepted Answer
So I just got a reply from Apple to a bug report I filed for this, and it turns out the propagation of safe area insets to a view with a scale or rotation transform is not supported. You have to apply the transform to a view deeper in the view hierarchy or handle it yourself some other way.

Bummer, but good to know.
CGAffineTransform breaks safe area insets in iOS 14?
 
 
Q