helloworld12345
helloworld12345

Reputation: 127

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

Upvotes: 3

Views: 105

Answers (2)

Md. Ibrahim Hassan
Md. Ibrahim Hassan

Reputation: 5477

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

Upvotes: 1

Tarun Tyagi
Tarun Tyagi

Reputation: 10112

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

Upvotes: 0

Related Questions