你有几个问题要处理——但要解决第一个问题:
“为什么我的 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 子类,其中包含 UISearchBar 和 UITableView 以及所有相关逻辑。
我在代码中包含了 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),以及相关的UISearchBarDelegate 和UITableViewDataSource, 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
}
}
}
}
请注意:这只是示例代码!!!试一试...如果它看起来适合您,请试一试大量的测试!