While other answers seem helpful and partially do the job, it doesn't solve the issue of search bar not receiving the user's touches because it moves outside the bounds of its parent view as you change its frame.
What's worse is that, when you click on the search bar to make it the first responder, it is very likely that the tableView delegate will call tableView:didSelectRowAtIndexPath:
on cell that is laid out under the search bar.
In order to address those issues described above, you need to wrap the search bar in a plain UIView, a view which is capable of processing touches occurred outside of its boundaries. By this way, you can relay those touches to the search bar.
So let's do that first:
class SearchBarView: UIView {
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
for subview in subviews {
if !subview.userInteractionEnabled { continue }
let newPoint = subview.convertPoint(point, fromView: self)
if CGRectContainsPoint(subview.bounds, newPoint) {
return subview.hitTest(newPoint, withEvent: event)
}
}
return super.hitTest(point, withEvent: event)
}
}
Right now, we have a UIView subclass named SearchBarView
which is capable of receiving touches occurred outside of its boundaries.
Secondly, we should put the search bar into that new view while the view controller is loading its view:
class TableViewController: UITableViewController {
private let searchBar = UISearchBar(frame: CGRectZero)
...
override func viewDidLoad() {
super.viewDidLoad()
...
searchBar.sizeToFit()
let searchBarView = SearchBarView(frame: searchBar.bounds)
searchBarView.addSubview(searchBar)
tableView.tableHeaderView = searchBarView
}
}
At last, we should update the frame of the search bar as user scrolls down the table view so that it will stay fixed at the top:
override func scrollViewDidScroll(scrollView: UIScrollView) {
searchBar.frame.origin.y = max(0, scrollView.contentOffset.y)
}
Here is the result:
--
Important note: If your table view has sections, they will probably shadow your search bar so you need to bring the search bar on top of them every time the table view's bounds
gets updated.
viewDidLayoutSubviews
is a good place to do that:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
...
if let tableHeaderView = tableView.tableHeaderView {
tableView.bringSubviewToFront(tableHeaderView)
}
}
--
Hope this helps. You can download the example project from here.