skip to Main Content

I am trying to create a custom UISearchBar that is placed as the titleView of a navigationController. Using the following code, the suggestionTableView of suggestions appears perfectly; however, It does not recognize any taps. In fact, it is like the suggestionTableView isn’t even there because my taps are being sent to another view under the suggestion suggestionTableView. I was told that I could use hitTest(...) to catch these touches, but I don’t know how I would implement this in my SuggestionSearchBar or in my ViewController. How can I send these touches to the suggestionTableView?

SuggestionSearchBar

class SuggestionSearchBar: UISearchBar, UISearchBarDelegate {
    
    var suggestionTableView = UITableView(frame: .zero)
    let allPossibilities: [String]!
    var possibilities = [String]()
    //let del: UISearchBarDelegate!
    
    init(del: UISearchBarDelegate, dropDownPossibilities: [String]) {
        self.allPossibilities = dropDownPossibilities
        super.init(frame: .zero)
        isUserInteractionEnabled = true
        delegate = del
        searchTextField.addTarget(self, action: #selector(searchBar(_:)), for: .editingChanged)
        searchTextField.addTarget(self, action: #selector(searchBarCancelButtonClicked(_:)), for: .editingDidEnd)
        sizeToFit()
        addTableView()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func addTableView() {
        let cellHeight = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "").frame.height
        suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        suggestionTableView.backgroundColor = UIColor.clear
        //suggestionTableView.separatorStyle = .none
        suggestionTableView.tableFooterView = UIView()
        addSubview(suggestionTableView)
        
        suggestionTableView.delegate = self
        suggestionTableView.dataSource = self
        suggestionTableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            suggestionTableView.topAnchor.constraint(equalTo: bottomAnchor),
            suggestionTableView.rightAnchor.constraint(equalTo: rightAnchor),
            suggestionTableView.leftAnchor.constraint(equalTo: leftAnchor),
            suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
        ])
        hideSuggestions()
    }
    
    func showSuggestions() {
        let sv = suggestionTableView.superview
        sv?.clipsToBounds = false
        suggestionTableView.isHidden = false
    }
    
    func hideSuggestions() {
        suggestionTableView.isHidden = true
    }
    
    @objc func searchBar(_ searchBar: UISearchBar) {
        print(searchBar.text?.uppercased() ?? "")
        showSuggestions()
        possibilities = allPossibilities.filter {$0.contains(searchBar.text?.uppercased() ?? "")}
        print(possibilities.count)
        suggestionTableView.reloadData()
        if searchBar.text == "" || possibilities.count == 0 {
            hideSuggestions()
        }
    }
    
    @objc func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        hideSuggestions()
    }
}

extension SuggestionSearchBar: UITableViewDataSource, UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return possibilities.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = suggestionTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.backgroundColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 0.75)
        if traitCollection.userInterfaceStyle == .light {
            cell.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.75)
        }
        cell.textLabel?.text = possibilities[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //add method that fills in and searches based on the text in that indexpath.row
        print("selected")
    }
    
}

ViewController

import UIKit

class ViewController: UIViewController {

    lazy var searchBar = SuggestionSearchBar(del: self, dropDownPossibilities: ["red","green","blue","yellow"])

    override func viewDidLoad() {
        super.viewDidLoad()
        setUpUI()
    }

    func setUpUI() {
        setUpSearchBar()
    }
}

extension ViewController: UISearchBarDelegate {
    
    func setUpSearchBar() {
        searchBar.searchBarStyle = UISearchBar.Style.prominent
        searchBar.placeholder = "Search"
        searchBar.sizeToFit()
        searchBar.isTranslucent = false
        searchBar.backgroundImage = UIImage()
        searchBar.delegate = self
        navigationItem.titleView = searchBar
    }
    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        print(searchBar.text!)
    }
    
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.endEditing(true)
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        
    }
}

2

