Creating WhatsApp-like animations in iOS
How to create a fluid and consistent UITabBar and UIToolBar animation like in WhatsApp for your app.
--
Since I started developing iOS apps, I have been paying more attention to how other extremely successful apps implement UI transitions so that I can implement them in my own ones.
Recently I was developing an app which had a UICollectionView
embedded in a UIViewController
, which in turn was embedded in a UICollectionViewController
, and I wanted to be able to hide the tab bar at the bottom of the screen to show a tool bar when in edit mode.
One of the apps I use the most is WhatsApp, but until now I had not tried to dissect how it manages its UI transitions. I noticed that the way it handles the transition and position of the UIToolBar
at the bottom of the screen when in edit mode fit perfectly what I wanted to implement.
In this article, I will show you how I implemented it in my app and explain my design decisions. It turns out that doing the above is not trivial and took me a while to figure out. I had to write custom code and too much trial and error. So I hope that this article and the sample project makes your life easier if you want to implement the same.
Analysis
First, we should check how WhatsApp does things:
From what I can tell, WhatsApp fades the UITabBar
in or out and at the same time moves the UIToolBar
up or down depending on the editing mode.
Hence our plan of action is to do exactly the above.
Planning
These are our requirements:
- We should allow the
UINavigationBar
to use iOS 11’s large titles - We should hide the
UIToolBar
when it’s not being used - We should do the same for the
UITabBar
Attack
This is where I was completely wrong. I thought that implementing what we discussed above would have been a piece of cake, but it turned out to be extremely convoluted.
Why?
It turns out that using the default animation and system for large titles screws up any simple positioning code your might write for your UIToolBar
.
The naive approach
The first thing I tried was to use the UINavigationController
’s default UIToolBar
and change its position according to the editing state of the app.
However, as I said above, that doesn’t really work if you are using large titles.
Moreover, I discovered that I needed to manually resize the bounds property of the collection view if I wanted it to cover the whole space between the moved UIToolBar
and the UITabBar
.
The solution
In order to circumvent that, we need to add a UIToolBar
manually rather than using the default one provided by the UINavigationController
object associated with our view controller.
First we create it lazily:
private lazy var toolBar: UIToolbar = {
return UIToolbar()
}()
and then set up its constraints in viewDidLoad:
private var toolBarConstraints = [NSLayoutConstraint]()...
override func viewDidLoad() {
super.viewDidLoad()
...
navBar.prefersLargeTitles = true
navigationController!.isToolbarHidden = true
view.addSubview(toolBar)
toolBar.translatesAutoresizingMaskIntoConstraints = false
toolBarConstraints.append(contentsOf: [
toolBar.leftAnchor.constraint(equalTo: self.view.leftAnchor),
toolBar.rightAnchor.constraint(equalTo: self.view.rightAnchor),
toolBar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
toolBar.topAnchor.constraint(equalTo: tabBar.topAnchor)
])
}
We then create some computed properties which we use throughout the sample view controller to setup the custom UIToolBar
correctly:
private var screenHeight: CGFloat {
return UIScreen.main.bounds.height
}
private var toolBarYPos: CGFloat {
if currentState == .normal {
return screenHeight
}
// Ideally check for other states here, but since we only have two, keep it
// simple; would do something like: else if currentState == .edit {
else {
return screenHeight - toolBar.frame.height
}
}
Finally, we use these computed properties to position the UIToolBar
and/or the UITabBar
correctly in cases. So when the user taps the Edit or Done buttons:
and this is achieved (partially, but mostly) with this code:
@IBAction func onEditBtnTouchUp(_ sender: Any) {
// Switch to the editing mode
if currentState == .normal {
currentState = .edit
fade(tabBar, toAlpha: 0, withDuration: 0.2, andHide: true)
UIView.animate(withDuration: 0.2, animations: {
// Set edit to done
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self,
action: #selector(self.onDoneBtnTouchUp))
// Fade away + btn
self.plusBtn.isEnabled = false
self.plusBtn.tintColor = UIColor.clear
// Position the toolbar
self.toolBar.frame.origin.y = self.toolBarYPos
})
}
}
@objc func onDoneBtnTouchUp(_ sender: Any) {
// Switch to normal state
if currentState == .edit {
currentState = .normal
fade(tabBar, toAlpha: 1, withDuration: 0.2, andHide: false)
UIView.animate(withDuration: 0.2, animations: {
// Set edit to done
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self,
action: #selector(self.onEditBtnTouchUp))
// Fade in + btn
self.plusBtn.isEnabled = true
self.plusBtn.tintColor = nil
// Position the toolbar
self.toolBar.frame.origin.y = self.toolBarYPos
})
}
Conclusions
The final result should look something like this:
I have setup a repository with the sample project so that you can go and read the full code:
I recommend checking it out, since I deliberately missed some details in the article for the sake of brevity.
It took me a long time to figure this out, because the interplay between the different UIKit
elements did not work as I expected.
The main takeaways when developing more advanced UI interactions in Swift on iOS are:
- Try the easiest solution first; if that works, cool!
- If not, keep on trying new ones and experiment.
- It can be very frustrating, but don’t give up!
If you like this kind of stuff, you should follow me on Twitter: twitter.com/albtaiuti
The style of this post was partially inspired by Nathan Gitter’s Medium posts, and I also used some of his code. Thanks for sharing it with us Nathan!
Follow us on social media platforms:
Facebook: facebook.com/AppCodamobile/
Twitter: twitter.com/AppCodaMobile
Instagram: instagram.com/AppCodadotcom