Aha! It's actually magic!
I finally figured this out after hours of cursing Apple.
UIKit actually does handle this for you, and it appears that the shifted tab bar items are due to incorrect setup (and probably an actual UIKit bug). There is no need for subclassing or a background view.
UITabBar will "just work" if it is constrained to the superview's bottom, NOT to the bottom safe area.
It even works in Interface builder.
Correct Setup
- In interface builder, viewing as iPhone X, drag a UITabBar out to where it snaps to the bottom safe area inset. When you drop it, it should look correct (fill the space all the way to the bottom edge).
- You can then do an "Add Missing Constraints" and IB will add the correct constraints and your tab bar will magically work on all iPhones! (Note that the bottom constraint looks like it has a constant value equal to the height of the iPhone X unsafe area, but the constant is actually 0)
Sometimes it still doesn't work
What's really dumb is that you can actaully see the bug in IB as well, even if you add the exact constraints that IB adds in the steps above!
- Drag out a UITabBar and don't snap it to the bottom safe area inset
- Add leading, trailing and bottom constraints all to superview (not safe area)
Weirdly, this will fix itself if you do a "Reverse First And Second Item" in the constraint inspector for the bottom constraint. ¯_(ツ)_/¯