Loading

Animated search box

Not sure why I did this one. I think it was just a cool effect and I got carried away making it work.

The scenario is that I have a table view and I want to be able to search it. But I don't want the search on the screen all the time so I need a way to make it appear and disappear. In true iOS style, you can't just make something appear and disappear, it has to be animated on and off the screen:

Animate search box into view

It takes a bit of set-up and a bit of code to make this work. So, starting in Interface Builder, I have a scene with a search box fixed to the top of the scene and of the desired fixed height:

Interface Builder - Search Box

Now, it's a bit difficult to see the search box because I've put the table view over the top of it. This is the initial state I want, the table view over the search. If you select the search box, you can see the constraints…

Search box constraints

The three search box constraints anchor the search box rot the top of the view. Combined with a fixed height of 64 points, this gives me the basic search functionality. There is a fourth constraint, which is the constraint between the top of the search box and the top of the tableview.

Search/table top constraints

First thing to note, I have given this constraint an identifier. This is needed because I am going to be accessing this constraint and modifying it from within code.

Secondly, the constraint itself is from the top of the table to the top of the search box. This is set to 0, so the two tops will be the same, placing the table firmly over the search box.

The magic

The magic happens when you tap the search icon.

    @IBAction func searchBarToggle(_ sender: Any) {

        let filteredConstraints = self.view.constraints.filter { $0.identifier == "TableToSearchAreaTop" }
        if let tableTopConstraint = filteredConstraints.first {
            let gap = tableTopConstraint.constant

            if gap == 0 {
                // Show the search box
                tableTopConstraint.constant = searchBar.bounds.height
                searchBar.becomeFirstResponder()

            } else {
                // Hide the search box and turn off searching.
                tableTopConstraint.constant = 0

                clearSearch()
            }

            UIView.animate(withDuration: 0.4,
            animations: {
                self.view.layoutIfNeeded()
            },
            completion: { (_) in
                if !self.filterByName {
                    self.refreshFilters()
                }
            })
        }
    }

First things first, we are going to be modifying that constraint, so we filter the current view constraints by the identifier we gave our top constraint.

Once we have the constraint, we retrieve it's current value. This is going to be a toggle where 0 indicates that the search box isn't visible and some other value when it is. So we retrieve the constraint constant value. If it is 0, then we want to display the search box. To do that, we set the constant to the height of the search bar and set the search bar as the first responder to get the keyboard animated into view.

If the constant was non-zero, then the search bar is already displayed and we need to close it. That's as simple as setting the constant to 0.

At this point, if we just exit, the search bar will just suddenly appear or disappear. To get the animation, we need to tell the UI that we want to animate the changes we made to the constraints. That's the purpose of the UIView.animate function. The call is passed the duration of the animation (0.4 seconds) and a closure that defines the animation, then a closure to call when the animation completes.

The bummer of all this is that you cannot animate a constraint change within the animations closure in the same way that you would any other animation. Turns out this isn't a problem to iOS and we just have to tell the screen to lay itself out and it figures out the constraint change. So layoutIfNeeded causes the new constraint to come into force over the 0.4 second period.

Job done.