Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom UISearchController Animation

The Bottom Line Question:

How do I override the default animation for the dismissal of a searchBar belonging to a UISearchController?


Standard Search Controller Behavior:

Okay so I am trying to create a custom animation for when the UISearchBar that is attached to a UISearchController becomes active. It seems the standard animation expects for the searchBar to begin with a width that takes up the screen. When the animation begins it shrinks the searchBar and fades in a cancel button to the right of it.

What I want to Achieve:

I want to have my searchBar begin in a small state, roughly half the screen width, to allow for two buttons to be placed in the navbar next to it as well.

Present Animation:

When the searchBar becomes active I want the animation to expand the searchBar and for the cancel button to fade in.

Dismissing Animation:

When the searchBar is dismissed I want the exact opposite animation to occur: Cancel button fade out and searchBar to shrink to it's original size.


The Problem:

I have found a way to achieve the desired presenting animation by using a UISearchControllerDelegate method, presentSearchController:

func presentSearchController(searchController: UISearchController) {

    // Animate Buttons
    UIView.animateWithDuration(0.1, animations: {

        // First Hide Buttons
        self.createMoxyButton.alpha = 0
        self.centerMapButton.alpha  = 0
    })

    // Animate Search Bar
    UIView.animateWithDuration(0.5, animations: {

        // Search Bar
        searchController.searchBar.frame = CGRectMake(searchController.searchBar.frame.origin.x, searchController.searchBar.frame.origin.y, self.wBounds - 40, searchController.searchBar.frame.height)
        self

    })

}

but I have not been able to achieve the dismissal animation. I have tried using the didDismissSearchController: and willDismissSearchController: delegate methods but it results in weird behavior and does not use the animation of frames that I set in these respective delegate methods. When the searchBar is dismissed it will expand to the full screen width, while fading out cancel button, then it will immediately change the frame of the searchBar back to the original size, ignoring my animation. I have also tried using the removeAllAnimation() method to try and stop the default animation from taking place, but to no avail.

func didDismissSearchController(searchController: UISearchController) {
    searchController.searchBar.layer.removeAllAnimations()

    // Animate
    UIView.animateWithDuration(0.5, animations: {

        // Show hidden buttons
        self.createMoxyButton.alpha = 1
        self.centerMapButton.alpha  = 1

        // Search Bar
        searchController.searchBar.frame = CGRectMake(searchController.searchBar.frame.origin.x, searchController.searchBar.frame.origin.y, self.wBounds - 10 - self.createMoxyButton.frame.size.width - 20 - self.centerMapButton.frame.size.width - 20, searchController.searchBar.frame.height)
        self

    })
}

Image of Problem Dismissing SearchBar

Gif Animation begins with the searchBar in the Active state with the cancel button visible

Gif Animation begins with the searchBar in the Active state with the cancel button visible

like image 768
heading_to_tahiti Avatar asked Feb 01 '15 19:02

heading_to_tahiti


2 Answers

Subclass UISearchController and implement the optional UIViewControllerTransitioningDelegate method - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;

to return your animation object to do the animation for the dismiss.

like image 148
Dad Avatar answered Nov 07 '22 16:11

Dad


I cannot claim that this produces super smooth animation, but it doesn't look terrible and might be helpful (or even exactly what you need). If you want to try it out, you can create a new project (single view application) and just add navigation controller as the initial one and object of class ViewController as its root controller (just to quickly get the navigation bar). Code below is my whole implementation of ViewController

import UIKit

class ViewController: UIViewController,UISearchBarDelegate,UISearchControllerDelegate,UISearchResultsUpdating {

    lazy var createMoxyButton:UIBarButtonItem = {
        //customize this as your equire
        let button = UIBarButtonItem(title: "Done", style: .Plain, target: nil, action: nil)
        return button
    }()

    lazy var centerMapButton:UIBarButtonItem = {
        //customize this as your equire
        let button = UIBarButtonItem(title: "Done", style: .Plain, target: nil, action: nil)
        return button
        }()

    lazy var searchController:UISearchController = {
        let controller = UISearchController(searchResultsController: self)
        controller.delegate = self
        controller.searchResultsUpdater = self
        controller.dimsBackgroundDuringPresentation = false
        controller.hidesNavigationBarDuringPresentation = false
        return controller
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.navigationItem.titleView = self.searchController.searchBar
        self.navigationItem.rightBarButtonItems = [self.centerMapButton,self.createMoxyButton]
        self.edgesForExtendedLayout = UIRectEdge.None
        self.searchController.searchBar.delegate = self
    }

    func updateSearchResultsForSearchController(searchController: UISearchController) {

    }

    func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
        //this needs to be done because in shouldEndEditing
        //we need to set it to false to smooth animation
        searchBar.showsCancelButton = true
        self.navigationItem.setRightBarButtonItems(nil, animated: true)
    }

    func searchBarShouldEndEditing(searchBar: UISearchBar) -> Bool {
        searchBar.showsCancelButton = false
        self.navigationItem.rightBarButtonItems = [self.centerMapButton,self.createMoxyButton]
        return true
    }
}
like image 23
Andriy Gordiychuk Avatar answered Nov 07 '22 16:11

Andriy Gordiychuk