Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make NavigationBar's titleView larger than itself

Tags:

ios

uikit

swift

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)

like image 930
palme Avatar asked Nov 05 '17 12:11

palme


1 Answers

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

like image 165
trungduc Avatar answered Nov 10 '22 01:11

trungduc