Search code examples
iosswiftipaduinavigationcontrolleruinavigationbar

Chevron in UINavigationBar not dimmed when showing popover


When I'm showing a popover, I expect all views outside the popover to be dimmed. When I create a popover via IB, this works fine. When I create a popover programmatically and call it via an UIBarButtonItem, this doesn't quite work: the back chevron in the navigationbar is not dimmed. Instead, it remains blue:

enter image description here

Code:

class GreenViewController: UIViewController {

    private var barButtonItem: UIBarButtonItem!

    func barButtonItemAction() {
        let blueViewController = BlueViewController()
        let navigationController = UINavigationController(rootViewController: blueViewController)
        navigationController.modalPresentationStyle = .popover
        navigationController.popoverPresentationController?.barButtonItem = self.barButtonItem
        self.present(navigationController, animated: true, completion: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.barButtonItem = UIBarButtonItem(title: "Show blue popover", style: .plain, target: self, action: #selector(barButtonItemAction))
        self.navigationItem.rightBarButtonItem = barButtonItem
    }

}

Why does this happen?

Test project on Github: https://github.com/bvankuik/TestNavigationBarChevronTint/


Solution

  • I think something may be off in the view hierarchy when the popovercontroller uses the UIBarButtonItem as it's anchor. In InterfaceBuilder, the UIButton is the anchor for the presented popover, and since the UIButton is in the view hierarchy of the presenting view controller, seems to just work.

    So I attempted to reproduce some similar conditions by setting the sourceRect and sourceView properties on the popoverPresentationController as follows and it did the trick.

    class GreenViewController: UIViewController, UIPopoverPresentationControllerDelegate {
    
        private var barButtonItem: UIBarButtonItem!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            barButtonItem = UIBarButtonItem(title: "Show blue popover", style: .plain,
                                            target: self,  action: #selector(barButtonItemAction))
            navigationItem.rightBarButtonItem = barButtonItem
        }
    
    
        // Defined constants for solution readability
    
        private let sourceRectHeight         : CGFloat = 44.0   // NavigationBar Height?
        private let sourceRectWidth          : CGFloat = 160.0  // UIBarButtonItem Width?
        private let sourceRectRightMargin    : CGFloat = 20.0   // Right Margin
    
    
        // This returns the source rect to align our popoverPresentationController
        // against, this is pretty much my imaginary frame of the UIBarButtonItem
    
        private var sourceRect : CGRect
        {
            var rect = navigationController!.navigationBar.frame
            rect.origin.x = view.bounds.width - sourceRectWidth - sourceRectRightMargin
            rect.origin.y = sourceRectHeight / 2.0
            rect.size.width = sourceRectWidth
            return rect
        }
    
        func barButtonItemAction() {
    
            let blueViewController = BlueViewController()
            let navigationController = UINavigationController(rootViewController: blueViewController)
            navigationController.modalPresentationStyle = .popover
    
            // Instead of setting the barButtonItem on the popoverPresentationController
            // set the srouce view as the root view of the presenting controller
    
            navigationController.popoverPresentationController?.sourceView = view
    
            // Set the source rec to present from, which is calclated relative to the width
            // of the current device orientation
    
            navigationController.popoverPresentationController?.sourceRect = sourceRect
    
            // Set self as the delegate for the popoverPresentationController because
            // we need to provide a relaculated rect when the device changes orientation
    
            navigationController.popoverPresentationController?.delegate = self
    
            // Present the view controller, and voila :)
    
            self.present(navigationController, animated: true, completion: nil)
        }
    
    
        // UIPopoverPresentationControllerDelegate method that allows us to update 
        // the source rect of the popover after an orientation change has occurred,
        // which calculated relative to with in the sourceRect property above
    
        public func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController,
                                                  willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>,
                                                  in view: AutoreleasingUnsafeMutablePointer<UIView>)
        {
            rect.initialize(to: sourceRect)
        }
    }
    

    Hope this helps :)