【问题标题】:Why isn't my UITableView appearing when I begin to type in the custom UISearchBar?当我开始输入自定义 UISearchBar 时,为什么我的 UITableView 没有出现?
【发布时间】:2021-08-16 06:55:19
【问题描述】:

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

建议搜索栏


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 {$0.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
    }
    
}

视图控制器


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

【问题讨论】:

  • 您希望表格视图出现在哪里?你的视图控制器在哪里?
  • @ElTomato 我编辑了这个问题,在我的ViewController中包含了我是如何实现它的@
  • 为什么要在搜索栏中添加表格视图,而实际上您可能想用视图控制器显示它?
  • @ElTomato 我确实希望它与我的视图控制器一起显示,但我希望它可以在我的整个应用程序中使用,我认为如果我将它构建为自己的对象会更容易将此作为内置功能。

标签: ios swift xcode uitableview uisearchbar


【解决方案1】:

你有几个问题要处理——但要解决第一个问题:

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

您将 tableView 添加为子视图,但您将其显示为 超出其父视图的边界

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

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

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

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

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

不过,下一个问题是您无法在表格中选择一行,因为它仍然在其父视图的范围之外。要处理这个问题,您需要实现 hitTest(...),但这会很复杂,因为 导航栏 会获得点击,并且必须将点击传递到表格。


编辑

举一个更完整的例子...我将您的 SuggestionSearchBar 更改为 UIView 子类,其中包含 UISearchBarUITableView 以及所有相关逻辑。

我在代码中包含了 cmets,应该让一切都非常清楚。

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

完成这项工作的一种方法是使用子类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 中使用它,只需将导航控制器导航栏的自定义类分配给 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 {$0.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
            }
        }
        
    }
    
}

请注意:这只是示例代码!!!试一试...如果它看起来适合您,请试一试大量的测试!

【讨论】:

  • 感谢您的回复,这解决了我的问题。但是,我不知道如何实现 hitTest(...)。你有什么建议吗?如果那是更好的提问方式,我可以发布另一个问题。
  • @helloworld12345 - 请参阅我的回答的编辑
  • 我刚刚尝试使用它,但遇到了同样的问题。我仍然无法点击 TableView
  • @helloworld12345 - 你设置了自定义导航栏类​​吗?
  • 我相信是的。您将如何在上面发布的 ViewController 中实现它?
猜你喜欢
  • 1970-01-01
  • 2014-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多