Bart van Kuik
Bart van Kuik

Reputation: 4862

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/

Upvotes: 0

Views: 194

Answers (1)

AntonTheDev
AntonTheDev

Reputation: 919

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 :)

Upvotes: 1

Related Questions