How to prevent search bar from disappearing on scroll? iOS Swift

12,888

Problem is view hierarchy.

Your storyboard is this:

enter image description here

Search Bar is added on TableView.

Correct hierarchy is this:

enter image description here

You change UITableViewController to UIViewController, and appending SearchDisplayController while being careful about view hierarcy.

It will work :)

Share:
12,888
MasterProgrammer200
Author by

MasterProgrammer200

Application Developer at Do it Best Corp.

Updated on August 21, 2022

Comments

  • MasterProgrammer200
    MasterProgrammer200 over 1 year

    I am creating a contact app. I have a scroll bar at the top of my table view.

    You see a search bar

    When I scroll down the search bar disapears.

    Now you dont

    How to prevent search bar from disappearing on scroll? I want it to stay at the top of the page at all times like in the first picture.

    Here is a picture of my storyboard:

    enter image description here

    Here is my view controller code for if the solution is not in storyboard:

    class ViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating {
    
        //manages search bar
        var searchController:UISearchController!
    
        var contacts = [Contact]()
    
        //array to hold contacts that match the search results
        var filteredContacts = [Contact]()
    
        override func viewDidLoad() {
    
            super.viewDidLoad()
    
            //initialize the defaults manager class
            NSUserDefaultsManager.initializeDefaults()
    
            //search controller
            searchController = UISearchController(searchResultsController: nil)
            searchController.searchBar.sizeToFit()
            tableView.tableHeaderView = searchController.searchBar
            definesPresentationContext = true
    
            searchController.searchResultsUpdater = self
            searchController.dimsBackgroundDuringPresentation = false
    
            //load the contacts
            title = "Contacts"
    
            tableView.backgroundView = UIImageView(image: UIImage(named: "valblur15"))
    
            contacts = [Contact]()
            let api = ContactAPI()
            api.loadContacts(didLoadContacts)
    
        }
    
        override func viewDidAppear(animated: Bool) {
            super.viewDidAppear(animated)
    
            //reload the table view to check for new contacts
            tableView.reloadData()
    
            //set the color of the nav bar to valbruna yellow
            navigationController?.navigationBar.backgroundColor = UIColor(red: 254.0/255.0, green: 196.0/255.0, blue: 37.0/255.0, alpha: 0.95)
    
        }
    
        //MARK: -Helper Methods
    
        // Uupdate searching results
        func updateSearchResultsForSearchController(searchController: UISearchController) {
    
            let searchText = searchController.searchBar.text
            filterContentForSearchText(searchText)
            tableView.reloadData()
    
        }
    
        func filterList() { // should probably be called sort and not filter
    
            //sort contacts from a-z
            contacts.sort() { $0.name < $1.name }
    
            //remove contacts whose locations are nil
            contacts = contacts.filter() { $0.location != "nil"}
    
            //remove contacts whose titles phone email and department are all nil
            contacts = contacts.filter() {
                if($0.title != "" || $0.phone != "" || $0.email != "" || $0.department != ""){
                    return true
                }
                return false
            }
    
    
            tableView.reloadData()
        }
    
        func didLoadContacts(contacts: [Contact]){
            self.contacts = contacts
            filterList()
            tableView.reloadData()
        }
    
        //MARK: -Table View
    
        //set number opf sections in table view
        override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
            return 1
        }
    
        //delegeate that tells tabel view how many cells to have
        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            //return size of array
    
            //if searching show count of filtered contacts
            if (searchController.active){
    
                return self.filteredContacts.count
    
            }else{
    
                return self.contacts.count
    
            }
    
        }
    
        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
            let cell = self.tableView.dequeueReusableCellWithIdentifier("customcell") as! CustomCell //from the customcell class
    
            //change color of cell text label
            cell.textLabel?.textColor = UIColor.blackColor()
            cell.backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 0.8)
    
            let contact : Contact
    
            //if users is searching then return filtered contacts
            if (searchController.active){
    
                contact = self.filteredContacts[indexPath.row]
    
            }else{
    
                contact = self.contacts[indexPath.row]
    
            }
    
    
            //handel assignment of text
            cell.textLabel?.text = contact.name
    
            //retrieve from customcell class
            cell.contact = contact
    
            return cell
        }
    
    
        //MARK: -Search
    
        func filterContentForSearchText(searchText: String, scope: String = "Title")
        {
            self.filteredContacts = self.contacts.filter({( contact: Contact) -> Bool in
    
                //filters contacts array
    
                var categoryMatch = (scope == "Title")
                var stringMatch = contact.name?.rangeOfString(searchText)
    
                return categoryMatch && (stringMatch != nil)
    
            })
        }
    
        func searchDisplayController(controller: UISearchController, shouldReloadTableForSearchString searchString: String!) -> Bool {
    
            self.filterContentForSearchText(searchString, scope: "Title")
    
            return true
    
        }
    
    
        func searchDisplayController(controller: UISearchController, shouldReloadTableForSearchScope searchOption: Int) -> Bool {
    
            self.filterContentForSearchText(self.searchController!.searchBar.text, scope: "Title")
    
            return true
    
        }
    
        //MARK: -Segue
    
        override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    
            if(segue.identifier == "detailview"){
                let cell = sender as! CustomCell
                let detailView = segue.destinationViewController as! DetailViewController
                detailView.preContact = cell.contact
    
            }
        }
    
    }
    

    EDIT 1:

    Here are the steps I have taken based off Ryosuke Hiramatsu's solution:

    1. Command X the table view.
    2. Add a view.
    3. Command V the table view into the new view.
    4. In the view controller, change UITableViewController to UITableView
    5. Add an outlet to the view controller named table view.
    6. Remove the overrides in the table view functions.
    7. Remove this line of code: tableView.tableHeaderView = searchController.searchBar
    8. Go back to storyboard and move the search bar out of the table view. It'll appear behind the table view.
    9. Move the table view down and the search bar up.
    10. Add necessary constraints.

    My View now looks like this:

    2 Search Bars

    When I scroll down:

    1 Search Bar

    My Storyboard:

    Storyboard edit 1

    And finally My updated view controller code:

    import UIKit
    
    class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating {
    
    
    @IBOutlet var tableView: UITableView!
    
    
    //manages search bar
    var searchController:UISearchController!
    
    var contacts = [Contact]()
    
    //array to hold contacts that match the search results
    var filteredContacts = [Contact]()
    
    override func viewDidLoad() {
    
        super.viewDidLoad()
    
        //initialize the defaults manager class
        NSUserDefaultsManager.initializeDefaults()
    
        //search controller
        searchController = UISearchController(searchResultsController: nil)
        searchController.searchBar.sizeToFit()
        definesPresentationContext = true
    
        tableView.tableHeaderView = searchController.searchBar
    
        searchController.searchResultsUpdater = self
        searchController.dimsBackgroundDuringPresentation = false
    
        //load the contacts
        title = "Valbruna Contacts"
    
        tableView.backgroundView = UIImageView(image: UIImage(named: "valblur15"))
    
        contacts = [Contact]()
        let api = ContactAPI()
        api.loadContacts(didLoadContacts)
    
    }
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
    
        //reload the table view to check for new contacts
        tableView.reloadData()
    
        //set the color of the nav bar to valbruna yellow
        navigationController?.navigationBar.backgroundColor = UIColor(red: 254.0/255.0, green: 196.0/255.0, blue: 37.0/255.0, alpha: 0.95)
    
    }
    
    //MARK: -Helper Methods
    
    // Uupdate searching results
    func updateSearchResultsForSearchController(searchController: UISearchController) {
    
        let searchText = searchController.searchBar.text
        filterContentForSearchText(searchText)
        tableView.reloadData()
    
    }
    
    func filterList() { // should probably be called sort and not filter
    
        //sort contacts from a-z
        contacts.sort() { $0.name < $1.name }
    
        //remove contacts whose locations are nil
        contacts = contacts.filter() { $0.location != "nil"}
    
        //remove contacts whose titles phone email and department are all nil
        contacts = contacts.filter() {
            if($0.title != "" || $0.phone != "" || $0.email != "" || $0.department != ""){
                return true
            }
            return false
        }
    
    
        tableView.reloadData()
    }
    
    func didLoadContacts(contacts: [Contact]){
        self.contacts = contacts
        filterList()
        tableView.reloadData()
    }
    
    //MARK: -Table View
    
    //set number opf sections in table view
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    //delegeate that tells tabel view how many cells to have
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        //return size of array
    
        //if searching show count of filtered contacts
        if (searchController.active){
    
            return self.filteredContacts.count
    
        }else{
    
            return self.contacts.count
    
        }
    
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
        let cell = self.tableView.dequeueReusableCellWithIdentifier("customcell") as! CustomCell //from the customcell class
    
        //change color of cell text label
        cell.textLabel?.textColor = UIColor.blackColor()
        cell.backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 0.8)
    
        let contact : Contact
    
        //if users is searching then return filtered contacts
        if (searchController.active){
    
            contact = self.filteredContacts[indexPath.row]
    
        }else{
    
            contact = self.contacts[indexPath.row]
    
        }
    
    
        //handel assignment of text
        cell.textLabel?.text = contact.name
    
        //retrieve from customcell class
        cell.contact = contact
    
        return cell
    }
    
    
    //MARK: -Search
    
    func filterContentForSearchText(searchText: String, scope: String = "Title")
    {
        self.filteredContacts = self.contacts.filter({( contact: Contact) -> Bool in
    
            //filters contacts array
    
            var categoryMatch = (scope == "Title")
            var stringMatch = contact.name?.rangeOfString(searchText)
    
            return categoryMatch && (stringMatch != nil)
    
        })
    }
    
    func searchDisplayController(controller: UISearchController, shouldReloadTableForSearchString searchString: String!) -> Bool {
    
        self.filterContentForSearchText(searchString, scope: "Title")
    
        return true
    
    }
    
    
    func searchDisplayController(controller: UISearchController, shouldReloadTableForSearchScope searchOption: Int) -> Bool {
    
        self.filterContentForSearchText(self.searchController!.searchBar.text, scope: "Title")
    
        return true
    
    }
    
    //MARK: -Segue
    
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    
        if(segue.identifier == "detailview"){
            let cell = sender as! CustomCell
            let detailView = segue.destinationViewController as! DetailViewController
            detailView.preContact = cell.contact
    
        }
    }
    

    }

    My Problem now is that only the first search bar is stationary and it doesn't search when I type in it. The second search bar disappears on scroll. Also when I click a name it no longer segues to the next view.