palme
palme

Reputation: 2629

Make NavigationBar's titleView larger than itself

I want to place an image in the middle of a navigation bar that is bigger then the bar itself. So far I tried to use a UIView with a UIImageView inside and it works quite well as you can see here:

enter image description here

However as soon as I push another controller and pop back my ImageView gets cropped to the size of the NavigationBar again.

enter image description here Any ideas on how to prevent the cropping?

My code so far for iOS 11:

override func viewDidLoad() {
    super.viewDidLoad()

    let logo = UIImage(named: "Logo")
    let titleView = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
    let imageView = UIImageView(image: logo)
    imageView.frame = CGRect(x: 0, y: 0, width: titleView.frame.width, height: titleView.frame.height)
    titleView.addSubview(imageView)
    imageView.contentMode = .scaleAspectFit
    imageView.image = logo
    navigationItem.titleView = titleView
}

Edit: Currently there is a temporary solution which uses an observer to overwrite the clipsToBounds property of the view that causes the trouble: Link (shout out to @trungduc for that)

Upvotes: 8

Views: 3056

Answers (3)

trungduc
trungduc

Reputation: 12154

I found why you got this issue. It's because of a private view which has name _UINavigationBarContentView. It's a subview of UINavigationBar. navigationItem.titleView is contained in this view.

enter image description here

At first time, when you change navigationItem.titleView. _UINavigationBarContentView.clipsToBounds is false .But after you push another controller and pop back, _UINavigationBarContentView.clipsToBounds is true. That's why titleView is cropped.

So i have a temporary solution. Each time when viewController appears, find this view and change _UINavigationBarContentView.clipsToBounds to false and layout titleView.

override func viewDidAppear(_ animated: Bool) {
    for view : UIView in (navigationController?.navigationBar.subviews)! {
      view.clipsToBounds = false;
    }
    navigationItem.titleView?.layoutIfNeeded()
  }

  override func viewWillAppear(_ animated: Bool) {
    for view : UIView in (navigationController?.navigationBar.subviews)! {
      view.clipsToBounds = false;
    }
    navigationItem.titleView?.layoutIfNeeded()
  }

I tried and it works. But i think you shouldn't do something whit it because it's private view. Maybe Apple don't want us do anything with it.

Hope somehow my suggestion can help you. Good luck ;)

SOLUTION

Adding observer for _UINavigationBarContentView.clipsToBounds, each time when it changes to false, set to true and update layout of titleView

override func viewDidLoad() {
  super.viewDidLoad()
  // Do any additional setup after loading the view, typically from a nib.

  let logo = UIImage(named: "Logo")
  let titleView = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
  let imageView = UIImageView(image: logo)
  imageView.frame = CGRect(x: 0, y: 0, width: titleView.frame.width, height: titleView.frame.height)
  titleView.addSubview(imageView)
  imageView.contentMode = .scaleAspectFit
  imageView.image = logo
  navigationItem.titleView = titleView
  navigationController?.navigationBar.subviews[2].addObserver(self, forKeyPath: "clipsToBounds", options: [.old, .new], context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  if  (navigationController?.navigationBar.subviews[2].isEqual(object))! {
    DispatchQueue.main.async {
      self.navigationController?.navigationBar.subviews[2].clipsToBounds = false
      self.navigationItem.titleView?.layoutIfNeeded()
    }
  }
}

deinit {
  navigationController?.navigationBar.subviews[2].removeObserver(self, forKeyPath: "clipsToBounds")
}

For more detail and easier, you can check my demo here https://github.com/trungducc/stackoverflow/tree/big-title-navigation-bar

Upvotes: 6

facumenzella
facumenzella

Reputation: 559

The UINavigationBar has UIBarMetrics if you want a custom height but it's not fully interactive.

There's a bunch of code suggested by apple to actually develop nice things like iMessage app.

Upvotes: 0

Sebastian
Sebastian

Reputation: 36

You could try to put the code in the viewWillAppear method. This way you will add the image to the bar everytime the view appears. However you should then remove the inageview within the viewDidDissappear method. If you need it in several views you could subclass the UIViewController and use this one.

Upvotes: 0

Related Questions