3

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) {
        
    }
}
helloworld12345
  • 176
  • 1
  • 4
  • 22

2 Answers2

1

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

Md. Ibrahim Hassan
  • 5,359
  • 1
  • 25
  • 45
0

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
        ])
    }
}
Tarun Tyagi
  • 9,364
  • 2
  • 17
  • 30
  • What code would I need to add in the SuggestionSearchBar class? – helloworld12345 Jun 03 '21 at 12:51
  • Instead of adding tableView to self `addSubview(suggestionTableView)`, add it to the passed in `suggestionsListContainer` like `suggestionsListContainer.addArrangedSubview(suggestionTableView)` – Tarun Tyagi Jun 03 '21 at 13:00
  • Can you write out code in your answer to show how this would be implemented in the ViewController and SuggestionSearchBar – helloworld12345 Jun 03 '21 at 16:26
  • See **UPDATE** to my answer. – Tarun Tyagi Jun 03 '21 at 16:40
  • I tried using this, but the ```suggestionTableView``` that was appearing before, is no longer appearing, is there a reason for this? – helloworld12345 Jun 03 '21 at 16:54
  • You need to debug this using `View Hierarchy Debugger` and find out at runtime where your views are and what are their frames. – Tarun Tyagi Jun 03 '21 at 17:16
  • I changed the zPosition of the ```suggestionTableView ``` and ```suggestionsListContainer ``` and this fixed the visual problem, but it is still not recognizing touches. Do you know why? – helloworld12345 Jun 03 '21 at 17:40
  • Why don't you use UISearchBar in combination with UISearchController? You'd define UISearchController (which owns the search-bar) and set "self.navigationItem.searchController = searchController". Look here for further details https://developer.apple.com/documentation/uikit/view_controllers/displaying_searchable_content_by_using_a_search_controller – Sardorbek Ruzmatov Jun 03 '21 at 20:52