Answers


  1. As long as you are adding the UITableView as a subview to the SearchBar or UINavigationBar, you will keep finding these touch issues.

    A possible way to handle this would be have an empty container UIView instance at the call site (ViewController in your case) and ask SuggestionsSearchBar to add it’s tableView inside that container.

    SuggestionSearchBar(
        del: self, 
        suggestionsListContainer: <UIStackView_Inside_ViewController>, 
        dropDownPossibilities: ["red","green","blue","yellow"]
    )
    

    SuggestionsSearchBar will still manage everything about the tableView’s dataSource, delegate, it’s visibility etc. It just needs a view that can hold it’s tableView from the call site.

    UPDATE

    I’m highlighting only the relevant parts that need to change – everything else remains the same.

    class SuggestionSearchBar: UISearchBar, UISearchBarDelegate {
        
        init(del: UISearchBarDelegate, suggestionsListContainer: UIStackView, dropDownPossibilities: [String]) {
            //// All the current setUp
            addTableView(in: suggestionsListContainer)
        }
        
        private func addTableView(in container: UIStackView) {
            //// All the current setUp
            container.addArrangedSubview(suggestionTableView)
            
            NSLayoutConstraint.activate([
                suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
                /// We need to assign only height here
                /// top, leading, trailing will be driven by container at call site
            ])
        }
    }
    
    class ViewController: UIViewController {
    
        lazy var suggestionsListContainer: UIStackView = {
            let stackView = UIStackView()
            stackView.axis = .vertical
            stackView.distribution = .fill
            stackView.translatesAutoresizingMaskIntoConstraints = false
            return stackView
        }()
        
        lazy var searchBar = SuggestionSearchBar(
            del: self,
            suggestionsListContainer: suggestionsListContainer,
            dropDownPossibilities: ["red","green","blue","yellow"]
        )
    
        func setUpUI() {
            setUpSearchBar()
            setUpSuggestionsListContainer()
        }
        
        func setUpSuggestionsListContainer() {
            self.view.addSubview(suggestionsListContainer)
            
            NSLayoutConstraint.activate([
                suggestionsListContainer.topAnchor.constraint(equalTo: self.view.topAnchor),
                suggestionsListContainer.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
                suggestionsListContainer.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
                /// Height is not needed as it will be driven by tableView's height
            ])
        }
    }
    
    Login or Signup to reply.
  2. Reviewing your provided code, I can get the UI to work properly and even get the UITableViewDelegate callbacks inside SuggestionSearchBar.
    Here are the changes

    Suggestion Search Bar

    class SuggestionSearchBar: UISearchBar, UISearchBarDelegate {
        
        var suggestionTableView = UITableView(frame: .zero)
        let allPossibilities: [String]!
        var possibilities = [String]()
        var fromController: UIViewController?
        //let del: UISearchBarDelegate!
        
        init(del: UISearchBarDelegate, dropDownPossibilities: [String], fromController: UIViewController) {
            self.fromController = fromController
            self.allPossibilities = dropDownPossibilities
            super.init(frame: .zero)
            isUserInteractionEnabled = true
            delegate = del
            searchTextField.addTarget(self, action: #selector(searchBar(_:)), for: .editingChanged)
            searchTextField.addTarget(self, action: #selector(searchBarCancelButtonClicked(_:)), for: .editingDidEnd)
            sizeToFit()
            addTableView()
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        private func addTableView() {
            guard let view = fromController?.view else {return}
            let cellHeight = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "").frame.height
            suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
            suggestionTableView.backgroundColor = UIColor.clear
            //suggestionTableView.separatorStyle = .none
            suggestionTableView.tableFooterView = UIView()
            view.addSubview(suggestionTableView)
    //        addSubview(suggestionTableViewse
            
            suggestionTableView.delegate = self
            suggestionTableView.dataSource = self
            suggestionTableView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                suggestionTableView.topAnchor.constraint(equalTo: view.topAnchor),
                suggestionTableView.rightAnchor.constraint(equalTo: view.rightAnchor),
                suggestionTableView.leftAnchor.constraint(equalTo: view.leftAnchor),
                suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
            ])
    
            hideSuggestions()
        }
        
        func showSuggestions() {
            let sv = suggestionTableView.superview
            sv?.clipsToBounds = false
            suggestionTableView.isHidden = false
        }
        
        func hideSuggestions() {
            suggestionTableView.isHidden = true
        }
        
        @objc func searchBar(_ searchBar: UISearchBar) {
            print(searchBar.text?.uppercased() ?? "")
            showSuggestions()
            possibilities = allPossibilities.filter {$0.contains(searchBar.text?.lowercased() ?? "")}
            print(possibilities.count)
            suggestionTableView.reloadData()
            if searchBar.text == "" || possibilities.count == 0 {
                hideSuggestions()
            }
        }
        
        @objc func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            hideSuggestions()
        }
    }
    

    ViewController

    class ViewController: UIViewController {
    
       lazy var searchBar = SuggestionSearchBar(del: self, dropDownPossibilities: ["red","green","blue","yellow"], fromController: self)
    
        override func viewDidLoad() {
            super.viewDidLoad()
            setUpUI()
        }
    
        func setUpUI() {
            setUpSearchBar()
        }
    }
    

    To summarise the changes, the code above tried to add suggestionTableView
    to the SearchBarView which is not possible so I initialized SearchBarView with the reference to the parent ViewController which is stored as

    var fromController: UIViewController?
    

    This property is later used in addTableView()

    private func addTableView() {
            guard let view = fromController?.view else {return}
            let cellHeight = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "").frame.height
            suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
            suggestionTableView.backgroundColor = UIColor.clear
            //suggestionTableView.separatorStyle = .none
            suggestionTableView.tableFooterView = UIView()
            view.addSubview(suggestionTableView)
    //        addSubview(suggestionTableViewse
    
            suggestionTableView.delegate = self
            suggestionTableView.dataSource = self
            suggestionTableView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                suggestionTableView.topAnchor.constraint(equalTo: view.topAnchor),
                suggestionTableView.rightAnchor.constraint(equalTo: view.rightAnchor),
                suggestionTableView.leftAnchor.constraint(equalTo: view.leftAnchor),
                suggestionTableView.heightAnchor.constraint(equalToConstant: cellHeight*6),
            ])
    
            hideSuggestions()
        }
    

    There is another small change

    possibilities = allPossibilities.filter {$0.contains(searchBar.text?.lowercased() ?? "")}
    

    in @objc func searchBar(_ searchBar: UISearchBar) {

    Result

    logs

    Result Gif

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search