当我开始输入自定义 UISearchBar 时,为什么我的 UITableView 没有出现?

Why isn't my UITableView appearing when I begin to type in the custom UISearchBar?

我正在尝试自定义 UISearchBar,当用户开始输入时,UITableView 出现在 UISearchBar 下方;然而,现在当开始打字时,什么也没有出现。我添加了打印语句,它们正在打印更改后的文本。需要更改什么才能显示 UITableView

SuggestionSearchBar


import UIKit

class SuggestionSearchBar: UISearchBar, UISearchBarDelegate {
    
    var suggestionTableView = UITableView(frame: .zero)
    let allPossibilities: [String]!
    var possibilities = [String]()

    init(del: UISearchBarDelegate, dropDownPossibilities: [String]) {
        self.allPossibilities = dropDownPossibilities
        super.init(frame: .zero)
        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() {
        suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        suggestionTableView.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.75)
        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: 300),
        ])
        hideSuggestions()
    }
    
    func showSuggestions() {
        suggestionTableView.isHidden = false
    }
    
    func hideSuggestions() {
        suggestionTableView.isHidden = true
    }
    
    @objc func searchBar(_ searchBar: UISearchBar) {
        print(searchBar.text!)
        showSuggestions()
        possibilities = allPossibilities.filter {[=10=].contains(searchBar.text!)}
        print(possibilities.count)
        suggestionTableView.reloadData()
    }
    
    @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
    }
    
}

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

您有几个问题需要处理 -- 但要解决第一个问题:

“为什么我的 UITableView 没有出现...”

您正在添加一个 table 视图作为子视图,但是您显示它 在其父视图的边界 之外。

您可以通过将 table 视图的顶部约束更改为以下内容来轻松确认这一点:

suggestionTableView.topAnchor.constraint(equalTo: bottomAnchor, constant: -20.0),

当您现在开始在搜索字段中输入内容时,它会被 table 视图的顶部部分覆盖。

要显示 table 视图,您需要在 table 视图的父视图上禁用 .clipsToBounds

func showSuggestions() {
    var sv = suggestionTableView.superview
    sv?.clipsToBounds = false
    suggestionTableView.isHidden = false
}

不过,下一个问题是您不能 select table 中的一行,因为它仍然在其父视图的范围之外。要处理这个问题,您需要实施 hitTest(...),但它会变得很复杂,因为 导航栏 获得命中,并且必须将命中传递给table.


编辑

对于更完整的示例...我将您的 SuggestionSearchBar 更改为包含 UISearchBarUITableView 以及所有相关逻辑的 UIView 子类。

我在代码中添加了注释,应该 使一切都非常清楚。

要启用与存在于其父视图边界之外的元素的交互,您需要做的是在两者中覆盖hitTest(...) 导航栏和自定义标题视图。

完成这项工作的一种方法是使用子类 UINavigationBar:

class CustomNavBar: UINavigationBar {
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

        // if the titleView is not an instance of SuggestionSearchBarView
        //  just allow the default hitTest
        guard let t = topItem, t.titleView is SuggestionSearchBarView else {
            return super.hitTest(point, with: event)
        }
        
        // loop through subviews, checking hitTest until we find one
        //  this will allow tapping a view outside the bounds of this view
        for v in self.subviews.reversed() {
            if v.subviews.count > 0 {
                for subv in v.subviews {
                    let p = subv.convert(point, from: self)
                    let r = subv.hitTest(p, with: event)
                    if r != nil {
                        return r
                    }
                }
            }
            let p = v.convert(point, from: self)
            let r = v.hitTest(p, with: event)
            if r != nil {
                return r
            }
        }

        return nil
    }
    
}

要在 Storyboard 中使用它,只需将导航控制器导航栏的自定义 Class 分配给 CustomNavBar

或者,如果您通过代码创建导航控制器:

let navigationController = UINavigationController(navigationBarClass: CustomNavBar.self, toolbarClass: nil)
    

可以可能通过混合hitTest(...)来做同样的事情,但这可能是一个更简单的方法。

这是修改后的 SuggestionSearchBar(现在是 SuggestionSearchBarView),以及相关的 UISearchBarDelegateUITableViewDataSource, UITableViewDelegate 扩展:

class SuggestionSearchBarView: UIView {
    
    var didSelect: ((String)->())?
    var searchTapped: ((String)->())?

    private let searchBar = UISearchBar()
    
    private let suggestionTableView = UITableView()
    private let tableHolderView = UIView()
    
    public var allPossibilities: [String] = []
    private var possibilities: [String] = []
    
