Search code examples
iosswiftxcodeuisearchbarhittest

How to tell if UITableView in custom UISearchBar is touched?


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) {
        
    }
}

Solution

  • 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