Search code examples
swiftuinavigationcontrolleruinavigationbaruinavigationitemcgpoint

Get position of navigation bar item button in Swift


I want to get position(point) of my navigation item which is a button. If I can get this point I will be able to add a guide circle on my app. But I can't get this point right.

let point = searchBarButton.center

or

let point = view.convert(CGPoint(x: searchBarButton.center.x, y: searchBarButton.center.y + UIApplication.shared.statusBarFrame.height), from: view)

y point was calculated correctly when I added the status bar height. But x point always has the value as 20 and it doesn't make sense. So how do I get the x point correctly?

Here's my code and a screenshot of my app.

class ViewController: UIViewController {

    let searchBarButton = UIButton()

    // MARK: - viewDidLoad
    override func viewDidLoad() {
        super.viewDidLoad()

        configureNavigationBarItems()

        let added_view: UIView = navigationController?.view ?? view
        let point = added_view.convert(CGPoint(x: searchBarButton.center.x, y: searchBarButton.center.y + UIApplication.shared.statusBarFrame.height), from: added_view)
        addCircle(point: point)

    }

    func configureNavigationBarItems() {

        searchBarButton.setImage(UIImage(named: "icon_search_border"), for: .normal)
        searchBarButton.frame = CGRect.init(x: 0, y: 0, width: 40, height: 40)
        let searchBarButtonItem = UIBarButtonItem.init(customView: searchBarButton)

        self.navigationItem.setRightBarButton(searchBarButtonItem, animated: true)

    }

    func addCircle(point: CGPoint) {

        let overlayView = UIView(frame: self.view.bounds)
        overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        self.navigationController?.view.addSubview(overlayView)

        let circlePath = UIBezierPath(arcCenter: point,
                                      radius: 30,
                                      startAngle: 0.0,
                                      endAngle: 2.0 * .pi,
                                      clockwise: true)

        let path = CGMutablePath()
        path.addPath(circlePath.cgPath)
        path.addRect(overlayView.frame)

        let maskLayer = CAShapeLayer()
        maskLayer.path = path
        maskLayer.fillRule = .evenOdd

        overlayView.layer.mask = maskLayer
        overlayView.layer.masksToBounds = true

    }

}

screenshot


Solution

  • When you want to access to nav bar items you should access them in viewDidAppear and thats the answer of my question. It's inspired from @jawadAli's answer.

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    
        if let navigationBarSubviews = self.navigationController?.navigationBar.subviews {
            for view in navigationBarSubviews {
    
                if let findClass = NSClassFromString("_UINavigationBarContentView"),
                    view.isKind(of: findClass),
                    let barButtonView = self.navigationItem.rightBarButtonItem?.value(forKey: "view") as? UIView {
    
                    let point = barButtonView.convert(barButtonView.center, to: view)
                    addCircle(point: CGPoint(x: point.x, y: point.y + UIApplication.shared.statusBarFrame.height))
                }
            }
        }
    
    }
    

    enter image description here