ipadipadosios18ipados18

How to disable the new UITabBarController view style in iPadOS 18


iPadOS 18 introduces new API for creating tab bars.

Create a UITabBarController and assign an array of UITab objects to its tabs property.

How do we disable this new style and keep the tab bar at the bottom for iPadOS 18?

enter image description here


Solution

  • After playing around, I have found the following to work the best. It works with split screen resizing, the more tab, keeps the original Tab Bar on iPhones and older iOS, adjusts layout margins for child view controllers, and can still compile under Xcode 15.

    Basically it just hides the new tab bar and places the old style back, copying the tab bar items and selected item across.

    
    
    class CustomTabBarController: UITabBarController {
    
        /// Active for iPads running iOS 18+ where the traditional tab bar has been removed by Apple
        lazy var alternateTabBarActive: Bool = {
        #if compiler(>=6.0) // Compiler flag for Xcode >= 16
            if #available(iOS 18.0, *), UIDevice.current.userInterfaceIdiom == .pad {
                self.isTabBarHidden = true
                return true
            }
        #endif
            return false
        }()
        
        var tabBarHeightConstraint: NSLayoutConstraint?
        
        lazy var alternateTabBar: UITabBar = {
            UITabBar()
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            if self.alternateTabBarActive {
                self.tabBar.isHidden = true
                
                self.alternateTabBar.items = self.tabBar.items
                self.alternateTabBar.selectedItem = self.tabBar.selectedItem
                
                if UIDevice.current.userInterfaceIdiom == .pad {
                    // Add Custom Tabbar
                    let tabbar = self.alternateTabBar
                    self.view.addSubview(tabbar)
                    
                    // Add layout constraints
                    tabbar.translatesAutoresizingMaskIntoConstraints = false
                    let bottom = tabbar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
                    let leading = tabbar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor)
                    let trailing = tabbar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor)
                    let height = NSLayoutConstraint(item: self.alternateTabBar, attribute: .height, relatedBy: .equal,
                                                    toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 1)
                    self.tabBarHeightConstraint = height
                    self.view.addConstraints([bottom, leading, trailing, height])
                }
            }
        }
    
        override func viewWillLayoutSubviews() {
            super.viewWillLayoutSubviews()
            
            if self.alternateTabBarActive {
                self.alternateTabBar.items = self.tabBar.items
                self.alternateTabBar.selectedItem = self.tabBar.selectedItem
            }
        }
        
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            
            if self.alternateTabBarActive {
                // Adjust height constraint
                let height = self.alternateTabBar.intrinsicContentSize.height
                self.tabBarHeightConstraint?.constant = height
                
                // Set insets for child view controllers
                let bottomInset = self.alternateTabBar.frame.size.height-self.view.safeAreaInsets.bottom
                self.viewControllers?.forEach { $0.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: bottomInset, right: 0) }
            }
        }
    
    }