    private var svClips: Bool = true
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() -> Void {
        
        suggestionTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        
        suggestionTableView.delegate = self
        suggestionTableView.dataSource = self
        
        searchBar.translatesAutoresizingMaskIntoConstraints = false
        suggestionTableView.translatesAutoresizingMaskIntoConstraints = false
        tableHolderView.translatesAutoresizingMaskIntoConstraints = false
        
        addSubview(searchBar)
        tableHolderView.addSubview(suggestionTableView)
        addSubview(tableHolderView)
        
        NSLayoutConstraint.activate([
            
            searchBar.topAnchor.constraint(equalTo: topAnchor),
            searchBar.leadingAnchor.constraint(equalTo: leadingAnchor),
            searchBar.trailingAnchor.constraint(equalTo: trailingAnchor),
            searchBar.bottomAnchor.constraint(equalTo: bottomAnchor),
            
            // top and height constraints for tableHolderView
            //  leading/trailing will be set in didMoveToSuperview()
            tableHolderView.topAnchor.constraint(equalTo: bottomAnchor),
            tableHolderView.heightAnchor.constraint(equalToConstant: 300),
            
            suggestionTableView.topAnchor.constraint(equalTo: tableHolderView.topAnchor),
            suggestionTableView.leadingAnchor.constraint(equalTo: tableHolderView.leadingAnchor),
            suggestionTableView.trailingAnchor.constraint(equalTo: tableHolderView.trailingAnchor),
            suggestionTableView.bottomAnchor.constraint(equalTo: tableHolderView.bottomAnchor),

        ])
        
        hideSuggestions()

        // allows the tableView to show outside our bounds
        clipsToBounds = false
        
        searchBar.searchBarStyle = UISearchBar.Style.prominent
        searchBar.placeholder = "Search"
        searchBar.isTranslucent = false
        searchBar.backgroundImage = UIImage()
        
        searchBar.setShowsCancelButton(true, animated: false)
        searchBar.delegate = self
        
        // some stylizing
        suggestionTableView.backgroundColor = .white
        suggestionTableView.layer.borderColor = UIColor.gray.cgColor
        suggestionTableView.layer.borderWidth = 1.0
        
        tableHolderView.layer.shadowColor = UIColor.black.cgColor
        tableHolderView.layer.shadowRadius = 4
        tableHolderView.layer.shadowOpacity = 0.6
        tableHolderView.layer.shadowOffset = CGSize(width: 0, height: 2)
        tableHolderView.layer.masksToBounds = false

    }
    
    override func didMoveToSuperview() {
        if let sv = superview {
            NSLayoutConstraint.activate([

                // if we want the tableView width to match the searchField
                //tableHolderView.leadingAnchor.constraint(equalTo: searchBar.searchTextField.leadingAnchor),
                //tableHolderView.trailingAnchor.constraint(equalTo: searchBar.searchTextField.trailingAnchor),

                // if we want the tableView to span the full view width
                tableHolderView.leadingAnchor.constraint(equalTo: sv.leadingAnchor),
                tableHolderView.trailingAnchor.constraint(equalTo: sv.trailingAnchor),
                
            ])
            
            // save .clipsToBounds state of superview so we can
            //  restore it when hiding the table view
            svClips = sv.clipsToBounds
        }
    }
    
    func updateTable() -> Void {
        let s = searchBar.text ?? ""
        if s.isEmpty {
            possibilities = allPossibilities
        } else {
            possibilities = allPossibilities.filter {[=14=].contains(s.lowercased())}
        }
        suggestionTableView.reloadData()
    }
    
    func showSuggestions() {
        // we need to set .clipsToBounds = false on the superView
        if let sv = superview {
            sv.clipsToBounds = false
        }
        tableHolderView.isHidden = false
        updateTable()
    }
    
    func hideSuggestions() {
        // set .clipsToBounds on the superView
        //  back to its original state
        if let sv = superview {
            sv.clipsToBounds = svClips
        }
        tableHolderView.isHidden = true
    }
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        
        // loop through subviews, checking hitTest until we find one
        //  this will allow tapping a view outside the bounds of this view
        for v in subviews.reversed() {
            let p = v.convert(point, from: self)
            let r = v.hitTest(p, with: event)
            if r != nil {
                return r
            }
        }
        
        return nil
        
    }
    
}

// MARK: searchBar Delegate funcs
extension SuggestionSearchBarView: UISearchBarDelegate {
    
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        endEditing(true)
    }
    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        let s = searchBar.text ?? ""
        print("Search Button Tapped:", s)
        // use the closure to tell the controller that the Search button was tapped
        searchTapped?(s)
    }
    
    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        showSuggestions()
    }
    
    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        hideSuggestions()
    }
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        updateTable()
    }
    
}

// MARK: tableView DataSource and Delegate funcs
extension SuggestionSearchBarView: 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(white: 0.25, alpha: 0.75)
        if traitCollection.userInterfaceStyle == .light {
            cell.backgroundColor = UIColor(white: 1.0, alpha: 0.75)
        }
        cell.textLabel?.text = possibilities[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected:", possibilities[indexPath.row])
        tableView.deselectRow(at: indexPath, animated: true)
        endEditing(true)
        // use the closure to tell the controller that a row was selected
        didSelect?(possibilities[indexPath.row])
    }
    
}

这是一个显示其用法的示例视图控制器:

class ViewController: UIViewController {

    let searchBar = SuggestionSearchBarView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        searchBar.translatesAutoresizingMaskIntoConstraints = false
        
        // titleView width will be auto-sized by navigationBar,
        //  but only if wider than available space
        // so, let's constrain the width to something like 10,000
        //  with less-than-required Priority
        let c = searchBar.widthAnchor.constraint(equalToConstant: 10000)
        c.priority = .defaultHigh
        c.isActive = true
        navigationItem.titleView = searchBar

        // give the searchBar some suggested values
        searchBar.allPossibilities = ["red", "green", "blue", "yellow"]
        
        // assign a closure so we can take action when a
        //  suggestion is selected
        searchBar.didSelect = { [weak self] str in
            if let self = self {
                let vc = UIViewController()
                switch str {
                case "red":
                    vc.view.backgroundColor = .red
                case "green":
                    vc.view.backgroundColor = .green
                case "blue":
                    vc.view.backgroundColor = .blue
                case "yellow":
                    vc.view.backgroundColor = .yellow
                default:
                    vc.view.backgroundColor = .white
                }
                self.navigationController?.pushViewController(vc, animated: true)
            }
        }

        // assign a closure so we can take action when a
        //  the Search button is tapped
        searchBar.searchTapped = { [weak self] str in
            print("Search button was tapped....")
            if let self = self {
                // do something
            }
        }
        
    }
    
}

请注意:这只是示例代码!!!试一试...如果它看起来可行对你来说,给它很多很多测试!