Issue #144

There are times we want the same UIViewController to look good when it’s presented modally or pushed from UINavigationController stack. Take a look at BarcodeScanner and the PR https://github.com/hyperoslo/BarcodeScanner/pull/82

When it is presented, we need a header view so that we can show a title and a close button. We can create a custom HeaderView that inherits from UIView or either embed it in a UINavigationController before presenting.

If we go with the controller being embedded in UINavigationController approach, it will collide with the other UINavigationController when it is pushed.

If we go with custom HeaderView, then we need to layout the view so that it looks good on both portrait and landscape, and on iPhone X that as safeAreaLayoutGuide.

Using standalone UINavigationBar

Since UINavigationController uses UINavigationBar under the hood, which uses UINavigationItem info to present the content. We can imitate this behavior by using a standalone UINavigationBar. See Adding Content to a Standalone Navigation Bar

In the vast majority of scenarios you will use a navigation bar as part of a navigation controller. However, there are situations for which you might want to use the navigation bar UI and implement your own approach to content navigation. In these situations, you can use a standalone navigation bar.

A navigation bar manages a stack of UINavigationItem objects

The beauty is that our standalone UINavigationBar and that of UINavigationController are the same, use the same UINavigationItem and no manual layout are needed

Declare UINavigationItem

We can just set properties like we did with a normal navigationItem

let standaloneItem = UINavigationItem()
standaloneItem.leftBarButtonItem = UIBarButtonItem(customView: closeButton)
standaloneItem.titleView = UILabel()

Adding UINavigationBar

Customise your bar, then declare layout constraints. You only need to pin left, right, and top. Note that you need to implement UINavigationBarDelegate to attach bar to status bar, so that it appears good on iPhone X too

let navigationBar = UINavigationBar()
navigationBar.isTranslucent = false
navigationBar.delegate = self
navigationBar.backgroundColor = .white
navigationBar.items = [standaloneItem]
 
navigationBar.translatesAutoresizingMaskIntoConstraints = false
navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
 
if #available(iOS 11, *) {
  navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
} else {
  navigationBar.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
extension BarcodeScannerController: UINavigationBarDelegate {
  public func position(for bar: UIBarPositioning) -> UIBarPosition {
    return .topAttached
  }
}

Inside UINavigationController

When this UIViewController is pushed from a UINavigationController stack, we just need to hide our standalone navigationBar. If we prefer the default back button, we don’t need to set leftBarButtonItem

On iOS 10, you need to call sizeToFit for any items in UINavigationItem for it to get actual size

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
  if navigationController != nil {
      let label = HeaderElement.makeLabel()
      label.sizeToFit()
      navigationItem.titleView = label
    navigationBar.isHidden = true
  } else {
    navigationBar.isHidden = false
  }
}