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
}
}