Extending UINavigationBar with interactable UIView below it
In recent years, we have seen some great examples of extending the navigation bar to add helpful functionality below it in apps such as Telegram, GitHub, and others.
Here at Shareup, we strive to make sharing easy and convenient…and private. To ensure the privacy of the things you share, we encrypt everything locally on your device. Depending on how large the files you share are, this can take a few seconds. So, we thought adding an encryption progress bar under the navigation bar would be a wonderful way to show our users how far along the encryption process is after sharing files.
In this simple example, we will tell you how we did this by adding a similar view to an app that prepares tea☕️.
We want to attach our custom UIView
to the UINavigationBar
to be present on every UIViewController
pushed to the navigation stack. So, the first step is straightforward. Add the following code to your root view controller to attach the progress view to your navigation controller’s navigation bar.
override func viewDidLoad() {
super.viewDidLoad()
if let navigationBar = navigationController?.navigationBar {
navigationBar.addSubview(progressBarView)
progressBarView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
progressBarView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor),
progressBarView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor),
progressBarView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor),
progressBarView.heightAnchor.constraint(equalToConstant: 36),
])
}
}
And we would rather hide it initially.
progressBarView.isHidden = true
Since we have this fabulous tea-looking button, let’s add a simple action to show our tea preparation progress bar.
makeTeaButton.addAction(
UIAction(handler: { [weak self] _ in
self?.progressBarView.isHidden = false
}),
for: .touchUpInside
)
You can tell that something is not right so far. Since our progress bar overlaps the content of the UIViewController
. Furthermore, you will notice that it not only overlaps the content of the UIViewController
, but also does not accept touches, and you can easily select the row underneath it.
Let’s now fix the overlapping issue. To do that, we need to increase additional safe area insets in every UIViewController
affected by this problem.
makeTeaButton.addAction(
UIAction(handler: { [weak self] _ in
self?.progressBarView.isHidden = false
self?.additionalSafeAreaInsets.top = 36 // This the height of this particular view, yours might differ
}),
for: .touchUpInside
)
Remember to set insets to zero once you finish displaying your custom view.
self?.additionalSafeAreaInsets = .zero
The last issue to address is handling gestures. Despite looking solid, our custom view does not receive any events. This is obvious since it is located outside the frame of the UINavigationBar
.
Our solution will be easy to understand if you have heard about hit testing. Otherwise, look at it as follows: we force all of the subviews of the UINavigationBar
to check whether the gesture took place inside its frame so that we won’t miss any of it.
public extension UINavigationBar {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews where (!subview.isHidden && subview.alpha >= 0.01) {
let convertedPoint = subview.convert(point, from: self)
if subview.point(inside: convertedPoint, with: event) { return true }
}
return false
}
}
If you want to play with the code more, you can find the complete project here.
If you want to be among the first people to try Shareup - sign up for closed alpha by clicking the button below.
🚀🆙