You are right that setting masksToBounds=true is the only way to allow tabBar.layer.cornerRadius value to apply, but setting it to true will remove any shadows!
A lot of solutions involve adding a dummy view behind the tab bar, and apply shadows to it. It works but once I got it to work by adding the dummy view as a subview of the parent view, behind the tab bar, when I tried to navigate to another screen by pushing a view controller with hidesBottomBarWhenPushed=true, the tab bar is hidden but the dummy view remains on the screen :(
After 2 days of trial and error, I found a solution that doesn't require adding a dummy view, but instead it adds a sublayer to round the corners and set the drop shadows. It's simple enough that there's no subview involved and doesn't require masksToBounds to be set to true.
Inspired by this answer: https://stackoverflow.com/a/63793084/1241783
Instead of subclassing the UITabBar, I made a subclass of UITabBarController, and added this function:
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(
roundedRect: tabBar.bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: tabBarCornerRadius, height: 0.0)).cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.shadowPath = UIBezierPath(roundedRect: tabBar.bounds, cornerRadius: tabBarCornerRadius).cgPath
shapeLayer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2).cgColor
shapeLayer.shadowOpacity = 1
shapeLayer.shadowRadius = 16
shapeLayer.shadowOffset = CGSize(width: 0, height: -6)
// To improve rounded corner and shadow performance tremendously
shapeLayer.shouldRasterize = true
shapeLayer.rasterizationScale = UIScreen.main.scale
if let oldShapeLayer = self.shapeLayer {
tabBar.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
tabBar.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
Call this function in override of viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
addShape()
}
And finally, the last piece of the puzzle, Tab bar automatically creates a backdrop view which doesn't have rounded corners, so we need to make the backdrop view transparent, by adding these two lines in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
tabBar.shadowImage = UIImage() // this removes the top line of the tabBar
tabBar.backgroundImage = UIImage() // this changes the UI backdrop view of tabBar to transparent
}