为什么当我开始输入自定义 UISearchBar 时我的 UITableView 没有出现? [英] Why isn't my UITableView appearing when I begin to type in the custom UISearchBar?

查看:20
本文介绍了为什么当我开始输入自定义 UISearchBar 时我的 UITableView 没有出现?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to make a custom UISearchBar that has a UITableView appear beneath the UISearchBar when a user begins typing; However, right now when the typing begins, there is nothing that appears. I added print statements and they are printing the text as it is changed. What needs to be changed so that the UITableView appears?

建议搜索栏

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

推荐答案

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

You have several issues to deal with -- but to address the first issue:

为什么我的 UITableView 没有出现..."

您正在添加一个 tableView 作为子视图,但您正在在其超级视图的边界之外显示它.

You are adding a tableView as a subview, but you are showing it outside the bounds of its superview.

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

You can easily confirm this by changing the table view's top constraint to this:

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

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

When you now start typing in the search field, it will become partially covered by the top of the table view.

要显示 table view,您需要在 table view 的 superview 上禁用 .clipsToBounds:

To get the table view to show, you need to disable .clipsToBounds on the table view's superview:

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

但是,下一个问题是您不能在表中选择一行,因为它仍然在其超级视图的范围之外.为了解决这个问题,你需要实现 hitTest(...),但是它会很复杂,因为 导航栏 得到了点击,它必须将命中传递到桌子上.

The next issue, though, is that you cannot select a row in the table, because it's still outside the bounds of its superview. To handle that, you'll need to implement hitTest(...), but it's going to be complicated because the navigation bar gets the hit, and it will have to pass the hit through to the table.

编辑

一个更完整的例子...我将你的 SuggestionSearchBar 更改为包含 UISearchBarUITableView<的 UIView 子类/code> 和所有相关的逻辑.

For a more complete example... I changed your SuggestionSearchBar to a UIView subclass that contains the UISearchBar and UITableView and all of the associated logic.

我在代码中加入了注释,应该让一切都变得清晰起来.

I've included comments in the code that should make everything pretty clear.

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

To enable interaction with elements that exist outside the bounds of their superview, what you'll need to do is override hitTest(...) in both the navigation bar and in the custom title view.

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

One way to make this work is to use a subclassed 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.

To use that in Storyboard, simply assign the Custom Class of the navigation controller's navigation bar to CustomNavBar.

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

Or, if you're creating the navigation controller via code:

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

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

You could probably do the same thing by swizzling hitTest(...), but this is probably a much simpler route.

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

Here's a modified SuggestionSearchBar (now SuggestionSearchBarView), along with the associated UISearchBarDelegate and UITableViewDataSource, UITableViewDelegate extensions:

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

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

And here is an example view controller showing its usage:

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

请注意:这是示例代码!!!试一试...如果它看起来对你有用,就试一试大量的测试!

这篇关于为什么当我开始输入自定义 UISearchBar 时我的 UITableView 没有出现?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